Source:
moonlive_asm_host.h
moonlive_asm_host¶
The desktop backend: a named-instruction assembler for arm64 and x86-64, in the textbook MacroAssembler shape.
It appends one instruction at a time and back-patches label offsets, so the lowering composes a multi-op statement without hand-computing a branch displacement. This header is the neutral surface, and the per-ISA encodings live beside it in the implementation.
Enumerations¶
| Name | Description |
|---|---|
Reg |
An abstract register the assembler maps to a real one, which keeps the IR architecture-neutral. |
Cond |
A branch condition, holding the ones the IR needs. |
Reg¶
An abstract register the assembler maps to a real one, which keeps the IR architecture-neutral.
| Value | Description |
|---|---|
R0 |
|
R1 |
|
R2 |
|
R3 |
|
R4 |
|
R5 |
|
R6 |
|
R7 |
|
R8 |
|
R9 |
|
R10 |
|
R11 |
|
R12 |
|
R13 |
|
kRegCount |
|
R0 |
|
R1 |
|
R2 |
|
R3 |
|
R4 |
|
R5 |
|
R6 |
|
R7 |
|
R8 |
|
R9 |
|
R10 |
|
R11 |
|
R12 |
|
R13 |
|
kRegCount |
|
R0 |
|
R1 |
|
R2 |
|
R3 |
|
R4 |
|
R5 |
|
R6 |
|
R7 |
|
R8 |
|
R9 |
|
kRegCount |
Cond¶
A branch condition, holding the ones the IR needs.
| Value | Description |
|---|---|
Lo |
|
Hs |
|
Ne |
|
Ge |
|
Lo |
|
Hs |
|
Lo |
|
Hs |
Typedefs¶
| Return | Name | Description |
|---|---|---|
uint8_t |
Label |
An index into the label table; bind fixes its position and branches to it are patched then. |
Label¶
An index into the label table; bind fixes its position and branches to it are patched then.
HostAssembler¶
src/platform/desktop/moonlive_asm_host.h:46Public Methods¶
inline ~HostAssembler()
: Frees the buffer only when this emitter owns it; a copy would double-free, so copying is deleted.
inline explicit HostAssembler(size_t cap = kCodeCap)
: Allocate a cap-byte code buffer, sized per script because backends differ by up to 1.9x.
inline HostAssembler(uint8_t * out, size_t cap)
: Emit straight into the caller's staging buffer, which halves a compile's transient heap.
HostAssembler(const HostAssembler &) = delete
: Not copied: a copied owner would free the same buffer twice.
HostAssembler & operator=(const HostAssembler &) = delete
: Not copy-assigned, for the same reason.
inline void finalize()
: Resolve every fixup against its bound label; call it once, after the last instruction.
inline void alignForEntry()
: Pad to the alignment a function entry needs, which on both host architectures is nothing.
inline const uint8_t * bytes() const
: The finished bytes, valid only after finalize.
inline size_t size() const
: How many bytes were emitted.
inline bool overflowed() const
: Whether any write was dropped for want of room.
void emitBytes(const uint8_t * p, size_t n)
: Append raw bytes, which owns the bounds check and the overflow flag.
Label newLabel()
: A fresh label, to be bound once and branched to any number of times.
void bind(Label l)
: Mark l's position = current offset.
void prologue(uint8_t slots)
: Open a frame with room for slots spilled values, parking a frame pointer at its base.
void epilogue()
: Tear the frame down, then ret.
void retValue(Reg a)
: Park a where the ABI returns a value, before the epilogue tears the frame down.
void spillStore(Reg r, uint8_t slot)
: Write a register into a spill slot.
void spillLoad(Reg r, uint8_t slot)
: Read a spill slot back into a register.
void slotAddr(Reg d, uint8_t slot)
: Address one slot, which is how a call builds its argument block.
void movPtr(Reg d, const void * p)
: A full-width address into a register (ConstPtr).
void movImm(Reg d, int32_t imm)
: d = imm.
void addImm(Reg d, Reg a, int32_t imm)
: d = a + imm.
void addReg(Reg d, Reg a, Reg b)
: d = a + b.
void mulImm(Reg d, Reg a, int32_t imm)
: d = a * imm (index scaling by a constant).
void mulReg(Reg d, Reg a, Reg b)
: d = a * b (index scaling by a runtime cpl).
void mulhi(Reg d, Reg a, Reg b)
: d = the SIGNED high 32 bits of a * b (Q16.16 multiply).
void shlImm(Reg d, Reg a, uint8_t n)
: d = a << n.
void sarImm(Reg d, Reg a, uint8_t n)
: d = a >> n, ARITHMETIC (sign-filling).
void shrImm(Reg d, Reg a, uint8_t n)
: d = a >> n, LOGICAL (zero-filling).
void store8(Reg base, Reg off, Reg val)
: Byte store: base[off] = val (low 8 bits).
void load8(Reg d, Reg base, int32_t imm)
: d = base[imm] (zero-extended byte): control read.
void load32(Reg d, Reg base, int32_t imm)
: d = base[imm..imm+3]: a whole 4-byte slot.
void store32(Reg base, int32_t imm, Reg val)
: base[imm..imm+3] = val (offset IMMEDIATE).
void load32Idx(Reg d, Reg base, Reg off)
: d = base[off..off+3], index in a REG.
void store32Idx(Reg base, Reg off, Reg val)
: base[off..off+3] = val, index in a REG.
void load8Idx(Reg d, Reg base, Reg off)
: d = base[off] (zero-extended byte), index in a REG.
void movReg(Reg d, Reg a)
: d = a.
void branchIfZero(Reg a, Label l)
: if a == 0 goto l.
void branchGeU(Reg a, Reg b, Label l)
: if (unsigned)a >= b goto l.
void branchGeS(Reg a, Reg b, Label l)
: if (signed)a >= b goto l.
void branchNe(Reg a, Reg b, Label l)
: if a != b goto l.
void call(Reg d, Reg a, Reg b, Reg c, const void * fn)
: Call a host built-in: d = fn(a, b, c), preserving every caller register across it.
void callLabel(Label l, Reg d = R0, bool take = false)
: Call a function in this block by label: the script-to-script call.
void ret()
: Return to the caller.
Public Static Attributes¶
constexpr uint8_t kMaxSpillSlots = kTotalSlots
: The allocator's slot range plus the parked host arguments.
Public Types¶
using RegType = Reg
: The register type the shared lowering works in, each backend's Reg being its own enum.
More info¶
One buffer, borrowed¶
The emitter writes into the caller's staging buffer rather than a twin of its own. Allocating a second buffer held two code caps at once, and on a classic ESP32 fragmented to a 24 KB largest block that allocation failed. The compile then reported a script too large for a script that compiles fine.
Sized per script¶
The code cap is chosen per script rather than shared, the three backends differing by up to 1.9x on identical source.
Why the buffer is heap¶
The assembler is a stack local, so a cap-sized member put 2 KB on the compile chain's stack and overflowed the task on a classic ESP32.