// Expect retcode = 0 // SPL VM 机器码容器(移植自 stage0/spl_mcode.c 的构建/序列化/dump 部分)。 // 只负责「内存中的 prog」与「写入 .sir」,不包含反序列化(由 C 侧 spl_cli 读)。 // // 字符串一律是 ByteView(借用上层 IR/attr 的名称,本模块不复制、不释放); // gdata 条目是 ByteBuffer,由本模块拥有并释放。 // // 二进制格式(元数据字段均为 u64 小端): // [HEADER] magic(8) nfuncs(8) ninsns(8) nnatives(8) nstrs(8) ndata(8) // [FUNCS] 每项: name_len(8) name(pad8, 含结尾 NUL) idx_of_strtab(8) // nargs(8) ninsns(8) address(8) // [INSTRS] 每项: opcode(2) type(2) imm(8) = 12 字节 // [NATIVES] 每项: name_len(8) name(pad8, 含结尾 NUL) idx_of_strtab(8) // [STRTAB] 每项: slen(8) str(pad8) // [GDATA] 每项: dsize(8) data(pad8) type .{ .Error, .Status, .make } = @import("utils/error.spl"); type .{ .Vec } = @import("utils/vec.spl"); type .{ .ByteView } = @import("utils/byte_view.spl"); type .{ .ByteBuffer } = @import("utils/byte_buffer.spl"); type .{ .append_usize, .append_i64 } = @import("utils/fmt.spl"); type Mem = @import("sys/mem.spl"); type File = @import("sys/file.spl"); type .{ .open_cstr, .write_all, .read_all } = @import("utils/io.spl"); type Op = enum { Error, Push, Dup, Drop, Swap, Pick, Rot, Add, Sub, Mul, DivS, DivU, RemS, RemU, Neg, And, Or, Xor, Not, Shl, ShrU, ShrS, Eq, Ne, Slt, Sle, Ult, Ule, Sgt, Sge, Ugt, Uge, Trunc, Sext, Zext, Jmp, Bz, Bnz, Halt, Call, Calli, Ret, Alloc, Laddr, Gaddr, Load, Store, Ncall, Nlib, Bk, Dbg, } type VmKind = enum { Void, Bool, I8, U8, I16, U16, I32, U32, I64, U64, Isize, Usize, F32, F64, Ptr, } type VmIns = struct { opcode: Op, ty: VmKind, imm: usize, } type VmFunc = struct { name: ByteView, idx_of_strtab: usize, nargs: usize, ninsns: usize, address: usize, } type VmNative = struct { name: ByteView, idx_of_strtab: usize, } type Prog = @self; insns: Vec, // VmIns funcs: Vec, // VmFunc natives: Vec, // VmNative strtab: Vec, // ByteView gdata: Vec, // ByteBuffer(索引 0 为占位项) type ProgResult = enum { Ok: Prog, Err: Error, } fn op_name(op: Op): []u8 { match op { .Error => { ret "error"; }, .Push => { ret "push"; }, .Dup => { ret "dup"; }, .Drop => { ret "drop"; }, .Swap => { ret "swap"; }, .Pick => { ret "pick"; }, .Rot => { ret "rot"; }, .Add => { ret "add"; }, .Sub => { ret "sub"; }, .Mul => { ret "mul"; }, .DivS => { ret "div_s"; }, .DivU => { ret "div_u"; }, .RemS => { ret "rem_s"; }, .RemU => { ret "rem_u"; }, .Neg => { ret "neg"; }, .And => { ret "and"; }, .Or => { ret "or"; }, .Xor => { ret "xor"; }, .Not => { ret "not"; }, .Shl => { ret "shl"; }, .ShrU => { ret "shr_u"; }, .ShrS => { ret "shr_s"; }, .Eq => { ret "eq"; }, .Ne => { ret "ne"; }, .Slt => { ret "slt"; }, .Sle => { ret "sle"; }, .Ult => { ret "ult"; }, .Ule => { ret "ule"; }, .Sgt => { ret "sgt"; }, .Sge => { ret "sge"; }, .Ugt => { ret "ugt"; }, .Uge => { ret "uge"; }, .Trunc => { ret "trunc"; }, .Sext => { ret "sext"; }, .Zext => { ret "zext"; }, .Jmp => { ret "jmp"; }, .Bz => { ret "bz"; }, .Bnz => { ret "bnz"; }, .Halt => { ret "halt"; }, .Call => { ret "call"; }, .Calli => { ret "calli"; }, .Ret => { ret "ret"; }, .Alloc => { ret "alloc"; }, .Laddr => { ret "laddr"; }, .Gaddr => { ret "gaddr"; }, .Load => { ret "load"; }, .Store => { ret "store"; }, .Ncall => { ret "ncall"; }, .Nlib => { ret "nlib"; }, .Bk => { ret "breakpoint"; }, .Dbg => { ret "dbg"; }, } ret "error"; } fn kind_name(k: VmKind): []u8 { match k { .Void => { ret "void"; }, .Bool => { ret "bool"; }, .I8 => { ret "i8"; }, .U8 => { ret "u8"; }, .I16 => { ret "i16"; }, .U16 => { ret "u16"; }, .I32 => { ret "i32"; }, .U32 => { ret "u32"; }, .I64 => { ret "i64"; }, .U64 => { ret "u64"; }, .Isize => { ret "isize"; }, .Usize => { ret "usize"; }, .F32 => { ret "f32"; }, .F64 => { ret "f64"; }, .Ptr => { ret "ptr"; }, } ret "???"; } // ---- 生命周期 ---- fn new(): ProgResult { var prog = Prog { .insns = Vec.blank(@sizeof(VmIns), @alignof(VmIns)), .funcs = Vec.blank(@sizeof(VmFunc), @alignof(VmFunc)), .natives = Vec.blank(@sizeof(VmNative), @alignof(VmNative)), .strtab = Vec.blank(@sizeof(ByteView), @alignof(ByteView)), .gdata = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)), }; // 索引 0 占位,使真实数据地址非零。 var placeholder = ByteBuffer.new(); match Vec.push(&prog.gdata, &placeholder as *u8) { .Ok => {}, .Err(error) => { Vec.drop(&prog.gdata); Vec.drop(&prog.strtab); Vec.drop(&prog.natives); Vec.drop(&prog.funcs); Vec.drop(&prog.insns); ret .Err(error); }, } ret .Ok(prog); } fn gdata_at(self: *Prog, i: usize): *ByteBuffer { match Vec.get(&self.gdata, i) { .Ok(ptr) => { ret ptr as *ByteBuffer; }, .Err(error) => { ret null; }, } ret null; } fn native_at(self: *Prog, i: usize): *VmNative { match Vec.get(&self.natives, i) { .Ok(ptr) => { ret ptr as *VmNative; }, .Err(error) => { ret null; }, } ret null; } fn drop(self: *Prog) { var i: usize = 0; while i < self.gdata.len { var e = gdata_at(self, i); if e != null { ByteBuffer.drop(e); } i = i + 1; } Vec.drop(&self.gdata); Vec.drop(&self.strtab); Vec.drop(&self.natives); Vec.drop(&self.funcs); Vec.drop(&self.insns); } // ---- 构建 ---- fn add_instr(self: *Prog, op: Op, ty: VmKind, imm: usize): usize { var i = self.insns.len; if i < self.insns.cap { var p = (self.insns.data as *VmIns) + i; p.opcode = op; p.ty = ty; p.imm = imm; self.insns.len = i + 1; ret i + 1; } var ins = VmIns { .opcode = op, .ty = ty, .imm = imm }; if !Vec.push_raw(&self.insns, &ins as *u8) { ret 0; } ret self.insns.len; } fn add_func(self: *Prog, name: ByteView, nargs: usize, address: usize): usize { var f = VmFunc { .name = name, .idx_of_strtab = 0, .nargs = nargs, .ninsns = 0, .address = address, }; match Vec.push(&self.funcs, &f as *u8) { .Ok => {}, .Err(error) => { ret 0; }, } ret self.funcs.len; } // 复制 size 字节到新的 gdata 条目;返回其索引(0 表示失败)。 // ptr 是上层(Ast/Ast2Ir)拥有的稳定字节,调用期间保持有效。 fn add_data(self: *Prog, ptr: usize, size: usize): usize { var entry = ByteBuffer.new(); match ByteBuffer.reserve(&entry, size) { .Ok => {}, .Err(error) => { ByteBuffer.drop(&entry); ret 0; }, } if size != 0 { if ptr == 0 { ByteBuffer.drop(&entry); ret 0; } ignore_ptr(Mem.copy(entry.storage.data as *any, ptr as *any, size)); entry.storage.len = size; } match Vec.push(&self.gdata, &entry as *u8) { .Ok => {}, .Err(error) => { ByteBuffer.drop(&entry); ret 0; }, } ret self.gdata.len - 1; } // 返回 name 对应的 native 索引;不存在则登记。name 为空指针时返回 -1。 fn get_or_add_native(self: *Prog, name: ByteView): isize { if name.ptr == null { ret -1; } var i: usize = 0; while i < self.natives.len { var n = native_at(self, i); if n != null && ByteView.eq(n.name, name) { ret i as isize; } i = i + 1; } var nat = VmNative { .name = name, .idx_of_strtab = 0 }; match Vec.push(&self.natives, &nat as *u8) { .Ok => {}, .Err(error) => { ret -1; }, } ret (self.natives.len - 1) as isize; } // ---- dump(输出必须与 stage0 spl_prog_dump 逐字节一致)---- fn out_s(out: *ByteBuffer, s: []u8): bool { match ByteBuffer.append(out, s) { .Ok => { ret true; }, .Err(error) => { ret false; } } ret false; } fn out_u(out: *ByteBuffer, v: usize): bool { match append_usize(out, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; } } ret false; } fn out_i(out: *ByteBuffer, v: i64): bool { match append_i64(out, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; } } ret false; } // %4zd:右对齐、最小宽度 4 的有符号十进制。 fn out_width4_i(out: *ByteBuffer, v: i64): bool { var width: usize = 0; if v < 0 { if v >= -9 { width = 2; } else if v >= -99 { width = 3; } else { width = 4; } } else { if v < 10 { width = 1; } else if v < 100 { width = 2; } else if v < 1000 { width = 3; } else { width = 4; } } var pad: usize = 0; if width < 4 { pad = 4 - width; } if !out_s(out, " "[0..pad]) { ret false; } ret out_i(out, v); } fn dump(self: *Prog, out: *ByteBuffer): bool { if !out_s(out, ";; SPL bytecode dump: \n") { ret false; } if !out_s(out, ";; ninsns=") { ret false; } if !out_u(out, self.insns.len) { ret false; } if !out_s(out, " nfuncs=") { ret false; } if !out_u(out, self.funcs.len) { ret false; } if !out_s(out, " nnatives=") { ret false; } if !out_u(out, self.natives.len) { ret false; } if !out_s(out, " nstrs=") { ret false; } if !out_u(out, self.strtab.len) { ret false; } if !out_s(out, "\n\n") { ret false; } if self.funcs.len > 0 { if !out_s(out, ";; --- functions ---\n") { ret false; } var i: usize = 0; while i < self.funcs.len { var f = func_at(self, i); if f == null { @assert(false, "func missing in dump"); ret false; } if !out_s(out, " ") { ret false; } if !out_s(out, ByteView.as_slice(f.name)) { ret false; } if !out_s(out, " nargs=") { ret false; } if !out_u(out, f.nargs) { ret false; } if !out_s(out, " ninsns=") { ret false; } if !out_u(out, f.ninsns) { ret false; } if !out_s(out, " addr=") { ret false; } if !out_u(out, f.address) { ret false; } if !out_s(out, "\n") { ret false; } i = i + 1; } if !out_s(out, "\n") { ret false; } } if self.natives.len > 0 { if !out_s(out, ";; --- natives ---\n") { ret false; } var i: usize = 0; while i < self.natives.len { var n = native_at(self, i); if n == null { @assert(false, "native missing in dump"); ret false; } if !out_s(out, " ") { ret false; } if !out_s(out, ByteView.as_slice(n.name)) { ret false; } if !out_s(out, "\n") { ret false; } i = i + 1; } if !out_s(out, "\n") { ret false; } } if self.strtab.len > 0 { if !out_s(out, ";; --- string table ---\n") { ret false; } var i: usize = 0; while i < self.strtab.len { var s = str_at(self, i); if !out_s(out, " ") { ret false; } if !out_i(out, i as i64) { ret false; } if !out_s(out, ": \"") { ret false; } if !out_s(out, ByteView.as_slice(s)) { ret false; } if !out_s(out, "\"\n") { ret false; } i = i + 1; } if !out_s(out, "\n") { ret false; } } if !out_s(out, ";; --- instructions ---\n") { ret false; } var i: usize = 0; while i < self.insns.len { var ins = ins_at(self, i); if !out_width4_i(out, i as i64) { ret false; } if !out_s(out, ": ") { ret false; } if !out_s(out, op_name(ins.opcode)) { ret false; } if @enum2int(ins.ty) != @enum2int(VmKind.Void) { if !out_s(out, " ") { ret false; } if !out_s(out, kind_name(ins.ty)) { ret false; } } if !out_s(out, " ") { ret false; } if !out_i(out, ins.imm as i64) { ret false; } if @enum2int(ins.opcode) == @enum2int(Op.Jmp) || @enum2int(ins.opcode) == @enum2int(Op.Bz) || @enum2int(ins.opcode) == @enum2int(Op.Bnz) { if !out_s(out, " ; -> ") { ret false; } if !out_u(out, i + 1 + ins.imm) { ret false; } } else if @enum2int(ins.opcode) == @enum2int(Op.Call) { if !out_s(out, " ; nargs=") { ret false; } if !out_u(out, ins.imm) { ret false; } if !out_s(out, ", from stack") { ret false; } } else if @enum2int(ins.opcode) == @enum2int(Op.Calli) { if !out_s(out, " ; indirect call") { ret false; } } else if @enum2int(ins.opcode) == @enum2int(Op.Gaddr) { if !out_s(out, " ; gdata[") { ret false; } if !out_u(out, ins.imm) { ret false; } if !out_s(out, "]") { ret false; } } if !out_s(out, "\n") { ret false; } i = i + 1; } ret true; } fn ins_at(self: *Prog, i: usize): VmIns { if i >= self.insns.len { ret VmIns { .opcode = .Error, .ty = .Void, .imm = 0 }; } ret ((self.insns.data as *VmIns) + i).*; } fn func_at(self: *Prog, i: usize): *VmFunc { match Vec.get(&self.funcs, i) { .Ok(ptr) => { ret ptr as *VmFunc; }, .Err(error) => { ret null; }, } ret null; } fn str_at(self: *Prog, i: usize): ByteView { match Vec.get(&self.strtab, i) { .Ok(ptr) => { ret (ptr as *ByteView).*; }, .Err(error) => { ret ByteView.empty(); }, } ret ByteView.empty(); } // ---- 序列化 ---- fn align8(n: usize): usize { ret (n + 7) & (~(7 as usize)); } // 说明:以下 put_* 直接写入已预留容量的 ByteBuffer 存储(store 已按精确总大小 reserve), // 跳过 ByteBuffer.append → Vec.append 的校验/拷贝路径。若容量不足会越界,故 store 先校验容量。 fn put_u8(out: *ByteBuffer, b: u8): bool { out.storage.data[out.storage.len] = b; out.storage.len = out.storage.len + 1; ret true; } // 直接按小端整型写(x86 小端;VM 的 STORE 用 memcpy,未对齐也安全)。 fn put_u16(out: *ByteBuffer, v: usize): bool { var d = out.storage.data + out.storage.len; (d as *u16).* = v as u16; out.storage.len = out.storage.len + 2; ret true; } fn put_u64(out: *ByteBuffer, v: usize): bool { var d = out.storage.data + out.storage.len; (d as *u64).* = v as u64; out.storage.len = out.storage.len + 8; ret true; } fn put_zeros(out: *ByteBuffer, n: usize): bool { if n != 0 { ignore_ptr(Mem.set((out.storage.data + out.storage.len) as *any, 0, n)); out.storage.len = out.storage.len + n; } ret true; } fn put_view(out: *ByteBuffer, v: ByteView): bool { if v.len == 0 { ret true; } Mem.copy((out.storage.data + out.storage.len) as *any, v.ptr as *any, v.len); out.storage.len = out.storage.len + v.len; ret true; } // 写入「带结尾 NUL、再按 8 对齐」的名称字段(对应 C 侧 nlen = strlen+1)。 fn put_name_field(out: *ByteBuffer, name: ByteView): bool { var nlen = name.len + 1; if !put_u64(out, nlen) { ret false; } if !put_view(out, name) { ret false; } if !put_u8(out, 0) { ret false; } ret put_zeros(out, align8(nlen) - nlen); } // 把 prog 序列化为二进制写入 out(不落盘,供内嵌 VM 直接用)。 fn encode(self: *Prog, out: *ByteBuffer): Status { // 精确预估输出大小并一次性预留,避免大文件逐字节增长导致的重复拷贝。 var total: usize = 48; var fi: usize = 0; while fi < self.funcs.len { var f = func_at(self, fi); if f != null { var nlen = f.name.len + 1; total = total + 8 + align8(nlen) + 32; } fi = fi + 1; } total = total + self.insns.len * 12; fi = 0; while fi < self.natives.len { var n = native_at(self, fi); if n != null { var nlen = n.name.len + 1; total = total + 8 + align8(nlen) + 8; } fi = fi + 1; } fi = 0; while fi < self.strtab.len { total = total + 8 + align8(str_at(self, fi).len); fi = fi + 1; } fi = 1; while fi < self.gdata.len { var e = gdata_at(self, fi); if e != null { total = total + 8 + align8(e.storage.len); } fi = fi + 1; } match ByteBuffer.reserve(out, total) { .Ok => {}, .Err(error) => {} } if out.storage.cap < total || out.storage.data == null { ret .Err(make(.Io)); } if !put_view(out, ByteView.from_slice("SPLBIN")) { ret .Err(make(.Io)); } if !put_zeros(out, 2) { ret .Err(make(.Io)); } var nfuncs = self.funcs.len; var ninsns = self.insns.len; var nnatives = self.natives.len; var nstrs = self.strtab.len; var ndata: usize = 0; if self.gdata.len > 0 { ndata = self.gdata.len - 1; } if !put_u64(out, nfuncs) { ret .Err(make(.Io)); } if !put_u64(out, ninsns) { ret .Err(make(.Io)); } if !put_u64(out, nnatives) { ret .Err(make(.Io)); } if !put_u64(out, nstrs) { ret .Err(make(.Io)); } if !put_u64(out, ndata) { ret .Err(make(.Io)); } var i: usize = 0; while i < nfuncs { var f = func_at(self, i); if f == null { ret .Err(make(.Io)); } if !put_name_field(out, f.name) { ret .Err(make(.Io)); } if !put_u64(out, f.idx_of_strtab) { ret .Err(make(.Io)); } if !put_u64(out, f.nargs) { ret .Err(make(.Io)); } if !put_u64(out, f.ninsns) { ret .Err(make(.Io)); } if !put_u64(out, f.address) { ret .Err(make(.Io)); } i = i + 1; } i = 0; while i < ninsns { var ins = ins_at(self, i); var d = out.storage.data + out.storage.len; (d as *u16).* = @enum2int(ins.opcode) as u16; ((d + 2) as *u16).* = @enum2int(ins.ty) as u16; ((d + 4) as *u64).* = ins.imm as u64; out.storage.len = out.storage.len + 12; i = i + 1; } i = 0; while i < nnatives { var n = native_at(self, i); if n == null { ret .Err(make(.Io)); } if !put_name_field(out, n.name) { ret .Err(make(.Io)); } if !put_u64(out, n.idx_of_strtab) { ret .Err(make(.Io)); } i = i + 1; } i = 0; while i < nstrs { var s = str_at(self, i); if !put_u64(out, s.len) { ret .Err(make(.Io)); } if !put_view(out, s) { ret .Err(make(.Io)); } if !put_zeros(out, align8(s.len) - s.len) { ret .Err(make(.Io)); } i = i + 1; } i = 1; while i < self.gdata.len { var e = gdata_at(self, i); if e == null { ret .Err(make(.Io)); } var dsize = e.storage.len; if !put_u64(out, dsize) { ret .Err(make(.Io)); } if !put_view(out, ByteView.from_slice(ByteBuffer.as_slice(e.*))) { ret .Err(make(.Io)); } if !put_zeros(out, align8(dsize) - dsize) { ret .Err(make(.Io)); } i = i + 1; } ret .Ok; } fn store(self: *Prog, path: ByteView): Status { var out = ByteBuffer.new(); defer { ByteBuffer.drop(&out); } match encode(self, &out) { .Ok => {}, .Err(error) => { ret .Err(error); }, } var f = open_cstr(path, ByteView.from_slice("wb")); if !File.valid(&f) { ret .Err(make(.Io)); } var w = write_all(&f, ByteBuffer.as_slice(out)); var closed = File.close(&f); match w { .Ok => {}, .Err(error) => { ret .Err(error); }, } if closed != 0 { ret .Err(make(.Io)); } ret .Ok; } fn ignore_ptr(p: *any) {} fn ignore_usize(v: usize) {} // ---- 测试 ---- fn test_dump_basic() { var pr: Prog = undefined; match new() { .Ok(v) => { pr = v; }, .Err(error) => { @assert(false, "prog init"); ret; }, } defer { drop(&pr); } ignore_usize(add_instr(&pr, .Halt, .Void, 0)); var out = ByteBuffer.new(); defer { ByteBuffer.drop(&out); } if !dump(&pr, &out) { @assert(false, "dump"); ret; } var expected: [69]u8 = .[ ';', ';', ' ', 'S', 'P', 'L', ' ', 'b', 'y', 't', 'e', 'c', 'o', 'd', 'e', ' ', 'd', 'u', 'm', 'p', ':', ' ', 10, ';', ';', ' ', 'n', 'i', 'n', 's', 'n', 's', '=', '1', ' ', 'n', 'f', 'u', 'n', 'c', 's', '=', '0', ' ', 'n', 'n', 'a', 't', 'i', 'v', 'e', 's', '=', '0', ' ', 'n', 's', 't', 'r', 's', '=', '0', 10, 10, ';', ';', ' ', '-', '-', '-', ]; var got = ByteBuffer.as_slice(out); @assert(got.len > expected.len, "dump longer than header prefix"); var i: usize = 0; while i < expected.len { @assert(got.ptr[i] == expected[i], "dump header bytes"); i = i + 1; } } fn test_native_dedup() { var pr: Prog = undefined; match new() { .Ok(v) => { pr = v; }, .Err(error) => { @assert(false, "prog init"); ret; }, } defer { drop(&pr); } var vm = ByteView.from_slice("vm_printf"); @assert(get_or_add_native(&pr, vm) == 0, "first native idx"); @assert(get_or_add_native(&pr, vm) == 0, "dedup native idx"); @assert(pr.natives.len == 1, "native count"); } fn test_store_roundtrip() { var pr: Prog = undefined; match new() { .Ok(v) => { pr = v; }, .Err(error) => { @assert(false, "prog init"); ret; }, } defer { drop(&pr); } ignore_usize(add_func(&pr, ByteView.from_slice("main"), 0, 0)); ignore_usize(add_instr(&pr, .Halt, .Void, 0)); var path = ByteView.from_slice("build/_mcode_roundtrip.tmp"); match store(&pr, path) { .Ok => {}, .Err(error) => { @assert(false, "store to file"); ret; }, } var data = ByteBuffer.new(); defer { ByteBuffer.drop(&data); } match read_all(path, &data) { .Ok => {}, .Err(error) => { @assert(false, "read back"); ret; }, } var b = ByteBuffer.as_slice(data); // header(48) + 一个函数项(8 + 8 + 4*8 = 48) + 一条指令(12) = 108 @assert(b.len == 108, "serialized size"); @assert(b.ptr[0] == 'S' && b.ptr[1] == 'P' && b.ptr[2] == 'L' && b.ptr[3] == 'B' && b.ptr[4] == 'I' && b.ptr[5] == 'N' && b.ptr[6] == 0 && b.ptr[7] == 0, "magic"); @assert(b.ptr[8] == 1, "nfuncs"); @assert(b.ptr[16] == 1, "ninsns"); @assert(b.ptr[24] == 0 && b.ptr[32] == 0 && b.ptr[40] == 0, "nnatives/nstrs/ndata"); // 函数项起始:name_len(5) + "main\0" + pad(3) @assert(b.ptr[48] == 5, "name_len"); @assert(b.ptr[56] == 'm' && b.ptr[57] == 'a' && b.ptr[58] == 'i' && b.ptr[59] == 'n' && b.ptr[60] == 0, "name bytes"); @assert(b.ptr[61] == 0 && b.ptr[62] == 0 && b.ptr[63] == 0, "name padding"); // 指令:opcode(2)=halt(38) type(2)=void(0) imm(8)=0 var ins_off: usize = 96; @assert(b.ptr[ins_off + 0] == 38 && b.ptr[ins_off + 1] == 0, "opcode bytes"); @assert(b.ptr[ins_off + 2] == 0 && b.ptr[ins_off + 3] == 0, "type bytes"); @assert(b.ptr[ins_off + 4] == 0 && b.ptr[ins_off + 11] == 0, "imm bytes"); } fn main() { test_dump_basic(); test_native_dedup(); test_store_roundtrip(); }