Download Game! Currently 122 players and visitors. Last logged in:CovidPaziDollFloppy

Blitzer's Blog >> 71832

Back to blogs index
Posted: 29 Aug 2026 17:35 [ permalink ]
This is exactly the right philosophy for a robust systems project. By treating
this VM as a safe, isomorphic foundation, you keep the core brutally simple
and completely portable. Heavy lifting, complex data processing, or
platform-specific optimizations can be deferred to the host environment via
the SYSCALL/Efun interface.
Using Tagged Pointers on a 32-bit architecture is an incredibly elegant
solution, especially because x86 assembly can perform tag-aware math almost
for free.
Here is how we design the Tagged Pointer system to ensure it remains 100%
crash-proof and perfectly isomorphic across mquickjs, V8, and Ring 0 x86.
1. The 32-Bit Tagged Architecture
We will use the lowest 3 bits of the 32-bit register to define the type. This
means all memory allocations (strings, arrays, mappings) in the engine must be
8-byte aligned (which is standard practice and good for CPU cache anyway, even
on 32-bit systems).
Because the lowest 3 bits of an 8-byte aligned address are always 000, we can
steal them for our tags.
The Tag Map:
 * xxx...xxx 000 (0): Object Handle
 * xxx...xxx 001 (1): Integer (31-bit signed)
 * xxx...xxx 010 (2): String Handle
 * xxx...xxx 011 (3): Array Handle
 * xxx...xxx 100 (4): Mapping Handle
 * xxx...xxx 101 (5): Function Pointer / Closure Handle
 * xxx...xxx 110 (6): Buffer / Binary Handle
 * xxx...xxx 111 (7): Nil / Undefined / Error (e.g., 0x00000007 is exactly
nil)
2. The "Handle" Concept (Crash-Proofing the Foundation)
To maintain the absolute crash-proof guarantee, these tagged pointers are NOT
raw memory addresses.
If R1 holds 0x00000402 (Tag 010 = String, Value = 0x400), it does not point to
memory address 0x400. It means "String Index 128" (0x400 >> 3).
When the bytecode needs to read that string, it triggers an internal
bounds-checked lookup in the Master Context's String Table:
 * In JS/mquickjs: string_table[128]
 * In Ring 0 x86: mov eax, [STRING_TABLE_BASE + 128 * 4]
Why this is vital: A malicious or buggy LPC script can manipulate registers
all it wants, forge fake handles, and try to access memory. The worst it can
do is trigger a bounds-check failure (e.g., "Array Index Out of Bounds"),
which traps the VM safely. It can never segfault the kernel or break out of
the JS sandbox.
3. Blazing Fast x86 Tag-Aware Math
Because we are building this with x86 JIT in mind, let's look at how
beautifully tagged integers (tag 1) map to raw assembly.
If you have a 31-bit integer N, its tagged representation is (N << 1) | 1.
If you want to add two tagged integers (A and B) in bytecode, you don't even
need to untag them!
Mathematically: ((A << 1) | 1) + ((B << 1) | 1) = ((A + B) << 1) | 2
Notice the tag became 2. To fix it, you just subtract 1.
The x86 JIT translation for LPC x = a + b;:
; Assume R1 (EAX) is 'a', R2 (EBX) is 'b'
add eax, ebx    ; 1 cycle: Add the two tagged registers
dec eax         ; 1 cycle: Subtract 1 to restore the integer tag '1'
; Optionally: one instruction here to check if the tag is still valid, 
; trapping to host if a user tried to ADD a string and an integer.

This means basic LPC arithmetic runs at nearly bare-metal CPU speed, with zero
function calls or complex type-checking overhead on the happy path.
4. Bridging the Foundation to the Host (The Extensibility)
As you noted, this system shouldn't force everything into its constraints.
Let's say you want to parse JSON. Writing a JSON parser in LPC bytecode would
work, but it would be slow. Instead, you map an Efun: mixed parse_json(string).
 * LPC calls SYSCALL 45 (parse_json).
 * The VM suspends and hands the String Handle to the host environment via the
Interrupt Vector Table.
 * On V8/Chrome: The JS runtime calls JSON.parse(), builds the resulting
mappings/arrays in the JS heap, assigns them new Handles, and returns the root
Handle to the VM.
 * On HAUKI-X86 Bare Metal: The syscall routes to a highly optimized,
hardware-accelerated C/ASM JSON parser. It allocates the memory in the Ring 0
arena, registers the handles, and returns control to the JIT.
The LPC code has no idea whether it's running in Chrome or on bare metal; it
just receives a Mapping Handle and continues.
The Next Step: The Object Layout
We have the 32-bit tagged registers, the crash-proof handle system, and the
fixed-width 32-bit bytecode instructions.
The next piece to design is The Object.
In LPC, everything revolves around objects (blueprints and clones) which
contain:
 * Variables (State)
 * Functions (Bytecode)
 * Inheritance mapping
To keep this perfectly isomorphic and easily hot-reloadable, how should we
structure the memory layout of a compiled LPC Object?
 * Option A: A flat contiguous block of memory [Header | Variables Array |
Bytecode Array]. (Extremely easy to copy, serialize, and send over a
network/WebSocket to another node).
 * Option B: Split it up. Variables live in a global heap, bytecode lives in a
separate read-only executable page. (More secure for Ring 0 JIT, easier to
share bytecode between clones).
Given your networking and microkernel background (Mesh OS, HAUKI), Option B
(sharing bytecode, isolating state) seems optimal, but what is your preference
for the object architecture?