Source:
moonlive_asm_riscv.h
moonlive_asm_riscv¶
The ESP32-P4 backend, written against the same named-instruction interface as the other two.
Fixed four-byte instructions and a standard call ABI make it the simplest of the three. Every register maps into the caller-saved set, so a call saves the live pool explicitly, and branch displacements are back-patched against bound labels.
Enumerations¶
| Name | Description |
|---|---|
[Reg] |
Fourteen registers, which is what the caller-saved set alone provides. |
[Cond] |
A branch condition, holding the ones the IR needs. |
Reg¶
Fourteen registers, which is what the caller-saved set alone provides.
| 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 |
RiscvAssembler¶
src/platform/esp32/moonlive_asm_riscv.h:43Public Methods¶
inline ~RiscvAssembler()
: Frees the buffer only when this emitter owns it; a copy would double-free, so copying is deleted.
inline explicit RiscvAssembler(size_t cap = kCodeCap)
: Allocate a cap-byte code buffer, sized per script because backends differ by up to 1.9x.
inline RiscvAssembler(uint8_t * out, size_t cap)
: Emit straight into the caller's staging buffer, which halves a compile's transient heap.
RiscvAssembler(const RiscvAssembler &) = delete
: Not copied: a copied owner would free the same buffer twice.
RiscvAssembler & operator=(const RiscvAssembler &) = 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 this architecture 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 prologue(uint8_t slots = 0)
: Open a frame for slots spilled values; no slots emits nothing, so a plain script pays none.
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.
Label newLabel()
: A fresh label, to be bound once and branched to any number of times.
void bind(Label l)
: Fix this label's position at the current offset.
void movPtr(Reg d, const void * p)
: A full-width address into a register (ConstPtr).
void movImm(Reg d, int32_t imm)
: An immediate of any width into a register, through the small-immediate add where it fits.
void movReg(Reg d, Reg a)
: Mv rd, ra (addi rd, ra, 0).
void addImm(Reg d, Reg a, int32_t imm)
: Addi rd, ra, imm.
void addReg(Reg d, Reg a, Reg b)
: add rd, ra, rb.
void mulReg(Reg d, Reg a, Reg b)
: Mul rd, ra, rb.
void mulhi(Reg d, Reg a, Reg b)
: Mulh rd, ra, rb: the SIGNED high 32 bits.
void shlImm(Reg d, Reg a, uint8_t n)
: slli rd, ra, #n.
void sarImm(Reg d, Reg a, uint8_t n)
: srai rd, ra, #n: arithmetic, sign-filling.
void shrImm(Reg d, Reg a, uint8_t n)
: srli rd, ra, #n: logical, zero-filling.
void store8(Reg base, Reg off, Reg val)
: add tmp,base,off ; sb val,0(tmp).
void load8(Reg d, Reg base, int32_t imm)
: Lbu rDst, imm(rBase): a control read.
void load32(Reg d, Reg base, int32_t imm)
: Lw rDst, imm(rBase): a whole 4-byte slot.
void store32(Reg base, int32_t imm, Reg val)
: sw rVal, imm(rBase) (offset IMMEDIATE).
void load32Idx(Reg d, Reg base, Reg off)
: add tmp,base,off ; lw d,0(tmp).
void store32Idx(Reg base, Reg off, Reg val)
: add tmp,base,off ; sw val,0(tmp).
void load8Idx(Reg d, Reg base, Reg off)
: add tmp,base,off ; lbu d,0(tmp).
void branchIfZero(Reg a, Label l)
: Beqz a, l (bge x0, a... use bgeu against x0).
void branchGeU(Reg a, Reg b, Label l)
: Bgeu a, b, l.
void branchGeS(Reg a, Reg b, Label l)
: Bge a, b, l.
void branchNe(Reg a, Reg b, Label l)
: Bne a, b, l.
void call(Reg d, Reg a, Reg b, Reg c, const void * fn)
: Standard call to a host built-in.
void callLabel(Label l, Reg d = R0, bool take = false)
: Call a function in this block by label: the script-to-script call.
void epilogue()
: Undo prologue's frame (if any), then ret.
void retValue(Reg a)
: Park a where the ABI returns a value, before the epilogue tears the frame down.
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¶
Fourteen registers¶
That is what the caller-saved set alone provides, and a nested loop needs more than the twelve every backend started with. It briefly reached eighteen by mapping callee-saved registers too, on the reasoning that the routine saves what it uses. It does not, having no prologue, so it would have returned with four of them clobbered.
Slots address from the frame pointer¶
A call moves the stack pointer underneath them, and reading a spilled value after a built-in call is the ordinary case.
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.