Files
spl/stage1/spl_ir2vm.h
2026-08-06 19:26:30 +08:00

102 lines
6.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef __SPL_IR2VM_H__
#define __SPL_IR2VM_H__
#include "spl_ir.h"
#include "spl_type.h"
/*
* ================================================================
* SPL VM ABI (ir2vm 的正式约定;布局知识唯一来源)
* ================================================================
*
* 栈值槽 spl_val_t = sizeof(usize) = 8 字节。
* SIR 指令 type 只表达标量 tagSPL_I8..SPL_PTR聚合无 tag——
* 聚合值在 vreg 中是字节块,整体搬运用 NCALL vm_memcpy。
*
* ── 函数栈帧(字节坐标系,基址 = (char*)&stacks[fp])─────────────
*
* [canary] fp-8 .. 0 VM CALL 自动插入ir2vm 不触碰)
* [params 区] fp+0 .. fp+Na Na = C ABI 参数区字节数)
* [locals / vreg] fp+Na .. fp+Na+L
*
* ALLOC ceil((Na+L)/8) 一条 prologueepilogue 走 RET。
* LADDR(imm) 的 imm 是字节偏移canary 在 fp-1 槽,编译码不可见。
*
* ── 参数区 = C ABI聚合值按值 ─────────────────────────────────
* 标量参数align_up(off,8) 后占 8 字节(一槽)。
* 聚合参数:按聚合 size 排布跨多槽align8 + size
* 调用方:标量 ld/PUSH 一槽;聚合逐 8 字节块压栈(聚合 vreg 强制 8 对齐)。
* 被调方:标量参数映射参数槽;聚合参数在 prologue memcpy 参数区 → param vreg
* emit_value(param) 取 vreg 地址。参数槽数 = ceil(param_bytes/8) = CALL nargs。
*
* ── locals = 虚拟寄存器区 ────────────────────────────────────────
* 每个产生值的 IR 节点 = 一个 vreglocals 区按类型对齐的字节块)。
* 纯值节点type.const / mem.global_alloc / mem.alloca / 折叠 sizeof 等)不落 vreg
* 引用处重算PUSH / GADDR / LADDR其余产生值节点落 vreg引用处 LOAD。
* 聚合 vreg 引用处返回其地址LADDR搬运经 vm_memcpy。
* mem.alloca(tid)(cnt) 的 vreg 槽即缓冲区本体,节点值 = LADDR(vreg_off)。
* 所有运算走 load/storeld A; ld B; op; st Dst立即数直接 PUSH。
*
* ── 调用 ──────────────────────────────────────────────────────────
* 调用方:逐参数压栈(标量一槽;聚合按 C ABI 逐 8 字节块)→ push fn_addr(或
* native_idx) → CALL n / NCALL n。返回值落 call 节点 vreg。
* 原生函数IR 中 nodes 为空的函数(@extern 声明)→ 注册进 prog.natives
* 加载时由 spl_syscall_register 填 impl调用改 PUSH nat_idx + NCALL。
* 变参 nargs = 实际参数槽数。
*
* ── enum不展开直接保留─────────────────────────────────────
* IR 层保留 enum 类型(不再展开为 struct。enum 布局 = tag(usize, offset 0)
* + payload(offset 8最大变体。构造agg.construct(enum)(tag, payload)
* match先取 tag 判定变体,再 extract(enum, 1) 一跳取 payload
* field_tid = 变体具体类型(由 ast2ir 传入)。
*
* ── 返回 ──────────────────────────────────────────────────────────
* 标量ld vreg(val); RET(tag)RET 的 type 决定 VM 是否弹出返回值)。
* 聚合返回 = sret自展开不改 VM
* 被调函数签名尾部追加隐藏 *T 参数(最后一个);返回时把聚合 vreg
* memcpy 到 sret 地址RET(void)。
* 调用方在 locals 预留聚合槽(= call 节点 vreg压其地址为最后实参
* call 完成后聚合值已在该槽。
* 返回 voidRET(SPL_VOID)。
*
* ── 全局数据 ──────────────────────────────────────────────────────
* 任何聚合类型下(含 $root 顶层)的 var/const 属全局数据区。gdata 是
* spl_ir_node_vec_t每条 = 一个 value 节点type.const标量折叠值
* 聚合/无 init 零。ir2vm 遍历求值 → SIR gdata 条目(字节 blob
* mem.global_alloc(tid, const_node) 的 const_node = gdata 向量索引,
* 降级为 GADDR(idx)。符号(def) → gdata 索引的解析在 ast2ir 收集期
* gdata_ref 表ir2vm 按索引直接用。
*
* ── 类型布局C ABI唯一实现处────────────────────────────────
* type_align / type_size / field_offset标量按 bits/8 对齐;
* struct 顺序对齐 + 尾填充union 取最大字段enum = tag(8) + 最大 payload
* slice/range = [ptr, len] 各 8 字节array = len * elem。
*
* ── 分支 ──────────────────────────────────────────────────────────
* control.br/jmp/select 的 label 是 IR node ref基本块首指令。两遍发射
* 第一遍逐节点发指令并记录 label 节点 → 指令地址;第二遍回填
* JMP/BZ/BNZ 相对偏移 imm = target_addr - (jmp_addr + 1)。
* ================================================================
*/
/* 调试映射:每函数 IR 节点 → 首条指令 ip调试器显示当前 IR 节点用) */
typedef struct {
spl_ir_func_ref_t fid;
VEC(usize) node_first_ip; /* 索引 = IR 节点 ref值 = 该节点首指令绝对 ip */
} spl_ir2vm_fdbg_t;
typedef VEC(spl_ir2vm_fdbg_t) spl_ir2vm_fdbg_vec_t;
typedef struct {
const spl_ir_t *ir;
const spl_type_t *type;
spl_ir2vm_fdbg_vec_t fdbg; /* ir2vm_run 后填充splc0 -g 读取生成 debug 段 */
} spl_ir2vm_t;
void spl_ir2vm_init(spl_ir2vm_t *ctx, const spl_ir_t *ir, const spl_type_t *type);
void spl_ir2vm_drop(spl_ir2vm_t *ctx);
int spl_ir2vm_run(spl_ir2vm_t *ctx, const char *outpath); /* 返回错误数 */
void spl_ir2vm_dump(spl_ir2vm_t *ctx);
#endif /* __SPL_IR2VM_H__ */