Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Super duper dumb recompiler. #44

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

Super duper dumb recompiler. #44

wants to merge 4 commits into from

Conversation

tomusdrw
Copy link
Owner

As an excercise I wrote a super simple transpiler from PVM to AssemblyScript (which can then be compiled with asc into WASM).

The idea is to figure out how things like this work, obviously it's not very useful practically, but can serve as an example how this kind of stuff could be done. Most of the code is generated by altering strings of current code (sic!), but I wanted to keep the changes minimal.

Practically what could work (but not fast enough) is generating WASM straight from the PVM code (no assembly script).

Some issues that I already see:

  1. How to make sure that the interpreter code and generated code stays in sync (fuzzer?).
  2. How to extract data out of the compiled module (i.e. final registers, memory, etc).
  3. How to handle ecalli - we need to first dump registers and be able to read the memory, and after the host call is finished we need to write down the data and resume execution.

How to run:

npm run build // build the project
./bin/index.js --compile ./fib.json # (or any jam test vector that does not use memory)
npm run compile-pvm
Generated Fibonacci AssemblyScript
declare function trap(): void;
declare function outOfGas(): void;
declare function fault(address: u32): void;
declare function dumpReg(idx: u32, data: i64): void;
let regs0: i64 = 0xffff0000;
let regs1: i64 = 0x0;
let regs2: i64 = 0x0;
let regs3: i64 = 0x0;
let regs4: i64 = 0x0;
let regs5: i64 = 0x0;
let regs6: i64 = 0x0;
let regs7: i64 = 0x9;
let regs8: i64 = 0x0;
let regs9: i64 = 0x0;
let regs10: i64 = 0x0;
let regs11: i64 = 0x0;
let regs12: i64 = 0x0;

let gas = 0x2710;


block0();

function block0(): void {
  gas -= 3;
  if (gas < 0) { outOfGas(); }
  { // LOAD_IMM
    regs8 = 1;
  };
  { // LOAD_IMM
    regs9 = 1;
  };
  { // JUMP
    return block9(); // 6 + 3
  };
}
function block8(): void {
  gas -= 1;
  if (gas < 0) { outOfGas(); }
  { // TRAP
    trap(); abort();
  };
}
function block9(): void {
  gas -= 2;
  if (gas < 0) { outOfGas(); }
  { // ADD_IMM_64
    const sum: u64 = regs7 + -1;
    regs7 = sum;
  };
  { // BRANCH_EQ_IMM
    const b = u64(0);
    if (regs7 === b) {
    return block24(); // 12 + 12
    }
  };
}
function block15(): void {
  gas -= 4;
  if (gas < 0) { outOfGas(); }
  { // MOVE_REG
    regs10 = regs8;
  };
  { // ADD_64
    regs8 = regs9 + regs8;
  };
  { // MOVE_REG
    regs9 = regs10;
  };
  { // JUMP
    return block9(); // 22 + 4294967283
  };
}
function block24(): void {
  gas -= 4;
  if (gas < 0) { outOfGas(); }
  { // MOVE_REG
    regs7 = regs8;
  };
  { // LOAD_IMM
    regs8 = 0;
  };
  { // LOAD_IMM
    regs9 = 0;
  };
  { // FALLTHROUGH
  };
}
function block31(): void {
  gas -= 1;
  if (gas < 0) { outOfGas(); }
  { // JUMP_IND
    const address = u32(regs0 + 0);
    // dynamic jump to address
  };
}
// utils
@inline function u32SignExtend(v: u32): i64 { return i64(i32(v)); }
@inline function u16SignExtend(v: u16): i64 { return i64(i32(i16(v))); }
@inline function u8SignExtend(v: u8): i64 { return i64(i32(i16(i8(v)))); }

// registers
dumpReg(0, regs0);
dumpReg(1, regs1);
dumpReg(2, regs2);
dumpReg(3, regs3);
dumpReg(4, regs4);
dumpReg(5, regs5);
dumpReg(6, regs6);
dumpReg(7, regs7);
dumpReg(8, regs8);
dumpReg(9, regs9);
dumpReg(10, regs10);
dumpReg(11, regs11);
dumpReg(12, regs12);

Which in turns compiles to the following WASM:

Compiled WABT
(module
 (type $0 (func))
 (type $1 (func (param i32 i64)))
 (import "generated-pvm" "outOfGas" (func $generated-pvm/outOfGas))
 (import "generated-pvm" "dumpReg" (func $generated-pvm/dumpReg (param i32 i64)))
 (global $generated-pvm/regs0 (mut i64) (i64.const 4294901760))
 (global $generated-pvm/regs1 (mut i64) (i64.const 0))
 (global $generated-pvm/regs2 (mut i64) (i64.const 0))
 (global $generated-pvm/regs3 (mut i64) (i64.const 0))
 (global $generated-pvm/regs4 (mut i64) (i64.const 0))
 (global $generated-pvm/regs5 (mut i64) (i64.const 0))
 (global $generated-pvm/regs6 (mut i64) (i64.const 0))
 (global $generated-pvm/regs7 (mut i64) (i64.const 9))
 (global $generated-pvm/regs8 (mut i64) (i64.const 0))
 (global $generated-pvm/regs9 (mut i64) (i64.const 0))
 (global $generated-pvm/regs10 (mut i64) (i64.const 0))
 (global $generated-pvm/regs11 (mut i64) (i64.const 0))
 (global $generated-pvm/regs12 (mut i64) (i64.const 0))
 (global $generated-pvm/gas (mut i32) (i32.const 10000))
 (global $~lib/memory/__data_end i32 (i32.const 8))
 (global $~lib/memory/__stack_pointer (mut i32) (i32.const 32776))
 (global $~lib/memory/__heap_base i32 (i32.const 32776))
 (memory $0 0)
 (table $0 1 1 funcref)
 (elem $0 (i32.const 1))
 (export "memory" (memory $0))
 (start $~start)
 (func $generated-pvm/block24
  global.get $generated-pvm/gas
  i32.const 4
  i32.sub
  global.set $generated-pvm/gas
  global.get $generated-pvm/gas
  i32.const 0
  i32.lt_s
  if
   call $generated-pvm/outOfGas
  end
  global.get $generated-pvm/regs8
  global.set $generated-pvm/regs7
  i64.const 0
  global.set $generated-pvm/regs8
  i64.const 0
  global.set $generated-pvm/regs9
 )
 (func $generated-pvm/block9
  (local $0 i64)
  global.get $generated-pvm/gas
  i32.const 2
  i32.sub
  global.set $generated-pvm/gas
  global.get $generated-pvm/gas
  i32.const 0
  i32.lt_s
  if
   call $generated-pvm/outOfGas
  end
  global.get $generated-pvm/regs7
  i64.const -1
  i64.add
  local.set $0
  local.get $0
  global.set $generated-pvm/regs7
  global.get $generated-pvm/regs7
  i64.const 0
  i64.eq
  if
   call $generated-pvm/block24
   return
  end
 )
 (func $generated-pvm/block0
  global.get $generated-pvm/gas
  i32.const 3
  i32.sub
  global.set $generated-pvm/gas
  global.get $generated-pvm/gas
  i32.const 0
  i32.lt_s
  if
   call $generated-pvm/outOfGas
  end
  i64.const 1
  global.set $generated-pvm/regs8
  i64.const 1
  global.set $generated-pvm/regs9
  call $generated-pvm/block9
  return
 )
 (func $start:generated-pvm
  call $generated-pvm/block0
  i32.const 0
  global.get $generated-pvm/regs0
  call $generated-pvm/dumpReg
  i32.const 1
  global.get $generated-pvm/regs1
  call $generated-pvm/dumpReg
  i32.const 2
  global.get $generated-pvm/regs2
  call $generated-pvm/dumpReg
  i32.const 3
  global.get $generated-pvm/regs3
  call $generated-pvm/dumpReg
  i32.const 4
  global.get $generated-pvm/regs4
  call $generated-pvm/dumpReg
  i32.const 5
  global.get $generated-pvm/regs5
  call $generated-pvm/dumpReg
  i32.const 6
  global.get $generated-pvm/regs6
  call $generated-pvm/dumpReg
  i32.const 7
  global.get $generated-pvm/regs7
  call $generated-pvm/dumpReg
  i32.const 8
  global.get $generated-pvm/regs8
  call $generated-pvm/dumpReg
  i32.const 9
  global.get $generated-pvm/regs9
  call $generated-pvm/dumpReg
  i32.const 10
  global.get $generated-pvm/regs10
  call $generated-pvm/dumpReg
  i32.const 11
  global.get $generated-pvm/regs11
  call $generated-pvm/dumpReg
  i32.const 12
  global.get $generated-pvm/regs12
  call $generated-pvm/dumpReg
 )
 (func $~start
  call $start:generated-pvm
 )
)

@tomusdrw tomusdrw changed the title Super duper dumb compiler. Super duper dumb recompiler. Jan 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant