stage0 新增内存复制指令 重构vm交互

This commit is contained in:
zzy committed 2026-09-29 21:11:15 +08:00
1 parent 0b8f649818
commit 40cffa9cb5
22 files changed
+626 -158

No files matched your search

+4 -1
View File
@@ -45,7 +45,7 @@ type Body = enum {
type_expr: usize,
init_expr: usize,
},
ParamDecl: struct { attr_list: Vec, name: ByteView, type_expr: usize },
ParamDecl: struct { attr_list: Vec, is_comptime: bool, name: ByteView, type_expr: usize },
StatementRet: struct { expr: usize },
StatementBreak: struct { label: usize, expr: usize },
StatementContinue: struct { label: usize },
@@ -124,6 +124,7 @@ type Body = enum {
PatternList: struct { items: Vec },
PatternAggregate: struct { items: Vec },
PatternField: struct { name: ByteView, pattern: usize },
PatternRef: struct { pat: usize },
ExprComptime: struct { body: usize },
ExprComptimeInit: struct { name: ByteView, args: Vec, fields: Vec },
}
@@ -201,6 +202,7 @@ fn kind_name(body: Body): []u8 {
.PatternList(_) => { ret "pattern_list"; },
.PatternAggregate(_) => { ret "pattern_aggregate"; },
.PatternField(_) => { ret "pattern_field"; },
.PatternRef(_) => { ret "pattern_ref"; },
}
ret "?";
}
@@ -590,6 +592,7 @@ fn dump_node(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool):
.PatternList(v) => { ok = dump_list(ast, buf, stack, v.items); },
.PatternAggregate(v) => { ok = dump_list(ast, buf, stack, v.items); },
.PatternField(p) => { ok = dump_opt(ast, buf, stack, p.pattern, true); },
.PatternRef(p) => { ok = dump_opt(ast, buf, stack, p.pat, true); },
}
stack.len = stack.len - 1;
+128 -13
View File
@@ -279,14 +279,77 @@ fn variant_at(v: Vec, idx: usize): usize {
// ---- 模式绑定 ----
fn emit_pattern_bind(self: *Ast2Ir, pat_ref: usize, src_ptr: usize, tid: usize) {
fn pattern_has_ref(self: *Ast2Ir, pat_ref: usize): bool {
if pat_ref == 0 { ret false; }
var pat = Ast.node(self.ast, pat_ref);
if pat == null { ret false; }
match pat.body {
.PatternRef(_) => { ret true; },
.PatternList(x) => {
var i: usize = 0;
while i < x.items.len {
if pattern_has_ref(self, list_at(x.items, i)) { ret true; }
i = i + 1;
}
ret false;
},
.PatternTuple(x) => {
var i: usize = 0;
while i < x.items.len {
if pattern_has_ref(self, list_at(x.items, i)) { ret true; }
i = i + 1;
}
ret false;
},
.PatternDot(x) => { ret pattern_has_ref(self, x.sub_pattern); },
.PatternAggregate(x) => {
var i: usize = 0;
while i < x.items.len {
var f = Ast.node(self.ast, list_at(x.items, i));
if f != null {
match f.body {
.PatternField(pf) => {
if pattern_has_ref(self, pf.pattern) { ret true; }
},
_ => {},
}
}
i = i + 1;
}
ret false;
},
_ => { ret false; },
}
ret false;
}
fn emit_pattern_bind(self: *Ast2Ir, pat_ref: usize, src_ptr: usize, tid: usize, place_mode: bool) {
if pat_ref == 0 || src_ptr == 0 { ret; }
var pat = Ast.node(self.ast, pat_ref);
if pat == null { ret; }
match pat.body {
.PatternIdent(x) => {
if pat.resolved_def_id != 0 { ir_map_put(self, pat.resolved_def_id, src_ptr); }
if pat.resolved_def_id != 0 {
if place_mode {
var slot = IrBuilder.mem_alloca(self.ir, tid, const_usize(self, 1));
var v = IrBuilder.mem_load(self.ir, tid, src_ptr);
ignore_usize(IrBuilder.mem_store(self.ir, tid, slot, v));
ir_map_put(self, pat.resolved_def_id, slot);
} else {
ir_map_put(self, pat.resolved_def_id, src_ptr);
}
}
ret;
},
.PatternRef(x) => {
var in = Ast.node(self.ast, x.pat);
var ptr_tid: usize = 0;
if in != null { ptr_tid = node_ir(self, in); }
if ptr_tid == 0 { ptr_tid = TypeTable.make_ptr(&self.sema.types.ir, tid); }
var slot = IrBuilder.mem_alloca(self.ir, ptr_tid, const_usize(self, 1));
ignore_usize(IrBuilder.mem_store(self.ir, ptr_tid, slot, src_ptr));
emit_pattern_bind(self, x.pat, slot, ptr_tid, false);
ret;
},
.ExprIntLit(x) => { ret; },
@@ -299,7 +362,7 @@ fn emit_pattern_bind(self: *Ast2Ir, pat_ref: usize, src_ptr: usize, tid: usize)
.PatternList(x) => {
var i: usize = 0;
while i < x.items.len {
emit_pattern_bind(self, list_at(x.items, i), src_ptr, tid);
emit_pattern_bind(self, list_at(x.items, i), src_ptr, tid, place_mode);
i = i + 1;
}
ret;
@@ -316,7 +379,7 @@ fn emit_pattern_bind(self: *Ast2Ir, pat_ref: usize, src_ptr: usize, tid: usize)
while i < n {
var ft = variant_at(variants, i);
var fp = IrBuilder.mem_field(self.ir, tid, src_ptr, i);
emit_pattern_bind(self, list_at(x.items, i), fp, ft);
emit_pattern_bind(self, list_at(x.items, i), fp, ft, place_mode);
i = i + 1;
}
ret;
@@ -333,7 +396,7 @@ fn emit_pattern_bind(self: *Ast2Ir, pat_ref: usize, src_ptr: usize, tid: usize)
var payload_ir = variant_at(variants, vidx);
var pptr = IrBuilder.mem_field(self.ir, tid, src_ptr, vidx + 1);
if x.sub_pattern != 0 {
emit_pattern_bind(self, x.sub_pattern, pptr, payload_ir);
emit_pattern_bind(self, x.sub_pattern, pptr, payload_ir, place_mode);
}
ret;
},
@@ -357,7 +420,7 @@ fn emit_pattern_bind(self: *Ast2Ir, pat_ref: usize, src_ptr: usize, tid: usize)
var ft = variant_at(variants, idx);
var fp = IrBuilder.mem_field(self.ir, tid, src_ptr, idx);
if pf.pattern != 0 {
emit_pattern_bind(self, pf.pattern, fp, ft);
emit_pattern_bind(self, pf.pattern, fp, ft, place_mode);
}
}
},
@@ -429,7 +492,6 @@ fn match_check_literal(self: *Ast2Ir, pat_ref: usize, scrut_ir: usize, scrut_slo
fn emit_var_decl(self: *Ast2Ir, n: *Node): usize {
var var_ir = node_ir(self, n);
if var_ir == 0 { ret 0; }
var slot = IrBuilder.mem_alloca(self.ir, var_ir, const_usize(self, 1));
var iref: usize = 0;
var pat_ref: usize = 0;
@@ -438,11 +500,35 @@ fn emit_var_decl(self: *Ast2Ir, n: *Node): usize {
.ConstDecl(x) => { iref = x.init_expr; pat_ref = x.pattern; },
_ => {},
}
if pattern_has_ref(self, pat_ref) {
if iref == 0 {
Diag.err(self.diag, n.span, .NoLvalue,
"reference binding requires an initialized place");
ret 0;
}
var place = emit_lvalue(self, iref);
if place == 0 {
var in = Ast.node(self.ast, iref);
if in != null {
Diag.err(self.diag, in.span, .NoLvalue,
"cannot bind by reference: initializer is not an lvalue");
} else {
Diag.err(self.diag, n.span, .NoLvalue,
"cannot bind by reference: initializer is not an lvalue");
}
ret 0;
}
emit_pattern_bind(self, pat_ref, place, var_ir, true);
ret place;
}
var slot = IrBuilder.mem_alloca(self.ir, var_ir, const_usize(self, 1));
if iref != 0 {
var v = emit_expr(self, iref);
if v != 0 { IrBuilder.mem_store(self.ir, var_ir, slot, v); }
}
emit_pattern_bind(self, pat_ref, slot, var_ir);
emit_pattern_bind(self, pat_ref, slot, var_ir, false);
ret slot;
}
@@ -1428,10 +1514,39 @@ fn emit_match(self: *Ast2Ir, n: *Node, matched_ref: usize, arms: Vec): usize {
var matched_lang = node_lang(matched_node);
var matched_ir = to_ir_type(self, matched_lang);
if matched_ir == 0 { ret 0; }
var scrut_slot = IrBuilder.mem_alloca(self.ir, matched_ir, const_usize(self, 1));
var scrut_val = emit_expr(self, matched_ref);
if scrut_val == 0 { ret 0; }
ignore_usize(IrBuilder.mem_store(self.ir, matched_ir, scrut_slot, scrut_val));
var matched_place_mode = false;
var scan: usize = 0;
while scan < arms.len && !matched_place_mode {
var sa = Ast.node(self.ast, list_at(arms, scan));
if sa != null {
var spat: usize = 0;
match sa.body { .MatchArm(ma) => { spat = ma.pattern; }, _ => {} }
if pattern_has_ref(self, spat) { matched_place_mode = true; }
}
scan = scan + 1;
}
var scrut_slot: usize = 0;
if matched_place_mode {
scrut_slot = emit_lvalue(self, matched_ref);
if scrut_slot == 0 {
var mn = Ast.node(self.ast, matched_ref);
if mn != null {
Diag.err(self.diag, mn.span, .NoLvalue,
"cannot bind by reference: matched value is not an lvalue");
} else {
Diag.err(self.diag, n.span, .NoLvalue,
"cannot bind by reference: matched value is not an lvalue");
}
ret 0;
}
} else {
scrut_slot = IrBuilder.mem_alloca(self.ir, matched_ir, const_usize(self, 1));
var scrut_val = emit_expr(self, matched_ref);
if scrut_val == 0 { ret 0; }
ignore_usize(IrBuilder.mem_store(self.ir, matched_ir, scrut_slot, scrut_val));
}
var narms = arms.len;
var arm_branches = Vec.blank(@sizeof(usize), @alignof(usize));
@@ -1469,7 +1584,7 @@ fn emit_match(self: *Ast2Ir, n: *Node, matched_ref: usize, arms: Vec): usize {
var pat: usize = 0;
var body: usize = 0;
match arm.body { .MatchArm(ma) => { pat = ma.pattern; body = ma.body; }, _ => {} }
emit_pattern_bind(self, pat, scrut_slot, matched_ir);
emit_pattern_bind(self, pat, scrut_slot, matched_ir, matched_place_mode);
if body != 0 { ignore_usize(emit_expr(self, body)); }
}
var j: usize = 0;
+2 -1
View File
@@ -202,6 +202,7 @@ fn register_builtins(self: *Attr) {
registry_add(self, make_info("", "align", .Layout, .Type, .Required, 1, 1));
registry_add(self, make_info("", "volatile", .Ir, .Expr, .Required, 0, 0));
registry_add(self, make_info("", "atomic", .Ir, .Expr, .Required, 0, 1));
registry_add(self, make_info("", "export", .Ir, .Decl, .Optional, 1, 1));
}
// ---- dump(镜像 stage1 spl_attr_dump / spl_attr_dump_id)----
@@ -360,7 +361,7 @@ fn test_registry() {
}
defer { drop(&at); }
@assert(at.registry.len == 7, "1 sentinel + 6 builtins");
@assert(at.registry.len == 8, "1 sentinel + 7 builtins");
var i = lookup(&at, ByteView.empty(), ByteView.from_slice("extern"));
@assert(i != 0, "extern found");
-29
View File
@@ -1283,7 +1283,6 @@ fn run(self: *Ir2C) {
}
i = i + 1;
}
emit_ct_table(self);
var m = find_main(self);
if m != 0 {
var f = IrM.func(self.ir, m);
@@ -1311,34 +1310,6 @@ fn run(self: *Ir2C) {
}
}
// compiler function(`ct_` 前缀)回调表:供 native 端 vm_set_foreign 按名分发。
fn emit_ct_table(self: *Ir2C) {
put(self, "static const struct { const char *name; void *fn; } spl_ct_tab[] = {\n");
var n: usize = 0;
var i: usize = 1;
while i < self.ir.funcs.len {
var f = IrM.func(self.ir, i);
if f != null && !fn_is_extern(self, f) &&
ByteView.starts_with(f.name, ByteView.from_slice("ct_")) {
put(self, " {\"");
put_bv(self, f.name);
put(self, "\", (void*)f");
put_u(self, i);
put(self, "},\n");
n = n + 1;
}
i = i + 1;
}
if n == 0 { put(self, " {0, 0},\n"); }
put(self, "};\n");
put(self, "void *spl_ct_lookup(const char *name) {\n");
put(self, " unsigned long i;\n");
put(self, " for (i = 0; i < sizeof(spl_ct_tab)/sizeof(spl_ct_tab[0]); i++)\n");
put(self, " if (spl_ct_tab[i].name && strcmp(spl_ct_tab[i].name, name) == 0) return spl_ct_tab[i].fn;\n");
put(self, " return 0;\n");
put(self, "}\n");
}
fn new(ir: *Ir, ty: *TypeTable, layout: *Layout, attr: *Attr, out: *ByteBuffer): Ir2C {
ret Ir2C {
.ir = ir,
+2 -4
View File
@@ -638,6 +638,7 @@ fn cg_const(cg: *Cgen, ref: usize, n: *IrEntry) {
}
},
.Int(x) => { imm = const_usize(n); },
.Any => { imm = const_usize(n); },
.Float => {
var fv: f64 = 0.0;
match n.body {
@@ -915,10 +916,7 @@ fn cg_control(cg: *Cgen, ref: usize, n: *IrEntry) {
cg_load(cg, .Ptr);
cg_load_slot(cg, r.val, .Ptr);
cg_push(cg, .U64, sz);
var nat = Mcode.get_or_add_native(&ctx.prog, ByteView.from_slice("vm_memcpy"));
cg_push(cg, .U64, nat as usize);
emit(cg, .Ncall, .Void, 3);
emit(cg, .Drop, .Void, 0);
emit(cg, .Memcpy, .Void, 0);
}
emit(cg, .Ret, .Void, 0);
} else {
+3 -3
View File
@@ -122,11 +122,11 @@ fn fn_end(self: *IrBuilder) {
if fx.block < self.blocks.len { target = list_at(self.blocks, fx.block); }
if target != 0 {
if @enum2int(fx.slot) == @enum2int(FixKind.BrTrue) {
match e.body { .Br(b) => { b.true_label = target; e.body = .Br(b); }, _ => {} }
match e.body { .Br(&b) => { b.true_label = target; }, _ => {} }
} else if @enum2int(fx.slot) == @enum2int(FixKind.BrFalse) {
match e.body { .Br(b) => { b.false_label = target; e.body = .Br(b); }, _ => {} }
match e.body { .Br(&b) => { b.false_label = target; }, _ => {} }
} else {
match e.body { .Jmp(j) => { j.label = target; e.body = .Jmp(j); }, _ => {} }
match e.body { .Jmp(&j) => { j.label = target; }, _ => {} }
}
}
}
+7
View File
@@ -72,6 +72,7 @@ type Op = enum {
Gaddr,
Load,
Store,
Memcpy,
Ncall,
Nlib,
Bk,
@@ -177,6 +178,7 @@ fn op_name(op: Op): []u8 {
.Gaddr => { ret "gaddr"; },
.Load => { ret "load"; },
.Store => { ret "store"; },
.Memcpy => { ret "memcpy"; },
.Ncall => { ret "ncall"; },
.Nlib => { ret "nlib"; },
.Bk => { ret "breakpoint"; },
@@ -693,6 +695,11 @@ fn store(self: *Prog, path: ByteView): Status {
ret .Ok;
}
// 序列化到内存缓冲(供内嵌 VM 直接装载)。
fn serialize(self: *Prog, out: *ByteBuffer): Status {
ret encode(self, out);
}
fn ignore_ptr(p: *any) {}
fn ignore_usize(v: usize) {}
+66 -23
View File
@@ -460,9 +460,8 @@ fn parse_pattern_atom(self: *Parser): usize {
var sub = parse_pattern(self);
var pn = Ast.node(self.ast, ref);
match pn.body {
.PatternDot(pd) => {
.PatternDot(&pd) => {
pd.sub_pattern = sub;
pn.body = .PatternDot(pd);
},
_ => {},
}
@@ -508,6 +507,24 @@ fn parse_pattern_atom(self: *Parser): usize {
}
ret ref;
},
.And => {
// `&name`:解包左值引用绑定。仅允许作用于绑定名。
advance(self);
var nx = peek(self);
if nx == null {
parse_error(self, nx, "expected binding name after '&'");
ret 0;
}
match nx.kind {
.Ident => {},
_ => {
parse_error(self, nx, "expected binding name after '&'");
ret 0;
},
}
var inner = parse_pattern_atom(self);
ret new_node(self, .PatternRef(.{ .pat = inner }), t);
},
_ => {},
}
},
@@ -547,7 +564,7 @@ fn parse_pattern(self: *Parser): usize {
// ---- 声明 ----
fn parse_tag_list(self: *Parser, out: *Vec, is_external: bool, allow_at: bool) {
fn parse_tag_list(self: *Parser, out: *Vec, is_external: bool, allow_at: bool, bang_only: bool) {
while !self.failed {
var t = peek(self);
if t == null { ret; }
@@ -556,14 +573,21 @@ fn parse_tag_list(self: *Parser, out: *Vec, is_external: bool, allow_at: bool) {
else if kind_is_punct(t.kind, .Sharp) { is_at = false; }
else { ret; }
if !allow_at && is_at { ret; }
advance(self);
if is_external {
t = peek(self);
if t == null || !kind_is_punct(t.kind, .Not) {
parse_error(self, t, "expected '!' in external tag");
ret;
}
if bang_only {
// 仅消费 `#!`/`@!` 形式的当前层 tag;遇到普通 `#name` 即停。
var save = self.pos;
advance(self);
var nt = peek(self);
var has_bang = nt != null && kind_is_punct(nt.kind, .Not);
self.pos = save;
if !has_bang { ret; }
}
advance(self);
t = peek(self);
var ext_here = is_external;
if t != null && kind_is_punct(t.kind, .Not) {
advance(self);
ext_here = true;
}
t = peek(self);
if t == null { ret; }
@@ -572,14 +596,14 @@ fn parse_tag_list(self: *Parser, out: *Vec, is_external: bool, allow_at: bool) {
// stage1 先分配 tag 节点,再解析参数。
var body: Body = .TagItem(.{
.is_at = is_at,
.is_external = is_external,
.is_external = ext_here,
.name = t.text,
.args = ref_vec(),
});
if is_external {
if ext_here {
body = .ExternalTagItem(.{
.is_at = is_at,
.is_external = is_external,
.is_external = ext_here,
.name = t.text,
.args = ref_vec(),
});
@@ -608,7 +632,13 @@ fn parse_tag_list(self: *Parser, out: *Vec, is_external: bool, allow_at: bool) {
fn parse_param_decl_impl(self: *Parser): usize {
var attrs = ref_vec();
parse_tag_list(self, &attrs, false, true);
var is_comptime = false;
var c0 = peek(self);
if c0 != null && kind_is_keyword(c0.kind, .Comptime) {
advance(self);
is_comptime = true;
}
parse_tag_list(self, &attrs, false, true, false);
var name = peek(self);
if name == null { ret 0; }
var ok = false;
@@ -620,10 +650,10 @@ fn parse_param_decl_impl(self: *Parser): usize {
if !ok { ret 0; }
advance(self);
if !expect_punct(self, .Colon, "':' in parameter declaration") { ret 0; }
parse_tag_list(self, &attrs, false, false);
parse_tag_list(self, &attrs, false, false, false);
var ty = parse_type_expr(self);
ret new_node(self,
.ParamDecl(.{ .attr_list = attrs, .name = name.text, .type_expr = ty }),
.ParamDecl(.{ .attr_list = attrs, .is_comptime = is_comptime, .name = name.text, .type_expr = ty }),
name);
}
@@ -651,11 +681,24 @@ fn parse_param_list(self: *Parser): Vec {
if at_punct(self, .Comma) { advance(self); continue; }
break;
},
.Keyword(k) => {
if @enum2int(k) == @enum2int(Keyword.Comptime) {
var pd2 = parse_param_decl_impl(self);
if pd2 != 0 {
match Vec.push(&out, &pd2 as *u8) { .Ok => {}, .Err(e) => { ret out; } }
}
if at_punct(self, .Comma) { advance(self); continue; }
break;
}
parse_error(self, t, "unexpected token in parameter list");
break;
},
.Punct(p) => {
if @enum2int(p) == @enum2int(Punct.Ellipsis) {
advance(self);
var pd = new_node(self, .ParamDecl(.{
.attr_list = ref_vec(),
.is_comptime = false,
.name = ByteView.empty(),
.type_expr = 0,
}), t);
@@ -825,7 +868,7 @@ fn parse_member_decl(self: *Parser, tags: Vec): usize {
var t = peek(self);
if t != null && kind_is_punct(t.kind, .Colon) {
advance(self);
parse_tag_list(self, &tags, false, false);
parse_tag_list(self, &tags, false, false, false);
te = parse_type_expr(self);
t = peek(self);
if t != null && kind_is_punct(t.kind, .Assign) {
@@ -864,7 +907,7 @@ fn parse_comptime_decl(self: *Parser): usize {
fn parse_container_declaration(self: *Parser): usize {
var tags = ref_vec();
parse_tag_list(self, &tags, false, false);
parse_tag_list(self, &tags, false, false, false);
var t = peek(self);
if t == null { ret 0; }
match t.kind {
@@ -1282,9 +1325,9 @@ fn parse_prefix(self: *Parser): usize {
if ref == 0 { ret 0; }
var n = Ast.node(self.ast, ref);
match n.body {
.ExprWhile(w) => { w.label = label; n.body = .ExprWhile(w); },
.ExprLoop(l) => { l.label = label; n.body = .ExprLoop(l); },
.ExprFor(f) => { f.label = label; n.body = .ExprFor(f); },
.ExprWhile(&w) => { w.label = label; },
.ExprLoop(&l) => { l.label = label; },
.ExprFor(&f) => { f.label = label; },
_ => {},
}
ret ref;
@@ -2021,7 +2064,7 @@ fn parse_statement(self: *Parser): usize {
@enum2int(k) == @enum2int(Keyword.Const) ||
@enum2int(k) == @enum2int(Keyword.Type) {
var tags = ref_vec();
parse_tag_list(self, &tags, false, false);
parse_tag_list(self, &tags, false, false, false);
var decl: usize = 0;
if @enum2int(k) == @enum2int(Keyword.Var) {
decl = parse_var_const_decl(self, tags, false);
@@ -2188,7 +2231,7 @@ fn parse(self: *Parser) {
var t2 = peek(self);
self.pos = save;
if t2 != null && kind_is_punct(t2.kind, .Not) {
parse_tag_list(self, &external_tags, true, true);
parse_tag_list(self, &external_tags, true, true, true);
continue;
}
}
+9
View File
@@ -1268,6 +1268,15 @@ fn bind_pattern(self: *Sema, pat_ref: usize, matched_tid: usize) {
pat.resolved_lang_tid = matched_tid;
},
.PatternUnderscore => {},
.PatternRef(x) => {
if matched_tid == 0 {
diag(self, pat.span, .TypeMismatch, "ref pattern requires a concrete type");
ret;
}
var ptr = LangType.make_ptr(&self.types, matched_tid);
bind_pattern(self, x.pat, ptr);
pat.resolved_lang_tid = ptr;
},
.PatternList(x) => {
var i: usize = 0;
while i < x.items.len {
+8 -1
View File
@@ -8,6 +8,13 @@ type spl_vm_val_t = u64; // usize
#extern(vm) fn vm_load_from_buffer(vm: *any, buf: *any, len: usize): i32;
#extern(vm) fn vm_push(vm: *any, val: spl_vm_val_t): void;
// TODO
#extern(vm) fn vm_call(vm: *any, name: *u8, nargs: u64): i32;
#extern(vm) fn vm_run(vm: *any): i32;
#extern(vm) fn vm_ret_val(vm: *any): spl_vm_val_t;
#extern(vm) fn vm_mem_alloc(vm: *any, size: usize): spl_vm_val_t;
#extern(vm) fn vm_mem_read(vm: *any, off: spl_vm_val_t, dst: *any, n: usize): i32;
#extern(vm) fn vm_mem_write(vm: *any, off: spl_vm_val_t, src: *any, n: usize): i32;
#extern(vm) fn vm_stack_get(vm: *any, index: usize): spl_vm_val_t;
#extern(vm) fn vm_set_foreign(vm: *any, entry: []u8, ctx: u64): i32;
fn main() {}