Rust · CPU, assembler, kernel
Bedrock is a from-scratch virtual CPU, an assembler for its instruction set, and a small operating system kernel written in that assembly. This page runs the exact kernel the Rust CLI runs: it assembles the source, boots on the emulated machine, installs an interrupt vector table, and time-slices between two user tasks with a timer interrupt and a round-robin scheduler. Step it one instruction at a time and watch the registers, flags, mode, memory, and console change.
This page reimplements Bedrock's instruction set and runs the exact same kernel source the Rust CLI runs. Everything below is computed live in JavaScript: the same ISA, the same two-task round-robin scheduler, the same output.
Step: execute exactly one thing, one instruction, or the delivery of one pending timer interrupt. Watch the highlighted source line, the registers, and the mode badge change.Run / Pause: auto-step at the speed set by the slider. Great for watching the two tasks trade places.Run to completion: execute at full speed until the machine halts (both tasks have exited), then draw the whole run.Reset: reassemble the kernel and return the machine to its power-on state.Timer period: how many retired instructions between timer interrupts (the Rust default is 80). Lower it to preempt more aggressively. Changing it resets the machine.kernel/kernel.asm; the line at the program counter is highlighted as it runs.A, task 1 prints B.Step a few times. Boot runs in kernel mode: it sets the stack, installs the vector table with lidt, then hand-builds a saved stack frame for each task.iret of boot, the mode badge flips to user and you land in task0_entry. Task 0 prints its first A with trap SYS_PRINT.timer_handler saves the task's registers and switches to task 1. Now you see B appear.Run and watch the timeline fill with alternating blue and green, the two tasks time-slicing. When each task has printed its letter six times it calls trap SYS_EXIT; when both are gone the scheduler executes halt.Run to completion: the console should read ABBABABABABA, matching the Rust engine byte-for-byte.This page reimplements the same instruction set, assembler, and two-task round-robin scheduler the Rust engine ships, and runs the exact kernel/kernel.asm source. On load it self-checks its output against the Rust engine's ground truth, the value printed next to "The machine" above.
# cargo run --release -- kernel ABBABABABABA verified vs Rust engine: 2082 cycles, 24 timer irqs, 14 traps, 24 context switches # the same run every time: same config, identical trace and final state. # task 0 prints A, task 1 prints B, preempted by the timer between them.
Because Bedrock owns the whole machine, its registers, flags, memory, and instruction set, every claim about scheduling, interrupts, privilege levels, and determinism is asserted directly against observed state. A load or store out of the 64 KiB, a bad opcode, a privileged instruction in user mode, and a divide by zero all resolve to a clean guest fault instead of touching the host.
The primitives that matter
Every one is a real part of the machine you can single-step above, small enough to read in an afternoon.
One opcode byte, three register fields, and a 32-bit little-endian immediate. Decoding is trivially bounded, so a malformed stream can never run off the end.
lidt installs the timer, syscall, and fault vectors. The timer preempts the running task, and iret restores the interrupted registers, flags, stack pointer, and program counter exactly.
A mode bit in the flags register. Privileged instructions (lidt, sti, cli, iret, halt) run in kernel mode and fault if a user task attempts them.
A user task enters kernel mode through the syscall vector to print, yield, or exit. r6 carries the trap number and r1 the argument.
A suspended task's whole context lives on its own stack as [flags][pc][r0..r7]. A context switch is just save the stack pointer, pick the next runnable task, load its stack pointer, iret.
Out-of-bounds memory, a bad opcode, a divide by zero, and a wild stack pointer all raise a clean guest fault. If the fault itself cannot be delivered, the machine raises a double fault and halts instead of recursing.
Bedrock owns the machine end to end, so nothing is hand-waved and nothing is out of reach of a test.
Usually stop at the CPU and an assembler. Bedrock carries the same idea up through interrupts, privilege levels, and a preemptive scheduler written in the assembly itself.
Fixed-width encoding, a flags register, memory-mapped IO, and an interrupt vector table are taken from real hardware and shrunk to the smallest form that still teaches the mechanism.
Boot a real kernel and no unit test can assert what happened inside. Because Bedrock owns the emulated machine, every claim about scheduling, privilege, and determinism is checked directly against observed state.
A virtual CPU, an assembler, a disassembler, and a kernel in its own assembly. Small enough to single-step in a browser, strict enough to fuzz in CI, deterministic to the last of its 2082 cycles.
One binary with four subcommands, a library crate, and the correctness gate.
kernelBoot the bundled kernel: two tasks time-sliced by the timer. Add --trace for the mode, task, and event on every instruction.
runAssemble and run your own .asm program, with an optional --trace and a --timer period.
asm / disasmAssemble a source file to hex, or disassemble it back. assemble(disassemble(assemble(x))) reproduces the exact bytes.
The CPU, assembler, and disassembler are a Rust library crate. cargo test runs instruction semantics, interrupts, kernel behavior, and adversarial fuzzing.
# build the emulator, assembler, and bundled kernel cargo build --release # boot the bundled kernel: two tasks time-sliced by the timer cargo run --release -- kernel # trace it step by step (mode, task, and event per instruction) cargo run --release -- kernel --trace # assemble and run your own program, with a trace and a timer cargo run --release -- run path/to/program.asm --trace --timer 50 # assemble to hex, or disassemble cargo run --release -- asm path/to/program.asm cargo run --release -- disasm path/to/program.asm # the correctness gate: instruction semantics, interrupts, kernel, fuzzing cargo test