798 lines
24 KiB
Plaintext
798 lines
24 KiB
Plaintext
// 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;
|
|
}
|
|
|
|
// 追加一条 size 字节的零初始化全局数据(BSS 语义);返回其索引。
|
|
fn add_zero_data(self: *Prog, size: usize): usize {
|
|
var entry = ByteBuffer.new();
|
|
match ByteBuffer.reserve(&entry, size) {
|
|
.Ok => {},
|
|
.Err(error) => { ByteBuffer.drop(&entry); ret 0; },
|
|
}
|
|
if size != 0 {
|
|
entry.storage.len = size;
|
|
var p = entry.storage.data as *u8;
|
|
var i: usize = 0;
|
|
while i < size { p[i] = 0; i = i + 1; }
|
|
}
|
|
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();
|
|
}
|