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.
| 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. |
- Words are unsigned 256-bit values stored as 32 bytes in big-endian order.
- Arithmetic wraps modulo 2^256.
SUBpopsa, thenb, and pushesb - a.JUMPIpops the destination first and the condition second. Both values are consumed whether or not the jump is taken.- A
JUMPIdestination is validated only when its condition is non-zero. - A taken jump must target the byte offset of a
JUMPDESTinstruction. STOPreturns 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.
LOADandSTOREindexes are word indexes, not byte offsets.
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.
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.binThe generated runner contains native code and does not need to link against
LLVM. LLVM is needed only while building and running bc_compiler.
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