Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bytecode Compiler

This project compiles the task bytecode into a native object file containing a function compatible with the supplied C++ wrapper:

extern void f(unsigned char* in, unsigned char* out);

The compiler decodes bytecode, lowers it to LLVM IR, verifies the module, and emits a native relocatable object.

Instruction Set

Opcode Mnemonic Operands Behaviour
0x00 STOP none Halt execution
0x01 LOAD 1 byte — index into in Push in[i] onto the stack
0x02 STORE 1 byte — index into out Pop top of stack; write to out[i]
0x03 POP none Discard the top of stack
0x04 ADD none Pop two values, push their sum
0x05 SUB none Pop two values, push their difference
0x06 DUP none Push a copy of the top of stack
0x07 JUMPDEST none Mark this position as a valid jump target; a no-op at runtime
0x08 JUMPI none Pop destination (top of stack), pop condition; if condition is non-zero, jump to destination. Both values are consumed regardless of whether the jump is taken. The destination must be the bytecode offset of a JUMPDEST instruction.

Bytecode semantics

  • Words are unsigned 256-bit values stored as 32 bytes in big-endian order.
  • Arithmetic wraps modulo 2^256.
  • SUB pops a, then b, and pushes b - a.
  • JUMPI pops the destination first and the condition second. Both values are consumed whether or not the jump is taken.
  • A JUMPI destination is validated only when its condition is non-zero.
  • A taken jump must target the byte offset of a JUMPDEST instruction.
  • STOP returns normally from the generated function.
  • Falling off the end of the bytecode is a runtime error.
  • A taken jump to an invalid destination is a runtime error.
  • Stack underflow and stack overflow are runtime errors.
  • LOAD and STORE indexes are word indexes, not byte offsets.

Resource limits and runtime errors

The generated function uses a fixed stack with a limit of MAX_STACK_WORDS = 1024 to keep the its size to 32 KiB. Every generated push and pop operation must check the stack limit or required depth before accessing the stack.

Generated code will handle runtime failures by entering a dedicated error block and invoking llvm.trap. Compilation failures are instead printed as diagnostics by the compiler command.

The wrapper always allocates 64 output words. A STORE index outside the range 0..64 is therefore rejected while decoding.

The generated function receives no input length, so LOAD cannot perform bounds checking. The caller must provide enough complete 32-byte input words. The supplied wrapper additionally requires a non-empty input whose size is divisible by 32.

Compiler interface

The command-line interface is:

bc_compiler <bytecode-file> <object-file>

For example:

mkdir -p build
cargo run --release -- program.bc build/program.o
c++ -std=c++17 harness/wrapper.cpp build/program.o -o build/runner
./build/runner input.bin output.bin

The generated runner contains native code and does not need to link against LLVM. LLVM is needed only while building and running bc_compiler.

Development checks

The project provides three top-level development commands:

make build # build an optimized compiler binary
make test  # run decoder and native integration tests
make e2e   # run only the native compile/link/execute tests

About

A simple compiler for a defined set of instructions, decoding to bytecode, lowering to LLVM IR and emitting an object for running.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages