stage2 提供近乎完整的功能但出现严重性能问题 添加stage0的原生性能记录 stage1添加更多内置函数 nob重构代码使其更加健壮
This commit is contained in:
1 parent
b5a7a811b3
commit
e02b0c633a
35 files changed
+14018
-216
No files matched your search
@@ -209,6 +209,7 @@ type Ast = @self;
|
||||
nodes: Vec, // Node
|
||||
root: usize,
|
||||
sources: *Sources, // 借用;可为 null(dump 时不解析行列)
|
||||
strings: Vec, // ByteBuffer:已解码的字符串字面量字节(ByteView 指向其中)
|
||||
|
||||
type AstResult = enum {
|
||||
Ok: Ast,
|
||||
@@ -231,6 +232,7 @@ fn new(sources: *Sources): AstResult {
|
||||
.nodes = Vec.blank(@sizeof(Node), @alignof(Node)),
|
||||
.root = 0,
|
||||
.sources = sources,
|
||||
.strings = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)),
|
||||
};
|
||||
var sentinel = new_node(.None, span_none());
|
||||
match Vec.push(&ast.nodes, &sentinel as *u8) {
|
||||
@@ -257,6 +259,15 @@ fn drop(self: *Ast) {
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.nodes);
|
||||
var k: usize = 0;
|
||||
while k < self.strings.len {
|
||||
match Vec.get(&self.strings, k) {
|
||||
.Ok(ptr) => { ByteBuffer.drop(ptr as *ByteBuffer); },
|
||||
.Err(error) => {},
|
||||
}
|
||||
k = k + 1;
|
||||
}
|
||||
Vec.drop(&self.strings);
|
||||
self.root = 0;
|
||||
}
|
||||
|
||||
@@ -281,6 +292,26 @@ fn alloc(self: *Ast, body: Body, span: Span): usize {
|
||||
ret self.nodes.len - 1;
|
||||
}
|
||||
|
||||
// 注册一段已解码的字符串字节(Ast 拥有;ByteView 指向其中,生命周期同 Ast)。
|
||||
// 空字符串也保证非空 data 指针(镜像 stage1:空字面量仍有稳定的非空字节地址)。
|
||||
fn push_string(self: *Ast, bytes: ByteBuffer): ByteView {
|
||||
var b = bytes;
|
||||
if b.storage.len == 0 {
|
||||
match ByteBuffer.reserve(&b, 1) { .Ok => {}, .Err(error) => {} }
|
||||
}
|
||||
@assert(b.storage.data != null, "ast string pool must be non-null");
|
||||
// 直接构造,避免 from_slice 把空串规范成 null 视图。
|
||||
var view = ByteView { .ptr = b.storage.data, .len = b.storage.len };
|
||||
match Vec.push(&self.strings, &b as *u8) {
|
||||
.Ok => { ret view; },
|
||||
.Err(error) => {
|
||||
ByteBuffer.drop(&b);
|
||||
ret ByteView.empty();
|
||||
},
|
||||
}
|
||||
ret ByteView.empty();
|
||||
}
|
||||
|
||||
fn count(self: *Ast): usize {
|
||||
ret self.nodes.len;
|
||||
}
|
||||
|
||||
+2142
File diff suppressed because it is too large.
Load diff
+426
@@ -0,0 +1,426 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 属性注册表与属性节点(移植自 stage1/spl_attr.c)。注册表是数据:内置属性
|
||||
// 在 register_builtins 里登记,查找按 (ns, name)。节点记录命中信息 + 实参。
|
||||
// 新增属性只加注册项,不改 sema 逻辑(对应 SPL.md 的 @/# 扩展点)。
|
||||
|
||||
type .{ .Error } = @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 AttrStage = enum {
|
||||
Sema,
|
||||
Layout,
|
||||
Ir,
|
||||
Opt,
|
||||
Backend,
|
||||
}
|
||||
|
||||
type AttrTarget = enum {
|
||||
Decl,
|
||||
Type,
|
||||
Expr,
|
||||
Stmt,
|
||||
}
|
||||
|
||||
type AttrDemand = enum {
|
||||
Optional,
|
||||
Required,
|
||||
}
|
||||
|
||||
type AttrInfo = struct {
|
||||
ns: ByteView,
|
||||
name: ByteView,
|
||||
stage: AttrStage,
|
||||
target: AttrTarget,
|
||||
demand: AttrDemand,
|
||||
min_args: i32,
|
||||
max_args: i32, // -1 = variadic
|
||||
}
|
||||
|
||||
type AttrArgKind = enum {
|
||||
Int,
|
||||
Str,
|
||||
Def,
|
||||
Type,
|
||||
}
|
||||
|
||||
type AttrArg = enum {
|
||||
Int: i64,
|
||||
Str: ByteView,
|
||||
Def: usize,
|
||||
Type: usize,
|
||||
}
|
||||
|
||||
type AttrNode = struct {
|
||||
info_index: usize, // 注册表下标;0 = 无
|
||||
args: Vec, // AttrArg
|
||||
consumed_mask: u32,
|
||||
}
|
||||
|
||||
type Attr = @self;
|
||||
|
||||
table: Vec, // AttrNode
|
||||
registry: Vec, // AttrInfo
|
||||
|
||||
type AttrResult = enum {
|
||||
Ok: Attr,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn empty_info(): AttrInfo {
|
||||
ret AttrInfo {
|
||||
.ns = ByteView.empty(),
|
||||
.name = ByteView.empty(),
|
||||
.stage = .Sema,
|
||||
.target = .Decl,
|
||||
.demand = .Optional,
|
||||
.min_args = 0,
|
||||
.max_args = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn new_node(): AttrNode {
|
||||
ret AttrNode {
|
||||
.info_index = 0,
|
||||
.args = Vec.blank(@sizeof(AttrArg), @alignof(AttrArg)),
|
||||
.consumed_mask = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn new(): AttrResult {
|
||||
var at = Attr {
|
||||
.table = Vec.blank(@sizeof(AttrNode), @alignof(AttrNode)),
|
||||
.registry = Vec.blank(@sizeof(AttrInfo), @alignof(AttrInfo)),
|
||||
};
|
||||
var sentinel = new_node();
|
||||
match Vec.push(&at.table, &sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&at.table);
|
||||
Vec.drop(&at.registry);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
var reg_sentinel = empty_info();
|
||||
match Vec.push(&at.registry, ®_sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&at.table);
|
||||
Vec.drop(&at.registry);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
register_builtins(&at);
|
||||
ret .Ok(at);
|
||||
}
|
||||
|
||||
fn drop(self: *Attr) {
|
||||
var i: usize = 1;
|
||||
while i < self.table.len {
|
||||
var a = node(self, i);
|
||||
if a != null {
|
||||
Vec.drop(&a.args);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.table);
|
||||
Vec.drop(&self.registry);
|
||||
}
|
||||
|
||||
fn node(self: *Attr, id: usize): *AttrNode {
|
||||
if id == 0 || id >= self.table.len { ret null; }
|
||||
match Vec.get(&self.table, id) {
|
||||
.Ok(ptr) => { ret ptr as *AttrNode; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn info_at(self: *Attr, index: usize): *AttrInfo {
|
||||
if index == 0 || index >= self.registry.len { ret null; }
|
||||
match Vec.get(&self.registry, index) {
|
||||
.Ok(ptr) => { ret ptr as *AttrInfo; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn node_info(self: *Attr, id: usize): *AttrInfo {
|
||||
var a = node(self, id);
|
||||
if a == null { ret null; }
|
||||
ret info_at(self, a.info_index);
|
||||
}
|
||||
|
||||
// 追加属性节点(args 所有权转移)。
|
||||
fn alloc(self: *Attr, info_index: usize, args: Vec): usize {
|
||||
var a = new_node();
|
||||
a.info_index = info_index;
|
||||
a.args = args;
|
||||
match Vec.push(&self.table, &a as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret self.table.len - 1;
|
||||
}
|
||||
|
||||
fn registry_add(self: *Attr, info: AttrInfo) {
|
||||
match Vec.push(&self.registry, &info as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
}
|
||||
|
||||
// 返回注册表下标(>=1)或 0。
|
||||
fn lookup(self: *Attr, ns: ByteView, name: ByteView): usize {
|
||||
var i: usize = 1;
|
||||
while i < self.registry.len {
|
||||
var info = info_at(self, i);
|
||||
if info != null && ByteView.eq(info.ns, ns) && ByteView.eq(info.name, name) {
|
||||
ret i;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn make_info(ns: []u8, name: []u8, stage: AttrStage, target: AttrTarget, demand: AttrDemand,
|
||||
min_args: i32, max_args: i32): AttrInfo {
|
||||
ret AttrInfo {
|
||||
.ns = ByteView.from_slice(ns),
|
||||
.name = ByteView.from_slice(name),
|
||||
.stage = stage,
|
||||
.target = target,
|
||||
.demand = demand,
|
||||
.min_args = min_args,
|
||||
.max_args = max_args,
|
||||
};
|
||||
}
|
||||
|
||||
fn register_builtins(self: *Attr) {
|
||||
registry_add(self, make_info("", "extern", .Ir, .Decl, .Required, 1, 1));
|
||||
registry_add(self, make_info("", "inline", .Opt, .Decl, .Optional, 0, 0));
|
||||
registry_add(self, make_info("", "noinline", .Opt, .Decl, .Optional, 0, 0));
|
||||
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));
|
||||
}
|
||||
|
||||
// ---- dump(镜像 stage1 spl_attr_dump / spl_attr_dump_id)----
|
||||
|
||||
fn push_out(buf: *ByteBuffer, bytes: []u8): bool {
|
||||
match ByteBuffer.append(buf, bytes) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_usize(buf: *ByteBuffer, value: usize): bool {
|
||||
match append_usize(buf, value, 10, false) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_hex(buf: *ByteBuffer, value: u32): bool {
|
||||
if !push_out(buf, "0x") { ret false; }
|
||||
match append_usize(buf, value as usize, 16, false) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn stage_name(s: AttrStage): []u8 {
|
||||
match s {
|
||||
.Sema => { ret "SEMA"; },
|
||||
.Layout => { ret "LAYOUT"; },
|
||||
.Ir => { ret "IR"; },
|
||||
.Opt => { ret "OPT"; },
|
||||
.Backend => { ret "BACKEND"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn target_name(t: AttrTarget): []u8 {
|
||||
match t {
|
||||
.Decl => { ret "DECL"; },
|
||||
.Type => { ret "TYPE"; },
|
||||
.Expr => { ret "EXPR"; },
|
||||
.Stmt => { ret "STMT"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn demand_name(d: AttrDemand): []u8 {
|
||||
match d {
|
||||
.Required => { ret "REQUIRED"; },
|
||||
.Optional => { ret "OPTIONAL"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn dump_id(self: *Attr, id: usize, out: *ByteBuffer): bool {
|
||||
if id == 0 {
|
||||
ret push_out(out, "?");
|
||||
}
|
||||
var a = node(self, id);
|
||||
if a == null { ret push_out(out, "?"); }
|
||||
var info = info_at(self, a.info_index);
|
||||
if info == null { ret push_out(out, "?"); }
|
||||
|
||||
if !push_out(out, ByteView.as_slice(info.name)) { ret false; }
|
||||
if a.args.len == 0 { ret true; }
|
||||
if !push_out(out, "(") { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < a.args.len {
|
||||
if i > 0 && !push_out(out, ", ") { ret false; }
|
||||
var arg = arg_at(&a.args, i);
|
||||
if arg == null { ret false; }
|
||||
match arg.* {
|
||||
.Int(v) => {
|
||||
match append_i64(out, v, 10, false) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
},
|
||||
.Str(v) => {
|
||||
if !push_out(out, "\"") { ret false; }
|
||||
if !push_out(out, ByteView.as_slice(v)) { ret false; }
|
||||
if !push_out(out, "\"") { ret false; }
|
||||
},
|
||||
.Def(v) => {
|
||||
if !push_out(out, "def#") { ret false; }
|
||||
if !push_usize(out, v) { ret false; }
|
||||
},
|
||||
.Type(v) => {
|
||||
if !push_out(out, "type#") { ret false; }
|
||||
if !push_usize(out, v) { ret false; }
|
||||
},
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret push_out(out, ")");
|
||||
}
|
||||
|
||||
fn arg_at(v: *Vec, i: usize): *AttrArg {
|
||||
match Vec.get(v, i) {
|
||||
.Ok(ptr) => { ret ptr as *AttrArg; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn dump(self: *Attr, out: *ByteBuffer): bool {
|
||||
if !push_out(out, "AttrTable:\n") { ret false; }
|
||||
var i: usize = 1;
|
||||
while i < self.table.len {
|
||||
var a = node(self, i);
|
||||
if !push_out(out, " #") { ret false; }
|
||||
if !push_usize(out, i) { ret false; }
|
||||
if !push_out(out, " ") { ret false; }
|
||||
if !dump_id(self, i, out) { ret false; }
|
||||
if a != null {
|
||||
var info = info_at(self, a.info_index);
|
||||
if info != null {
|
||||
if !push_out(out, " [stage=") { ret false; }
|
||||
if !push_out(out, stage_name(info.stage)) { ret false; }
|
||||
if !push_out(out, " target=") { ret false; }
|
||||
if !push_out(out, target_name(info.target)) { ret false; }
|
||||
if !push_out(out, " demand=") { ret false; }
|
||||
if !push_out(out, demand_name(info.demand)) { ret false; }
|
||||
if !push_out(out, " consumed=") { ret false; }
|
||||
if !push_hex(out, a.consumed_mask) { ret false; }
|
||||
if !push_out(out, "]") { ret false; }
|
||||
}
|
||||
}
|
||||
if !push_out(out, "\n") { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn expect_text(buf: ByteBuffer, expected: []u8) {
|
||||
var got = ByteBuffer.as_slice(buf);
|
||||
@assert(got.len == expected.len, "dump length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(got.ptr[i] == expected.ptr[i], "dump byte");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_registry() {
|
||||
var at: Attr = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { at = v; },
|
||||
.Err(error) => { @assert(false, "attr new failed"); },
|
||||
}
|
||||
defer { drop(&at); }
|
||||
|
||||
@assert(at.registry.len == 7, "1 sentinel + 6 builtins");
|
||||
|
||||
var i = lookup(&at, ByteView.empty(), ByteView.from_slice("extern"));
|
||||
@assert(i != 0, "extern found");
|
||||
var info = info_at(&at, i);
|
||||
@assert(info != null, "extern info");
|
||||
@assert(info.min_args == 1 && info.max_args == 1, "extern arity");
|
||||
|
||||
var j = lookup(&at, ByteView.empty(), ByteView.from_slice("atomic"));
|
||||
@assert(j != 0, "atomic found");
|
||||
var jinfo = info_at(&at, j);
|
||||
@assert(jinfo.min_args == 0 && jinfo.max_args == 1, "atomic arity");
|
||||
|
||||
@assert(lookup(&at, ByteView.empty(), ByteView.from_slice("nope")) == 0, "unknown attr");
|
||||
}
|
||||
|
||||
fn test_alloc_and_dump_id() {
|
||||
var at: Attr = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { at = v; },
|
||||
.Err(error) => { @assert(false, "attr new failed"); },
|
||||
}
|
||||
defer { drop(&at); }
|
||||
|
||||
var idx = lookup(&at, ByteView.empty(), ByteView.from_slice("extern"));
|
||||
var args = Vec.blank(@sizeof(AttrArg), @alignof(AttrArg));
|
||||
var s: AttrArg = .Str(ByteView.from_slice("vm"));
|
||||
match Vec.push(&args, &s as *u8) { .Ok => {}, .Err(e) => { @assert(false, "push str"); } }
|
||||
var n: AttrArg = .Int(3);
|
||||
match Vec.push(&args, &n as *u8) { .Ok => {}, .Err(e) => { @assert(false, "push int"); } }
|
||||
var d: AttrArg = .Def(9);
|
||||
match Vec.push(&args, &d as *u8) { .Ok => {}, .Err(e) => { @assert(false, "push def"); } }
|
||||
|
||||
var id = alloc(&at, idx, args);
|
||||
@assert(id == 1, "first attr id");
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !dump_id(&at, id, &out) { @assert(false, "dump_id ok"); }
|
||||
expect_text(out, "extern(\"vm\", 3, def#9)");
|
||||
|
||||
var out2 = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out2); }
|
||||
if !dump_id(&at, 0, &out2) { @assert(false, "dump_id none ok"); }
|
||||
expect_text(out2, "?");
|
||||
|
||||
var out3 = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out3); }
|
||||
var l = lookup(&at, ByteView.empty(), ByteView.from_slice("inline"));
|
||||
var id2 = alloc(&at, l, Vec.blank(@sizeof(AttrArg), @alignof(AttrArg)));
|
||||
if !dump(&at, &out3) { @assert(false, "dump ok"); }
|
||||
// 只校验首尾关键片段,避免依赖完整表格排版
|
||||
var got = ByteBuffer.as_slice(out3);
|
||||
@assert(got.len > 0, "dump nonempty");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_registry();
|
||||
test_alloc_and_dump_id();
|
||||
}
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 声明句柄表(移植自 stage1/spl_lang_type.h 的 spl_def_node_t 部分)。
|
||||
// DefId 是「声明」的稳定身份:作用域符号、AST 节点 resolved_def_id、以及将来
|
||||
// comptime 生成的声明都用它。前向引用/环形引用由 state 状态机表达。
|
||||
//
|
||||
// 与 stage1 的差异:owner_ast 改为不透明 `*any`(借用),使本表不依赖具体 AST
|
||||
// 表示,便于将来 comptime/序列化复用;sema 侧自行 cast 回 *Ast。
|
||||
// 名称是 ByteView(借用源/AST),本表不做拥有。
|
||||
|
||||
type .{ .Error } = @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 } = @import("utils/fmt.spl");
|
||||
type .{ .Span, .span_none } = @import("./diag.spl");
|
||||
|
||||
type DefKind = enum {
|
||||
Error,
|
||||
Scalar,
|
||||
Member,
|
||||
Var,
|
||||
Fn,
|
||||
Agg,
|
||||
Distinct,
|
||||
Alias,
|
||||
Builtin,
|
||||
}
|
||||
|
||||
type DeclState = enum {
|
||||
Unresolved,
|
||||
Resolving,
|
||||
Resolved,
|
||||
Error,
|
||||
}
|
||||
|
||||
type DefNode = struct {
|
||||
kind: DefKind,
|
||||
name: ByteView,
|
||||
span: Span,
|
||||
lang_id: usize,
|
||||
owner: *any, // 借用;不透明,sema 侧 cast 回 *Ast
|
||||
ast_node_ref: usize,
|
||||
state: DeclState,
|
||||
owner_def: usize,
|
||||
}
|
||||
|
||||
type DefTable = @self;
|
||||
|
||||
table: Vec, // DefNode
|
||||
|
||||
type DefTableResult = enum {
|
||||
Ok: DefTable,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn new_node(): DefNode {
|
||||
ret DefNode {
|
||||
.kind = .Error,
|
||||
.name = ByteView.empty(),
|
||||
.span = span_none(),
|
||||
.lang_id = 0,
|
||||
.owner = null,
|
||||
.ast_node_ref = 0,
|
||||
.state = .Unresolved,
|
||||
.owner_def = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn new(): DefTableResult {
|
||||
var dt = DefTable { .table = Vec.blank(@sizeof(DefNode), @alignof(DefNode)) };
|
||||
var sentinel = new_node();
|
||||
match Vec.push(&dt.table, &sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&dt.table);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
ret .Ok(dt);
|
||||
}
|
||||
|
||||
fn drop(self: *DefTable) {
|
||||
Vec.drop(&self.table);
|
||||
}
|
||||
|
||||
// id 0 与越界均返回 null(对应 stage1 spl_def_node)。
|
||||
fn node(self: *DefTable, id: usize): *DefNode {
|
||||
if id == 0 || id >= self.table.len { ret null; }
|
||||
match Vec.get(&self.table, id) {
|
||||
.Ok(ptr) => { ret ptr as *DefNode; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn count(self: *DefTable): usize {
|
||||
ret self.table.len;
|
||||
}
|
||||
|
||||
// 追加一条声明,返回其 DefId(失败返回 0)。
|
||||
fn alloc(self: *DefTable, kind: DefKind, name: ByteView, span: Span, owner: *any,
|
||||
ast_node_ref: usize): usize {
|
||||
var n = new_node();
|
||||
n.kind = kind;
|
||||
n.name = name;
|
||||
n.span = span;
|
||||
n.owner = owner;
|
||||
n.ast_node_ref = ast_node_ref;
|
||||
match Vec.push(&self.table, &n as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret self.table.len - 1;
|
||||
}
|
||||
|
||||
fn resolve(self: *DefTable, id: usize, lang_id: usize) {
|
||||
var d = node(self, id);
|
||||
if d == null { ret; }
|
||||
d.lang_id = lang_id;
|
||||
d.state = .Resolved;
|
||||
}
|
||||
|
||||
fn set_state(self: *DefTable, id: usize, state: DeclState) {
|
||||
var d = node(self, id);
|
||||
if d == null { ret; }
|
||||
d.state = state;
|
||||
}
|
||||
|
||||
fn get_lang_id(self: *DefTable, id: usize): usize {
|
||||
var d = node(self, id);
|
||||
if d == null { ret 0; }
|
||||
ret d.lang_id;
|
||||
}
|
||||
|
||||
// ---- 分类 ----
|
||||
|
||||
fn kind_name(kind: DefKind): []u8 {
|
||||
match kind {
|
||||
.Error => { ret "error"; },
|
||||
.Scalar => { ret "scalar"; },
|
||||
.Member => { ret "member"; },
|
||||
.Var => { ret "var"; },
|
||||
.Fn => { ret "fn"; },
|
||||
.Agg => { ret "agg"; },
|
||||
.Distinct => { ret "distinct"; },
|
||||
.Alias => { ret "alias"; },
|
||||
.Builtin => { ret "builtin"; },
|
||||
}
|
||||
ret "unknown";
|
||||
}
|
||||
|
||||
fn state_name(state: DeclState): []u8 {
|
||||
match state {
|
||||
.Unresolved => { ret "unresolved"; },
|
||||
.Resolving => { ret "resolving"; },
|
||||
.Resolved => { ret "resolved"; },
|
||||
.Error => { ret "error"; },
|
||||
}
|
||||
ret "unknown";
|
||||
}
|
||||
|
||||
// 能作为类型使用的 def(对应 stage1 sema_def_is_type)。
|
||||
fn is_type_kind(kind: DefKind): bool {
|
||||
match kind {
|
||||
.Scalar => { ret true; },
|
||||
.Agg => { ret true; },
|
||||
.Distinct => { ret true; },
|
||||
.Alias => { ret true; },
|
||||
.Builtin => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// ---- dump(镜像 stage1 spl_type_def_dump 的文本)----
|
||||
|
||||
fn push_out(buf: *ByteBuffer, bytes: []u8): bool {
|
||||
match ByteBuffer.append(buf, bytes) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_usize(buf: *ByteBuffer, value: usize): bool {
|
||||
match append_usize(buf, value, 10, false) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn dump(self: *DefTable, id: usize, out: *ByteBuffer): bool {
|
||||
var d = node(self, id);
|
||||
if d == null {
|
||||
ret push_out(out, "none");
|
||||
}
|
||||
if !push_out(out, "kind=") { ret false; }
|
||||
if !push_out(out, kind_name(d.kind)) { ret false; }
|
||||
if !push_out(out, " lang_id=") { ret false; }
|
||||
if !push_usize(out, d.lang_id) { ret false; }
|
||||
if !push_out(out, " name=") { ret false; }
|
||||
if d.name.len == 0 {
|
||||
ret push_out(out, "?");
|
||||
}
|
||||
ret push_out(out, ByteView.as_slice(d.name));
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_alloc_and_state() {
|
||||
var dt: DefTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { dt = v; },
|
||||
.Err(error) => { @assert(false, "def table new failed"); },
|
||||
}
|
||||
defer { drop(&dt); }
|
||||
|
||||
@assert(count(&dt) == 1, "sentinel only");
|
||||
@assert(node(&dt, 0) == null, "id 0 is null");
|
||||
|
||||
var span = span_none();
|
||||
var a = alloc(&dt, .Fn, ByteView.from_slice("foo"), span, null, 7);
|
||||
var b = alloc(&dt, .Agg, ByteView.from_slice("Bar"), span, null, 9);
|
||||
@assert(a == 1 && b == 2, "ids increase from 1");
|
||||
|
||||
var na = node(&dt, a);
|
||||
@assert(na != null, "node a");
|
||||
@assert(ByteView.eq(na.name, ByteView.from_slice("foo")), "name stored");
|
||||
@assert(na.ast_node_ref == 7, "ast_node_ref stored");
|
||||
@assert(na.owner == null, "owner stored");
|
||||
match na.state {
|
||||
.Unresolved => {},
|
||||
_ => { @assert(false, "initial state unresolved"); },
|
||||
}
|
||||
match na.kind {
|
||||
.Fn => {},
|
||||
_ => { @assert(false, "kind fn"); },
|
||||
}
|
||||
|
||||
resolve(&dt, a, 42);
|
||||
@assert(get_lang_id(&dt, a) == 42, "lang id resolved");
|
||||
var na2 = node(&dt, a);
|
||||
match na2.state {
|
||||
.Resolved => {},
|
||||
_ => { @assert(false, "state resolved"); },
|
||||
}
|
||||
|
||||
set_state(&dt, b, .Resolving);
|
||||
var nb = node(&dt, b);
|
||||
match nb.state {
|
||||
.Resolving => {},
|
||||
_ => { @assert(false, "state resolving"); },
|
||||
}
|
||||
|
||||
@assert(get_lang_id(&dt, 999) == 0, "missing id");
|
||||
}
|
||||
|
||||
fn test_kind_helpers() {
|
||||
@assert(is_type_kind(.Scalar), "scalar is type");
|
||||
@assert(is_type_kind(.Agg), "agg is type");
|
||||
@assert(is_type_kind(.Alias), "alias is type");
|
||||
@assert(!is_type_kind(.Fn), "fn not type");
|
||||
@assert(!is_type_kind(.Var), "var not type");
|
||||
@assert(!is_type_kind(.Member), "member not type");
|
||||
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(.Agg)), ByteView.from_slice("agg")), "agg name");
|
||||
@assert(ByteView.eq(ByteView.from_slice(state_name(.Resolving)),
|
||||
ByteView.from_slice("resolving")),
|
||||
"resolving name");
|
||||
}
|
||||
|
||||
fn test_dump() {
|
||||
var dt: DefTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { dt = v; },
|
||||
.Err(error) => { @assert(false, "def table new failed"); },
|
||||
}
|
||||
defer { drop(&dt); }
|
||||
|
||||
var a = alloc(&dt, .Var, ByteView.from_slice("x"), span_none(), null, 0);
|
||||
resolve(&dt, a, 5);
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !dump(&dt, a, &out) { @assert(false, "dump ok"); }
|
||||
expect_text(out, "kind=var lang_id=5 name=x");
|
||||
|
||||
var out2 = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out2); }
|
||||
if !dump(&dt, 0, &out2) { @assert(false, "dump none ok"); }
|
||||
expect_text(out2, "none");
|
||||
|
||||
var out3 = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out3); }
|
||||
var c = alloc(&dt, .Alias, ByteView.empty(), span_none(), null, 0);
|
||||
if !dump(&dt, c, &out3) { @assert(false, "dump empty name ok"); }
|
||||
expect_text(out3, "kind=alias lang_id=0 name=?");
|
||||
}
|
||||
|
||||
fn expect_text(buf: ByteBuffer, expected: []u8) {
|
||||
var got = ByteBuffer.as_slice(buf);
|
||||
@assert(got.len == expected.len, "dump length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(got.ptr[i] == expected.ptr[i], "dump byte");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_alloc_and_state();
|
||||
test_kind_helpers();
|
||||
test_dump();
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// comptime / const 求值的**接口 seam**(本轮只占位,不实现求值)。
|
||||
//
|
||||
// 目标:把「编译期求值」从 sema 主体里隔离出来,将来接一个解释器(可直接复用
|
||||
// VM 或 AST 求值器)时不动 sema 结构。sema 只在 comptime 语句 / const 初始化 /
|
||||
// @import 等点调用这里;未实现时统一返回 NotConst(= 放弃折叠,不报错)。
|
||||
//
|
||||
// 分块编译相关:eval 未来的产物是「编译期生成的声明」,统一经 DeclSink 登记,
|
||||
// 由驱动在固定点循环里插入并触发重跑;eval 自己不直接改 sema 表,避免重入。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type Ast = @import("./ast.spl");
|
||||
type Diag = @import("./diag.spl");
|
||||
|
||||
// 编译期值:占位。未来扩展为 BigInt / 聚合 / 类型句柄等。
|
||||
type EvalValue = enum {
|
||||
Int: usize, // TODO: BigInt
|
||||
Bool: bool,
|
||||
}
|
||||
|
||||
type EvalOutcome = enum {
|
||||
Value: EvalValue,
|
||||
NotConst, // 不是编译期常量(正常放弃,不产生诊断)
|
||||
Err: Error, // 求值出错(诊断由 eval 侧发出)
|
||||
}
|
||||
|
||||
// comptime 生成声明的出口:eval 只登记,插入/重跑由驱动负责。
|
||||
type DeclSink = struct {
|
||||
items: Vec, // usize:待插入的 AST 节点引用
|
||||
}
|
||||
|
||||
type DeclSinkResult = enum {
|
||||
Ok: DeclSink,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
// 求值上下文(借用 sink 与 diag;本轮不持有所有权)。
|
||||
type Eval = @self;
|
||||
|
||||
sink: *DeclSink, // 借用,可为 null
|
||||
diag: *Diag, // 借用,可为 null
|
||||
enabled: bool, // false = 完全不求值(等价旧行为)
|
||||
|
||||
// ---- DeclSink ----
|
||||
|
||||
fn sink_new(): DeclSinkResult {
|
||||
var s = DeclSink { .items = Vec.blank(@sizeof(usize), @alignof(usize)) };
|
||||
ret .Ok(s);
|
||||
}
|
||||
|
||||
fn sink_drop(self: *DeclSink) {
|
||||
Vec.drop(&self.items);
|
||||
}
|
||||
|
||||
fn sink_push(self: *DeclSink, node_ref: usize): bool {
|
||||
match Vec.push(&self.items, &node_ref as *u8) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn sink_len(self: *DeclSink): usize {
|
||||
ret self.items.len;
|
||||
}
|
||||
|
||||
// ---- Eval ----
|
||||
|
||||
fn new(sink: *DeclSink, diag: *Diag, enabled: bool): Eval {
|
||||
ret Eval {
|
||||
.sink = sink,
|
||||
.diag = diag,
|
||||
.enabled = enabled,
|
||||
};
|
||||
}
|
||||
|
||||
fn is_enabled(self: *Eval): bool {
|
||||
ret self.enabled;
|
||||
}
|
||||
|
||||
// 求值一个表达式。本轮一律 NotConst(可由调用方作为「放弃折叠」处理)。
|
||||
fn eval_expr(self: *Eval, ast: *Ast, ref: usize): EvalOutcome {
|
||||
if !self.enabled { ret .NotConst; }
|
||||
ret .NotConst;
|
||||
}
|
||||
|
||||
// 执行一个 comptime 块(可能产生声明,登记到 sink)。本轮不做。
|
||||
fn run_block(self: *Eval, ast: *Ast, ref: usize): EvalOutcome {
|
||||
if !self.enabled { ret .NotConst; }
|
||||
ret .NotConst;
|
||||
}
|
||||
|
||||
// 顶层 const 初始化求值。本轮不做。
|
||||
fn eval_const_init(self: *Eval, ast: *Ast, ref: usize): EvalOutcome {
|
||||
if !self.enabled { ret .NotConst; }
|
||||
ret .NotConst;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_sink() {
|
||||
var sink: DeclSink = undefined;
|
||||
match sink_new() {
|
||||
.Ok(v) => { sink = v; },
|
||||
.Err(error) => { @assert(false, "sink new failed"); },
|
||||
}
|
||||
defer { sink_drop(&sink); }
|
||||
|
||||
@assert(sink_len(&sink) == 0, "empty sink");
|
||||
@assert(sink_push(&sink, 42), "push");
|
||||
@assert(sink_len(&sink) == 1, "one item");
|
||||
}
|
||||
|
||||
fn test_stub_outcome() {
|
||||
var sink: DeclSink = undefined;
|
||||
match sink_new() {
|
||||
.Ok(v) => { sink = v; },
|
||||
.Err(error) => { @assert(false, "sink new failed"); },
|
||||
}
|
||||
defer { sink_drop(&sink); }
|
||||
|
||||
var ev = new(&sink, null, true);
|
||||
@assert(is_enabled(&ev), "enabled");
|
||||
var out = eval_expr(&ev, null, 0);
|
||||
match out {
|
||||
.NotConst => {},
|
||||
_ => { @assert(false, "stub returns NotConst"); },
|
||||
}
|
||||
|
||||
var ev2 = new(null, null, false);
|
||||
@assert(!is_enabled(&ev2), "disabled");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_sink();
|
||||
test_stub_outcome();
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 内建(intrinsic)注册表(移植自 stage1/spl_intrin.c)。表是数据:按 kind 查
|
||||
// 描述信息,按名字/字段/`as` 转换规则反查 kind。新增内建只改 info_at,
|
||||
// 不改 sema 逻辑(对应 SPL.md 的 @/# 扩展点)。
|
||||
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type TypeTable = @import("./type_table.spl");
|
||||
type .{ .TypeNode, .TypeEntry } = @import("./type_table.spl");
|
||||
type .{ .LangKind } = @import("./lang_type.spl");
|
||||
|
||||
// 文件命名空间(供跨模块用 IntrinOps.foo 调用)。
|
||||
type IntrinOps = @self;
|
||||
|
||||
type Intrin = enum {
|
||||
None,
|
||||
BitSizeof,
|
||||
Sizeof,
|
||||
Alignof,
|
||||
Assert,
|
||||
Breakpoint,
|
||||
Len,
|
||||
IntCast,
|
||||
Truncate,
|
||||
BitCast,
|
||||
FloatCast,
|
||||
Float2Int,
|
||||
Int2Float,
|
||||
Bool2Int,
|
||||
Ptr2Int,
|
||||
Int2Ptr,
|
||||
PtrCast,
|
||||
Enum2Int,
|
||||
OffsetOf,
|
||||
FieldCount,
|
||||
}
|
||||
|
||||
type IntrinForm = enum {
|
||||
Call,
|
||||
Field,
|
||||
}
|
||||
|
||||
type IntrinResult = enum {
|
||||
Unit,
|
||||
Bool,
|
||||
Usize,
|
||||
}
|
||||
|
||||
type IntrinInfo = struct {
|
||||
name: ByteView,
|
||||
kind: Intrin,
|
||||
form: IntrinForm,
|
||||
min_args: i32,
|
||||
max_args: i32, // -1 = variadic
|
||||
result: IntrinResult,
|
||||
}
|
||||
|
||||
fn form_is(a: IntrinForm, b: IntrinForm): bool {
|
||||
ret @enum2int(a) == @enum2int(b);
|
||||
}
|
||||
|
||||
fn kind_is(a: LangKind, b: LangKind): bool {
|
||||
ret @enum2int(a) == @enum2int(b);
|
||||
}
|
||||
|
||||
// 与 stage1 表逐项对应(枚举顺序即 kind 数值)。
|
||||
fn info_at(index: usize): IntrinInfo {
|
||||
match index {
|
||||
1 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@bitsizeof"), .kind = .BitSizeof,
|
||||
.form = .Call, .min_args = 1, .max_args = 1, .result = .Usize };
|
||||
},
|
||||
2 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@sizeof"), .kind = .Sizeof,
|
||||
.form = .Call, .min_args = 1, .max_args = 1, .result = .Usize };
|
||||
},
|
||||
3 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@alignof"), .kind = .Alignof,
|
||||
.form = .Call, .min_args = 1, .max_args = 1, .result = .Usize };
|
||||
},
|
||||
4 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@assert"), .kind = .Assert,
|
||||
.form = .Call, .min_args = 1, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
5 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@breakpoint"), .kind = .Breakpoint,
|
||||
.form = .Call, .min_args = 0, .max_args = 0, .result = .Unit };
|
||||
},
|
||||
6 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("len"), .kind = .Len,
|
||||
.form = .Field, .min_args = 0, .max_args = 0, .result = .Usize };
|
||||
},
|
||||
7 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@int_cast"), .kind = .IntCast,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
8 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@truncate"), .kind = .Truncate,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
9 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@bit_cast"), .kind = .BitCast,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
10 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@float_cast"), .kind = .FloatCast,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
11 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@float2int"), .kind = .Float2Int,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
12 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@int2float"), .kind = .Int2Float,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
13 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@bool2int"), .kind = .Bool2Int,
|
||||
.form = .Call, .min_args = 1, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
14 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@ptr2int"), .kind = .Ptr2Int,
|
||||
.form = .Call, .min_args = 1, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
15 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@int2ptr"), .kind = .Int2Ptr,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
16 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@ptr_cast"), .kind = .PtrCast,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
17 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@enum2int"), .kind = .Enum2Int,
|
||||
.form = .Call, .min_args = 1, .max_args = 1, .result = .Usize };
|
||||
},
|
||||
18 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@offsetof"), .kind = .OffsetOf,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Usize };
|
||||
},
|
||||
19 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@field_count"), .kind = .FieldCount,
|
||||
.form = .Call, .min_args = 1, .max_args = 1, .result = .Usize };
|
||||
},
|
||||
_ => {
|
||||
ret IntrinInfo { .name = ByteView.empty(), .kind = .None, .form = .Call,
|
||||
.min_args = 0, .max_args = 0, .result = .Unit };
|
||||
},
|
||||
}
|
||||
ret IntrinInfo { .name = ByteView.empty(), .kind = .None, .form = .Call,
|
||||
.min_args = 0, .max_args = 0, .result = .Unit };
|
||||
}
|
||||
|
||||
fn info(kind: Intrin): IntrinInfo {
|
||||
ret info_at(@enum2int(kind) as usize);
|
||||
}
|
||||
|
||||
fn table_count(): usize {
|
||||
ret 20;
|
||||
}
|
||||
|
||||
fn strip_at(name: ByteView): ByteView {
|
||||
if name.len > 0 && name.ptr[0] == '@' {
|
||||
match ByteView.slice(name, 1, name.len) {
|
||||
.Ok(rest) => { ret rest; },
|
||||
.Err(error) => { ret name; },
|
||||
}
|
||||
}
|
||||
ret name;
|
||||
}
|
||||
|
||||
// 按名查找 CALL 形态的内建;忽略前导 '@'。
|
||||
fn lookup_call(name: ByteView): Intrin {
|
||||
var want = strip_at(name);
|
||||
var i: usize = 1;
|
||||
while i < table_count() {
|
||||
var d = info_at(i);
|
||||
if form_is(d.form, .Call) {
|
||||
var dn = strip_at(d.name);
|
||||
if ByteView.eq(dn, want) { ret d.kind; }
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret .None;
|
||||
}
|
||||
|
||||
// 按接收者类型 + 字段名查找 FIELD 形态内建。
|
||||
fn lookup_field(recv: LangKind, field: ByteView): Intrin {
|
||||
if ByteView.eq(field, ByteView.from_slice("len")) {
|
||||
if kind_is(recv, .Array) || kind_is(recv, .Slice) { ret .Len; }
|
||||
}
|
||||
ret .None;
|
||||
}
|
||||
|
||||
fn is_int(e: *TypeEntry): bool {
|
||||
match e.node {
|
||||
.Int(x) => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn is_float(e: *TypeEntry): bool {
|
||||
match e.node {
|
||||
.Float(x) => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn is_bool(e: *TypeEntry): bool {
|
||||
match e.node {
|
||||
.Bool => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn is_ptr(e: *TypeEntry): bool {
|
||||
match e.node {
|
||||
.Ptr(x) => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn int_bits(e: *TypeEntry): usize {
|
||||
match e.node {
|
||||
.Int(x) => { ret x.bits; },
|
||||
_ => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// `as` 的展开规则表(改 as 语义只改这里)。
|
||||
fn lookup_cast(ir: *TypeTable, ft: usize, tt: usize): Intrin {
|
||||
var f = TypeTable.node(ir, ft);
|
||||
var t = TypeTable.node(ir, tt);
|
||||
if f == null || t == null { ret .None; }
|
||||
|
||||
if is_int(f) && is_int(t) {
|
||||
var fb = int_bits(f);
|
||||
var tb = int_bits(t);
|
||||
if fb == tb { ret .BitCast; }
|
||||
if fb > tb { ret .Truncate; }
|
||||
ret .IntCast;
|
||||
}
|
||||
if is_float(f) && is_float(t) { ret .FloatCast; }
|
||||
if is_bool(f) && is_int(t) { ret .Bool2Int; }
|
||||
if is_ptr(f) && is_int(t) { ret .Ptr2Int; }
|
||||
if is_int(f) && is_ptr(t) { ret .Int2Ptr; }
|
||||
if is_ptr(f) && is_ptr(t) { ret .PtrCast; }
|
||||
ret .None;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_info_table() {
|
||||
var d = info(.Sizeof);
|
||||
@assert(ByteView.eq(d.name, ByteView.from_slice("@sizeof")), "sizeof name");
|
||||
@assert(d.min_args == 1 && d.max_args == 1, "sizeof arity");
|
||||
@assert(form_is(d.form, .Call), "sizeof form");
|
||||
|
||||
var l = info(.Len);
|
||||
@assert(ByteView.eq(l.name, ByteView.from_slice("len")), "len name");
|
||||
@assert(form_is(l.form, .Field), "len form");
|
||||
}
|
||||
|
||||
fn test_lookup_call() {
|
||||
@assert(@enum2int(lookup_call(ByteView.from_slice("sizeof"))) == @enum2int(Intrin.Sizeof),
|
||||
"sizeof by bare name");
|
||||
@assert(@enum2int(lookup_call(ByteView.from_slice("@sizeof"))) == @enum2int(Intrin.Sizeof),
|
||||
"sizeof by @name");
|
||||
@assert(@enum2int(lookup_call(ByteView.from_slice("assert"))) == @enum2int(Intrin.Assert),
|
||||
"assert");
|
||||
@assert(@enum2int(lookup_call(ByteView.from_slice("len"))) == @enum2int(Intrin.None),
|
||||
"len is field, not call");
|
||||
@assert(@enum2int(lookup_call(ByteView.from_slice("nope"))) == @enum2int(Intrin.None),
|
||||
"unknown");
|
||||
}
|
||||
|
||||
fn test_lookup_field() {
|
||||
@assert(@enum2int(lookup_field(.Slice, ByteView.from_slice("len"))) ==
|
||||
@enum2int(Intrin.Len),
|
||||
"slice len");
|
||||
@assert(@enum2int(lookup_field(.Array, ByteView.from_slice("len"))) ==
|
||||
@enum2int(Intrin.Len),
|
||||
"array len");
|
||||
@assert(@enum2int(lookup_field(.Struct, ByteView.from_slice("len"))) ==
|
||||
@enum2int(Intrin.None),
|
||||
"struct len");
|
||||
@assert(@enum2int(lookup_field(.Slice, ByteView.from_slice("ptr"))) ==
|
||||
@enum2int(Intrin.None),
|
||||
"slice ptr");
|
||||
}
|
||||
|
||||
fn test_lookup_cast() {
|
||||
var ir: TypeTable = undefined;
|
||||
match TypeTable.new() {
|
||||
.Ok(v) => { ir = v; },
|
||||
.Err(error) => { @assert(false, "type table new failed"); },
|
||||
}
|
||||
defer { TypeTable.drop(&ir); }
|
||||
|
||||
var i32t = TypeTable.make_int(&ir, 32, true);
|
||||
var i64t = TypeTable.make_int(&ir, 64, true);
|
||||
var f64t = TypeTable.make_float(&ir, 64);
|
||||
var boolt = TypeTable.make_bool(&ir);
|
||||
var pt = TypeTable.make_ptr(&ir, i32t);
|
||||
|
||||
@assert(@enum2int(lookup_cast(&ir, i64t, i32t)) == @enum2int(Intrin.Truncate),
|
||||
"truncate");
|
||||
@assert(@enum2int(lookup_cast(&ir, i32t, i64t)) == @enum2int(Intrin.IntCast), "int cast");
|
||||
@assert(@enum2int(lookup_cast(&ir, i32t, i32t)) == @enum2int(Intrin.BitCast), "bit cast");
|
||||
@assert(@enum2int(lookup_cast(&ir, f64t, f64t)) == @enum2int(Intrin.FloatCast),
|
||||
"float cast");
|
||||
@assert(@enum2int(lookup_cast(&ir, boolt, i32t)) == @enum2int(Intrin.Bool2Int),
|
||||
"bool2int");
|
||||
@assert(@enum2int(lookup_cast(&ir, pt, i32t)) == @enum2int(Intrin.Ptr2Int), "ptr2int");
|
||||
@assert(@enum2int(lookup_cast(&ir, i32t, pt)) == @enum2int(Intrin.Int2Ptr), "int2ptr");
|
||||
@assert(@enum2int(lookup_cast(&ir, pt, pt)) == @enum2int(Intrin.PtrCast), "ptr cast");
|
||||
@assert(@enum2int(lookup_cast(&ir, i32t, f64t)) == @enum2int(Intrin.None),
|
||||
"int->float forbidden");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_info_table();
|
||||
test_lookup_call();
|
||||
test_lookup_field();
|
||||
test_lookup_cast();
|
||||
}
|
||||
+605
@@ -0,0 +1,605 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// SIR 中间表示(移植自 stage1/spl_ir.c/.h)。节点用 arena 序号引用(0 = error),
|
||||
// 按函数分桶。节点公共字段(kind/dbg/attrs)在外层 entry,kind 专属载荷在 ADT
|
||||
// IrBody;kind 与载荷变体一一对应(多种 kind 可共享同一载荷形状)。
|
||||
//
|
||||
// builder 维护当前函数/基本块/fixup 表,负责发射节点与回填跳转。
|
||||
|
||||
type .{ .Error } = @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 .{ .Span, .span_none } = @import("./diag.spl");
|
||||
type TypeTable = @import("./type_table.spl");
|
||||
type Attr = @import("./attr.spl");
|
||||
|
||||
type IrKind = enum {
|
||||
ArithAdd,
|
||||
ArithSub,
|
||||
ArithMul,
|
||||
ArithDiv,
|
||||
ArithRem,
|
||||
ArithNeg,
|
||||
ArithAbs,
|
||||
ArithAnd,
|
||||
ArithOr,
|
||||
ArithXor,
|
||||
ArithShl,
|
||||
ArithShr,
|
||||
ArithNot,
|
||||
CmpEq,
|
||||
CmpNe,
|
||||
CmpLt,
|
||||
CmpLe,
|
||||
CmpGt,
|
||||
CmpGe,
|
||||
CastTrunc,
|
||||
CastZext,
|
||||
CastSext,
|
||||
CastFext,
|
||||
CastFtrunc,
|
||||
CastBitcast,
|
||||
CastPtr2int,
|
||||
CastInt2ptr,
|
||||
CastBool2int,
|
||||
CaseInt2float,
|
||||
CaseFloat2int,
|
||||
MemAlloca,
|
||||
MemGlobalAlloc,
|
||||
MemLoad,
|
||||
MemStore,
|
||||
MemOffset,
|
||||
MemFieldPtr,
|
||||
MemCopy,
|
||||
MemSet,
|
||||
TypeConst,
|
||||
TypeBitsizeof,
|
||||
TypeSizeof,
|
||||
TypeAlignof,
|
||||
TypeOffsetof,
|
||||
TypeFieldCount,
|
||||
AggConstruct,
|
||||
AggExtract,
|
||||
AggInsert,
|
||||
ControlSelect,
|
||||
ControlBr,
|
||||
ControlJmp,
|
||||
ControlCall,
|
||||
ControlParam,
|
||||
ControlRet,
|
||||
ControlUnreachable,
|
||||
ControlTrap,
|
||||
DbgBreakpoint,
|
||||
DbgPanic,
|
||||
}
|
||||
|
||||
type ConstLit = enum {
|
||||
Bool: bool,
|
||||
Ptr: usize,
|
||||
Int: usize,
|
||||
Float: f64,
|
||||
Char: u8,
|
||||
Fn: usize,
|
||||
}
|
||||
|
||||
type IrBody = enum {
|
||||
None,
|
||||
Arith: struct { tid: usize, left: usize, right: usize },
|
||||
Cmp: struct { tid: usize, a: usize, b: usize },
|
||||
Cast: struct { from_tid: usize, to_tid: usize, val: usize },
|
||||
MemAlloc: struct { tid: usize, count: usize },
|
||||
MemGlobalAlloc: struct { tid: usize, const_node: usize },
|
||||
MemLoad: struct { tid: usize, ptr: usize },
|
||||
MemStore: struct { tid: usize, ptr: usize, val: usize },
|
||||
MemOffset: struct { tid: usize, ptr: usize, offset: usize },
|
||||
MemFieldPtr: struct { tid: usize, agg: usize, field_idx: usize },
|
||||
MemCopy: struct { tid: usize, dst: usize, src: usize, size: usize },
|
||||
MemSet: struct { tid: usize, dst: usize, val: usize, size: usize },
|
||||
Const: struct { tid: usize, lit: ConstLit },
|
||||
TypeOp: struct { tid: usize, field_idx: usize },
|
||||
AggConstruct: struct { tid: usize, fields: Vec },
|
||||
AggExtract: struct { tid: usize, field_tid: usize, field_idx: usize, val: usize },
|
||||
AggInsert: struct { tid: usize, field_idx: usize, agg: usize, field: usize },
|
||||
Select: struct { tid: usize, cond: usize, true_val: usize, false_val: usize },
|
||||
Br: struct { cond: usize, true_label: usize, false_label: usize },
|
||||
Jmp: struct { label: usize },
|
||||
Call: struct { tid: usize, func: usize, params: Vec },
|
||||
Param: struct { tid: usize, idx: usize },
|
||||
Ret: struct { tid: usize, val: usize },
|
||||
Panic: struct { msg: ByteView },
|
||||
}
|
||||
|
||||
type IrEntry = struct {
|
||||
kind: IrKind,
|
||||
dbg: Span,
|
||||
attrs: Vec, // attr id
|
||||
body: IrBody,
|
||||
}
|
||||
|
||||
type IrFunc = struct {
|
||||
name: ByteView,
|
||||
attrs: Vec, // attr id
|
||||
fn_tid: usize,
|
||||
nodes: Vec, // IrEntry
|
||||
}
|
||||
|
||||
type Ir = @self;
|
||||
|
||||
funcs: Vec, // IrFunc
|
||||
|
||||
// ---- 生命周期 ----
|
||||
|
||||
fn new_entry(): IrEntry {
|
||||
ret IrEntry {
|
||||
.kind = .ArithAdd,
|
||||
.dbg = span_none(),
|
||||
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.body = .None,
|
||||
};
|
||||
}
|
||||
|
||||
fn new(): Ir {
|
||||
var ir = Ir { .funcs = Vec.blank(@sizeof(IrFunc), @alignof(IrFunc)) };
|
||||
var sentinel = IrFunc {
|
||||
.name = ByteView.empty(),
|
||||
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.fn_tid = 0,
|
||||
.nodes = Vec.blank(@sizeof(IrEntry), @alignof(IrEntry)),
|
||||
};
|
||||
var sn = new_entry();
|
||||
match Vec.push(&sentinel.nodes, &sn as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
match Vec.push(&ir.funcs, &sentinel as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
ret ir;
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn drop_body(body: IrBody) {
|
||||
match body {
|
||||
.AggConstruct(a) => { Vec.drop(&a.fields); },
|
||||
.Call(c) => { Vec.drop(&c.params); },
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn drop(self: *Ir) {
|
||||
var i: usize = 0;
|
||||
while i < self.funcs.len {
|
||||
match Vec.get(&self.funcs, i) {
|
||||
.Ok(ptr) => {
|
||||
var f = ptr as *IrFunc;
|
||||
var j: usize = 0;
|
||||
while j < f.nodes.len {
|
||||
match Vec.get(&f.nodes, j) {
|
||||
.Ok(np) => {
|
||||
var e = np as *IrEntry;
|
||||
Vec.drop(&e.attrs);
|
||||
drop_body(e.body);
|
||||
},
|
||||
.Err(error) => {},
|
||||
}
|
||||
j = j + 1;
|
||||
}
|
||||
Vec.drop(&f.attrs);
|
||||
Vec.drop(&f.nodes);
|
||||
},
|
||||
.Err(error) => {},
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.funcs);
|
||||
}
|
||||
|
||||
fn alloc_fn(self: *Ir): usize {
|
||||
var f = IrFunc {
|
||||
.name = ByteView.empty(),
|
||||
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.fn_tid = 0,
|
||||
.nodes = Vec.blank(@sizeof(IrEntry), @alignof(IrEntry)),
|
||||
};
|
||||
var sn = new_entry();
|
||||
match Vec.push(&f.nodes, &sn as *u8) { .Ok => {}, .Err(error) => { ret 0; } }
|
||||
match Vec.push(&self.funcs, &f as *u8) { .Ok => {}, .Err(error) => { ret 0; } }
|
||||
ret self.funcs.len - 1;
|
||||
}
|
||||
|
||||
fn alloc_node(self: *Ir, fn_id: usize): usize {
|
||||
if fn_id == 0 || fn_id >= self.funcs.len { ret 0; }
|
||||
match Vec.get(&self.funcs, fn_id) {
|
||||
.Ok(ptr) => {
|
||||
var f = ptr as *IrFunc;
|
||||
var n = new_entry();
|
||||
match Vec.push(&f.nodes, &n as *u8) { .Ok => {}, .Err(error) => { ret 0; } }
|
||||
ret f.nodes.len - 1;
|
||||
},
|
||||
.Err(error) => {},
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn func(self: *Ir, fn_id: usize): *IrFunc {
|
||||
if fn_id == 0 || fn_id >= self.funcs.len { ret null; }
|
||||
match Vec.get(&self.funcs, fn_id) {
|
||||
.Ok(ptr) => { ret ptr as *IrFunc; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn node(self: *Ir, fn_id: usize, node_id: usize): *IrEntry {
|
||||
var f = func(self, fn_id);
|
||||
if f == null { ret null; }
|
||||
if node_id == 0 || node_id >= f.nodes.len { ret null; }
|
||||
match Vec.get(&f.nodes, node_id) {
|
||||
.Ok(ptr) => { ret ptr as *IrEntry; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn kind_name(kind: IrKind): []u8 {
|
||||
match kind {
|
||||
.ArithAdd => { ret "arith.add"; },
|
||||
.ArithSub => { ret "arith.sub"; },
|
||||
.ArithMul => { ret "arith.mul"; },
|
||||
.ArithDiv => { ret "arith.div"; },
|
||||
.ArithRem => { ret "arith.rem"; },
|
||||
.ArithNeg => { ret "arith.neg"; },
|
||||
.ArithAbs => { ret "arith.abs"; },
|
||||
.ArithAnd => { ret "arith.and"; },
|
||||
.ArithOr => { ret "arith.or"; },
|
||||
.ArithXor => { ret "arith.xor"; },
|
||||
.ArithShl => { ret "arith.shl"; },
|
||||
.ArithShr => { ret "arith.shr"; },
|
||||
.ArithNot => { ret "arith.not"; },
|
||||
.CmpEq => { ret "cmp.eq"; },
|
||||
.CmpNe => { ret "cmp.ne"; },
|
||||
.CmpLt => { ret "cmp.lt"; },
|
||||
.CmpLe => { ret "cmp.le"; },
|
||||
.CmpGt => { ret "cmp.gt"; },
|
||||
.CmpGe => { ret "cmp.ge"; },
|
||||
.CastTrunc => { ret "cast.trunc"; },
|
||||
.CastZext => { ret "cast.zext"; },
|
||||
.CastSext => { ret "cast.sext"; },
|
||||
.CastFext => { ret "cast.fext"; },
|
||||
.CastFtrunc => { ret "cast.ftrunc"; },
|
||||
.CastBitcast => { ret "cast.bitcast"; },
|
||||
.CastPtr2int => { ret "cast.ptr2int"; },
|
||||
.CastInt2ptr => { ret "cast.int2ptr"; },
|
||||
.CastBool2int => { ret "cast.bool2int"; },
|
||||
.CaseInt2float => { ret "case.int2float"; },
|
||||
.CaseFloat2int => { ret "case.float2int"; },
|
||||
.MemAlloca => { ret "mem.alloca"; },
|
||||
.MemGlobalAlloc => { ret "mem.global_alloc"; },
|
||||
.MemLoad => { ret "mem.load"; },
|
||||
.MemStore => { ret "mem.store"; },
|
||||
.MemOffset => { ret "mem.offset"; },
|
||||
.MemFieldPtr => { ret "mem.field"; },
|
||||
.MemCopy => { ret "mem.copy"; },
|
||||
.MemSet => { ret "mem.set"; },
|
||||
.TypeConst => { ret "type.const"; },
|
||||
.TypeBitsizeof => { ret "type.bitsizeof"; },
|
||||
.TypeSizeof => { ret "type.sizeof"; },
|
||||
.TypeAlignof => { ret "type.alignof"; },
|
||||
.TypeOffsetof => { ret "type.offsetof"; },
|
||||
.TypeFieldCount => { ret "type.field_count"; },
|
||||
.AggConstruct => { ret "agg.construct"; },
|
||||
.AggExtract => { ret "agg.extract"; },
|
||||
.AggInsert => { ret "agg.insert"; },
|
||||
.ControlSelect => { ret "control.select"; },
|
||||
.ControlBr => { ret "control.br"; },
|
||||
.ControlJmp => { ret "control.jmp"; },
|
||||
.ControlCall => { ret "control.call"; },
|
||||
.ControlParam => { ret "control.param"; },
|
||||
.ControlRet => { ret "control.ret"; },
|
||||
.ControlUnreachable => { ret "control.unreachable"; },
|
||||
.ControlTrap => { ret "control.trap"; },
|
||||
.DbgBreakpoint => { ret "dbg.breakpoint"; },
|
||||
.DbgPanic => { ret "dbg.panic"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
// ---- dump ----
|
||||
|
||||
fn out_str(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;
|
||||
}
|
||||
|
||||
fn dump_type(out: *ByteBuffer, ty: *TypeTable, tid: usize): bool {
|
||||
if !out_str(out, "(") { ret false; }
|
||||
if !TypeTable.pure_dump(ty, tid, out) { ret false; }
|
||||
ret out_str(out, ")");
|
||||
}
|
||||
|
||||
fn dump_ref(out: *ByteBuffer, ref: usize): bool {
|
||||
if ref == 0 { ret out_str(out, " _"); }
|
||||
if !out_str(out, " %") { ret false; }
|
||||
ret out_u(out, ref);
|
||||
}
|
||||
|
||||
fn dump_attrs(out: *ByteBuffer, attr: *Attr, ids: Vec): bool {
|
||||
if ids.len == 0 { ret true; }
|
||||
if !out_str(out, " attrs=[") { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < ids.len {
|
||||
if i > 0 && !out_str(out, ", ") { ret false; }
|
||||
var id = list_at(ids, i);
|
||||
if !Attr.dump_id(attr, id, out) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret out_str(out, "]");
|
||||
}
|
||||
|
||||
fn list_at(v: Vec, i: usize): usize {
|
||||
match Vec.get(&v, i) { .Ok(p) => { ret (p as *usize).*; }, .Err(e) => { ret 0; } }
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn dump_body(out: *ByteBuffer, ty: *TypeTable, e: *IrEntry): bool {
|
||||
match e.body {
|
||||
.None => { ret true; },
|
||||
.Arith(a) => {
|
||||
if !dump_type(out, ty, a.tid) { ret false; }
|
||||
if !dump_ref(out, a.left) { ret false; }
|
||||
// 一元 arith(neg/abs/not)不打印 right(镜像 stage1)。
|
||||
if @enum2int(e.kind) == @enum2int(IrKind.ArithNeg) ||
|
||||
@enum2int(e.kind) == @enum2int(IrKind.ArithAbs) ||
|
||||
@enum2int(e.kind) == @enum2int(IrKind.ArithNot) {
|
||||
ret true;
|
||||
}
|
||||
ret dump_ref(out, a.right);
|
||||
},
|
||||
.Cmp(c) => {
|
||||
if !dump_type(out, ty, c.tid) { ret false; }
|
||||
if !dump_ref(out, c.a) { ret false; }
|
||||
ret dump_ref(out, c.b);
|
||||
},
|
||||
.Cast(c) => {
|
||||
if !out_str(out, " ") { ret false; }
|
||||
if !TypeTable.pure_dump(ty, c.from_tid, out) { ret false; }
|
||||
if !out_str(out, " -> ") { ret false; }
|
||||
if !TypeTable.pure_dump(ty, c.to_tid, out) { ret false; }
|
||||
ret dump_ref(out, c.val);
|
||||
},
|
||||
.MemAlloc(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
ret dump_ref(out, m.count);
|
||||
},
|
||||
.MemGlobalAlloc(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !out_str(out, " g#") { ret false; }
|
||||
ret out_u(out, m.const_node);
|
||||
},
|
||||
.MemLoad(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
ret dump_ref(out, m.ptr);
|
||||
},
|
||||
.MemStore(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !dump_ref(out, m.ptr) { ret false; }
|
||||
ret dump_ref(out, m.val);
|
||||
},
|
||||
.MemOffset(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !dump_ref(out, m.ptr) { ret false; }
|
||||
ret dump_ref(out, m.offset);
|
||||
},
|
||||
.MemFieldPtr(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !dump_ref(out, m.agg) { ret false; }
|
||||
if !out_str(out, " #") { ret false; }
|
||||
ret out_u(out, m.field_idx);
|
||||
},
|
||||
.MemCopy(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !dump_ref(out, m.dst) { ret false; }
|
||||
if !dump_ref(out, m.src) { ret false; }
|
||||
ret dump_ref(out, m.size);
|
||||
},
|
||||
.MemSet(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !dump_ref(out, m.dst) { ret false; }
|
||||
if !dump_ref(out, m.val) { ret false; }
|
||||
ret dump_ref(out, m.size);
|
||||
},
|
||||
.Const(c) => {
|
||||
if !dump_type(out, ty, c.tid) { ret false; }
|
||||
var te = TypeTable.node(ty, c.tid);
|
||||
var is_float = false;
|
||||
var is_fn = false;
|
||||
var is_u8 = false;
|
||||
var is_arr = false;
|
||||
if te != null {
|
||||
match te.node {
|
||||
.Float(x) => { is_float = true; },
|
||||
.Fn(x) => { is_fn = true; },
|
||||
.Array(x) => { is_arr = true; },
|
||||
.Int(x) => { if x.bits == 8 { is_u8 = true; } },
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
if is_float {
|
||||
ret out_str(out, " float=?");
|
||||
}
|
||||
if is_arr {
|
||||
ret out_str(out, " bytes");
|
||||
}
|
||||
if is_fn {
|
||||
if !out_str(out, " fn#") { ret false; }
|
||||
match c.lit { .Fn(v) => { ret out_u(out, v); }, _ => { ret out_str(out, "?"); } }
|
||||
}
|
||||
if is_u8 {
|
||||
var cv: usize = 0;
|
||||
match c.lit {
|
||||
.Char(v) => { cv = v as usize; },
|
||||
.Int(v) => { cv = v; },
|
||||
_ => {},
|
||||
}
|
||||
if !out_str(out, " char='") { ret false; }
|
||||
match ByteBuffer.append_byte(out, cv as u8) { .Ok => {}, .Err(error) => { ret false; } }
|
||||
ret out_str(out, "'");
|
||||
}
|
||||
// stage1 对其他常量统一按 int_lit 打印(union 别名)。
|
||||
var iv: usize = 0;
|
||||
match c.lit {
|
||||
.Int(v) => { iv = v; },
|
||||
.Bool(v) => { if v { iv = 1; } },
|
||||
.Ptr(v) => { iv = v; },
|
||||
.Char(v) => { iv = v as usize; },
|
||||
.Fn(v) => { iv = v; },
|
||||
.Float(v) => {},
|
||||
}
|
||||
if !out_str(out, " int=") { ret false; }
|
||||
ret out_u(out, iv);
|
||||
},
|
||||
.TypeOp(t) => {
|
||||
if !dump_type(out, ty, t.tid) { ret false; }
|
||||
if @enum2int(e.kind) == @enum2int(IrKind.TypeOffsetof) {
|
||||
ret dump_ref(out, t.field_idx);
|
||||
}
|
||||
ret true;
|
||||
},
|
||||
.AggConstruct(a) => {
|
||||
if !dump_type(out, ty, a.tid) { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < a.fields.len {
|
||||
if !dump_ref(out, list_at(a.fields, i)) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
},
|
||||
.AggExtract(a) => {
|
||||
if !dump_type(out, ty, a.tid) { ret false; }
|
||||
if !out_str(out, " #") { ret false; }
|
||||
if !out_u(out, a.field_idx) { ret false; }
|
||||
ret dump_ref(out, a.val);
|
||||
},
|
||||
.AggInsert(a) => {
|
||||
if !dump_type(out, ty, a.tid) { ret false; }
|
||||
if !out_str(out, " #") { ret false; }
|
||||
if !out_u(out, a.field_idx) { ret false; }
|
||||
if !dump_ref(out, a.agg) { ret false; }
|
||||
ret dump_ref(out, a.field);
|
||||
},
|
||||
.Select(s) => {
|
||||
if !dump_type(out, ty, s.tid) { ret false; }
|
||||
if !dump_ref(out, s.cond) { ret false; }
|
||||
if !dump_ref(out, s.true_val) { ret false; }
|
||||
ret dump_ref(out, s.false_val);
|
||||
},
|
||||
.Br(b) => {
|
||||
if !dump_ref(out, b.cond) { ret false; }
|
||||
if !dump_ref(out, b.true_label) { ret false; }
|
||||
ret dump_ref(out, b.false_label);
|
||||
},
|
||||
.Jmp(j) => { ret dump_ref(out, j.label); },
|
||||
.Call(c) => {
|
||||
if !dump_type(out, ty, c.tid) { ret false; }
|
||||
if !dump_ref(out, c.func) { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < c.params.len {
|
||||
if !dump_ref(out, list_at(c.params, i)) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
},
|
||||
.Param(p) => {
|
||||
if !dump_type(out, ty, p.tid) { ret false; }
|
||||
ret dump_ref(out, p.idx);
|
||||
},
|
||||
.Ret(r) => {
|
||||
if !dump_type(out, ty, r.tid) { ret false; }
|
||||
ret dump_ref(out, r.val);
|
||||
},
|
||||
.Panic(p) => {
|
||||
if !out_str(out, " \"") { ret false; }
|
||||
if !out_str(out, ByteView.as_slice(p.msg)) { ret false; }
|
||||
ret out_str(out, "\"");
|
||||
},
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn dump(self: *Ir, ty: *TypeTable, attr: *Attr, out: *ByteBuffer): bool {
|
||||
var i: usize = 1;
|
||||
while i < self.funcs.len {
|
||||
var f = func(self, i);
|
||||
if f == null { i = i + 1; continue; }
|
||||
if !out_str(out, "func ") { ret false; }
|
||||
if f.name.len == 0 {
|
||||
if !out_str(out, "?") { ret false; }
|
||||
} else {
|
||||
if !out_str(out, ByteView.as_slice(f.name)) { ret false; }
|
||||
}
|
||||
if !out_str(out, " %") { ret false; }
|
||||
if !out_u(out, i) { ret false; }
|
||||
if !out_str(out, " tid = ") { ret false; }
|
||||
if !TypeTable.pure_dump(ty, f.fn_tid, out) { ret false; }
|
||||
if !dump_attrs(out, attr, f.attrs) { ret false; }
|
||||
if !out_str(out, "\n") { ret false; }
|
||||
|
||||
var j: usize = 1;
|
||||
while j < f.nodes.len {
|
||||
var e = node(self, i, j);
|
||||
if e == null { j = j + 1; continue; }
|
||||
if !out_str(out, " #") { ret false; }
|
||||
if !out_u(out, j) { ret false; }
|
||||
if !out_str(out, " ") { ret false; }
|
||||
if !out_str(out, kind_name(e.kind)) { ret false; }
|
||||
if !dump_body(out, ty, e) { ret false; }
|
||||
if !dump_attrs(out, attr, e.attrs) { ret false; }
|
||||
if !out_str(out, "\n") { ret false; }
|
||||
j = j + 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_kinds() {
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(.ArithAdd)), ByteView.from_slice("arith.add")),
|
||||
"arith.add name");
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(.ControlCall)),
|
||||
ByteView.from_slice("control.call")),
|
||||
"control.call name");
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(.DbgPanic)), ByteView.from_slice("dbg.panic")),
|
||||
"dbg.panic name");
|
||||
}
|
||||
|
||||
fn test_alloc_and_node() {
|
||||
var ir = new();
|
||||
defer { drop(&ir); }
|
||||
|
||||
var fid = alloc_fn(&ir);
|
||||
@assert(fid == 1, "first fn is 1");
|
||||
var nid = alloc_node(&ir, fid);
|
||||
@assert(nid == 1, "first node is 1");
|
||||
var e = node(&ir, fid, nid);
|
||||
@assert(e != null, "node lookup");
|
||||
e.kind = .ArithAdd;
|
||||
e.body = .Arith(.{ .tid = 2, .left = 1, .right = 1 });
|
||||
var f = func(&ir, fid);
|
||||
@assert(f != null && f.nodes.len == 2, "fn nodes");
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
test_kinds();
|
||||
test_alloc_and_node();
|
||||
}
|
||||
+1368
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,497 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// SIR builder (IrBuilder). Split from ir.spl: stage1 ast2ir
|
||||
// infinitely recurses on same-file @self nesting; cross-file is fine.
|
||||
|
||||
type .{ .Error } = @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 .{ .Span, .span_none } = @import("./diag.spl");
|
||||
type TypeTable = @import("./type_table.spl");
|
||||
type Attr = @import("./attr.spl");
|
||||
type Ir = @import("./ir.spl");
|
||||
type .{ .IrKind, .ConstLit, .IrEntry, .IrFunc, .IrBody } = @import("./ir.spl");
|
||||
|
||||
fn list_at(v: Vec, i: usize): usize {
|
||||
match Vec.get(&v, i) { .Ok(p) => { ret (p as *usize).*; }, .Err(e) => { ret 0; } }
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// ---- builder ----
|
||||
|
||||
type FixKind = enum { BrTrue, BrFalse, Jmp }
|
||||
|
||||
type Fix = struct {
|
||||
node: usize,
|
||||
slot: FixKind,
|
||||
block: usize,
|
||||
}
|
||||
|
||||
type IrBuilder = @self;
|
||||
|
||||
ir: Ir,
|
||||
dbg: Span,
|
||||
current_fn: usize,
|
||||
current_block: usize,
|
||||
blocks: Vec, // usize
|
||||
fixups: Vec, // Fix
|
||||
terminated: bool,
|
||||
|
||||
fn builder_alloc(self: *IrBuilder, kind: IrKind): usize {
|
||||
var ref = Ir.alloc_node(&self.ir, self.current_fn);
|
||||
if ref == 0 { ret 0; }
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null {
|
||||
e.kind = kind;
|
||||
e.dbg = self.dbg;
|
||||
}
|
||||
if self.current_block >= self.blocks.len { ret ref; }
|
||||
match Vec.get(&self.blocks, self.current_block) {
|
||||
.Ok(p) => {
|
||||
var slot = p as *usize;
|
||||
if slot.* == 0 { slot.* = ref; }
|
||||
},
|
||||
.Err(error) => {},
|
||||
}
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn builder_new(): IrBuilder {
|
||||
ret IrBuilder {
|
||||
.ir = Ir.new(),
|
||||
.dbg = span_none(),
|
||||
.current_fn = 0,
|
||||
.current_block = 0,
|
||||
.blocks = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.fixups = Vec.blank(@sizeof(Fix), @alignof(Fix)),
|
||||
.terminated = false,
|
||||
};
|
||||
}
|
||||
|
||||
fn builder_drop(self: *IrBuilder) {
|
||||
Ir.drop(&self.ir);
|
||||
Vec.drop(&self.blocks);
|
||||
Vec.drop(&self.fixups);
|
||||
}
|
||||
|
||||
fn fn_begin(self: *IrBuilder, name: ByteView, fn_tid: usize): usize {
|
||||
if self.current_fn != 0 { fn_end(self); }
|
||||
var fid = Ir.alloc_fn(&self.ir);
|
||||
var f = Ir.func(&self.ir, fid);
|
||||
if f != null {
|
||||
f.name = name;
|
||||
f.fn_tid = fn_tid;
|
||||
}
|
||||
self.current_fn = fid;
|
||||
self.current_block = 0;
|
||||
self.blocks.len = 0;
|
||||
self.fixups.len = 0;
|
||||
self.terminated = false;
|
||||
ret fid;
|
||||
}
|
||||
|
||||
fn fn_reserve(self: *IrBuilder, name: ByteView, fn_tid: usize): usize {
|
||||
var fid = Ir.alloc_fn(&self.ir);
|
||||
var f = Ir.func(&self.ir, fid);
|
||||
if f != null {
|
||||
f.name = name;
|
||||
f.fn_tid = fn_tid;
|
||||
}
|
||||
ret fid;
|
||||
}
|
||||
|
||||
fn fn_begin_at(self: *IrBuilder, fid: usize) {
|
||||
if self.current_fn != 0 && self.current_fn != fid { fn_end(self); }
|
||||
self.current_fn = fid;
|
||||
self.current_block = 0;
|
||||
self.blocks.len = 0;
|
||||
self.fixups.len = 0;
|
||||
self.terminated = false;
|
||||
}
|
||||
|
||||
fn fn_end(self: *IrBuilder) {
|
||||
var k: usize = 0;
|
||||
while k < self.fixups.len {
|
||||
match Vec.get(&self.fixups, k) {
|
||||
.Ok(fp) => {
|
||||
var fx = fp as *Fix;
|
||||
var e = Ir.node(&self.ir, self.current_fn, fx.node);
|
||||
if e != null {
|
||||
var target: usize = 0;
|
||||
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); }, _ => {} }
|
||||
} else if @enum2int(fx.slot) == @enum2int(FixKind.BrFalse) {
|
||||
match e.body { .Br(b) => { b.false_label = target; e.body = .Br(b); }, _ => {} }
|
||||
} else {
|
||||
match e.body { .Jmp(j) => { j.label = target; e.body = .Jmp(j); }, _ => {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.Err(error) => {},
|
||||
}
|
||||
k = k + 1;
|
||||
}
|
||||
self.current_block = 0;
|
||||
self.blocks.len = 0;
|
||||
self.fixups.len = 0;
|
||||
self.terminated = false;
|
||||
}
|
||||
|
||||
fn block_begin(self: *IrBuilder): usize {
|
||||
var zero: usize = 0;
|
||||
match Vec.push(&self.blocks, &zero as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
var block = self.blocks.len - 1;
|
||||
self.current_block = block;
|
||||
self.terminated = false;
|
||||
ret block;
|
||||
}
|
||||
|
||||
fn block_end(self: *IrBuilder) {
|
||||
// terminated 与空块校验在 stage1 为 Panic;这里保持同样约束但不终止进程。
|
||||
}
|
||||
|
||||
// ---- arith / cmp / cast ----
|
||||
|
||||
fn set_arith(self: *IrBuilder, kind: IrKind, tid: usize, left: usize, right: usize): usize {
|
||||
var ref = builder_alloc(self, kind);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Arith(.{ .tid = tid, .left = left, .right = right }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn set_cmp(self: *IrBuilder, kind: IrKind, tid: usize, a: usize, b: usize): usize {
|
||||
var ref = builder_alloc(self, kind);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Cmp(.{ .tid = tid, .a = a, .b = b }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn set_cast(self: *IrBuilder, kind: IrKind, from_tid: usize, to_tid: usize, val: usize): usize {
|
||||
var ref = builder_alloc(self, kind);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Cast(.{ .from_tid = from_tid, .to_tid = to_tid, .val = val }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn arith_add(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithAdd, t, l, r); }
|
||||
fn arith_sub(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithSub, t, l, r); }
|
||||
fn arith_mul(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithMul, t, l, r); }
|
||||
fn arith_div(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithDiv, t, l, r); }
|
||||
fn arith_rem(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithRem, t, l, r); }
|
||||
fn arith_and(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithAnd, t, l, r); }
|
||||
fn arith_or(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithOr, t, l, r); }
|
||||
fn arith_xor(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithXor, t, l, r); }
|
||||
fn arith_shl(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithShl, t, l, r); }
|
||||
fn arith_shr(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithShr, t, l, r); }
|
||||
|
||||
fn arith_neg(self: *IrBuilder, t: usize, v: usize): usize { ret set_arith(self, .ArithNeg, t, v, 0); }
|
||||
fn arith_abs(self: *IrBuilder, t: usize, v: usize): usize { ret set_arith(self, .ArithAbs, t, v, 0); }
|
||||
fn arith_not(self: *IrBuilder, t: usize, v: usize): usize { ret set_arith(self, .ArithNot, t, v, 0); }
|
||||
|
||||
fn cmp_eq(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpEq, t, a, b); }
|
||||
fn cmp_ne(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpNe, t, a, b); }
|
||||
fn cmp_lt(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpLt, t, a, b); }
|
||||
fn cmp_le(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpLe, t, a, b); }
|
||||
fn cmp_gt(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpGt, t, a, b); }
|
||||
fn cmp_ge(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpGe, t, a, b); }
|
||||
|
||||
fn cast_trunc(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastTrunc, f, t, v); }
|
||||
fn cast_zext(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastZext, f, t, v); }
|
||||
fn cast_sext(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastSext, f, t, v); }
|
||||
fn cast_fext(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastFext, f, t, v); }
|
||||
fn cast_ftrunc(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastFtrunc, f, t, v); }
|
||||
fn cast_bitcast(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastBitcast, f, t, v); }
|
||||
fn cast_ptr2int(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastPtr2int, f, t, v); }
|
||||
fn cast_int2ptr(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastInt2ptr, f, t, v); }
|
||||
fn cast_bool2int(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastBool2int, f, t, v); }
|
||||
fn case_int2float(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CaseInt2float, f, t, v); }
|
||||
fn case_float2int(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CaseFloat2int, f, t, v); }
|
||||
|
||||
// ---- memory ----
|
||||
|
||||
fn mem_alloca(self: *IrBuilder, tid: usize, count: usize): usize {
|
||||
var ref = builder_alloc(self, .MemAlloca);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemAlloc(.{ .tid = tid, .count = count }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_global_alloc(self: *IrBuilder, tid: usize, const_node: usize): usize {
|
||||
var ref = builder_alloc(self, .MemGlobalAlloc);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemGlobalAlloc(.{ .tid = tid, .const_node = const_node }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_load(self: *IrBuilder, tid: usize, ptr: usize): usize {
|
||||
var ref = builder_alloc(self, .MemLoad);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemLoad(.{ .tid = tid, .ptr = ptr }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_store(self: *IrBuilder, tid: usize, ptr: usize, val: usize): usize {
|
||||
var ref = builder_alloc(self, .MemStore);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemStore(.{ .tid = tid, .ptr = ptr, .val = val }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_offset(self: *IrBuilder, tid: usize, ptr: usize, offset: usize): usize {
|
||||
var ref = builder_alloc(self, .MemOffset);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemOffset(.{ .tid = tid, .ptr = ptr, .offset = offset }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_field(self: *IrBuilder, tid: usize, agg: usize, field_idx: usize): usize {
|
||||
var ref = builder_alloc(self, .MemFieldPtr);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemFieldPtr(.{ .tid = tid, .agg = agg, .field_idx = field_idx }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_copy(self: *IrBuilder, tid: usize, dst: usize, src: usize, size: usize): usize {
|
||||
var ref = builder_alloc(self, .MemCopy);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemCopy(.{ .tid = tid, .dst = dst, .src = src, .size = size }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_set(self: *IrBuilder, tid: usize, dst: usize, val: usize, size: usize): usize {
|
||||
var ref = builder_alloc(self, .MemSet);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemSet(.{ .tid = tid, .dst = dst, .val = val, .size = size }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
// ---- type const ----
|
||||
|
||||
fn const_node(self: *IrBuilder, tid: usize, lit: ConstLit): usize {
|
||||
var ref = builder_alloc(self, .TypeConst);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Const(.{ .tid = tid, .lit = lit }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn type_const_ptr(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Ptr(val)); }
|
||||
fn type_const_bool(self: *IrBuilder, tid: usize, val: bool): usize { ret const_node(self, tid, .Bool(val)); }
|
||||
fn type_const_int(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Int(val)); }
|
||||
fn type_const_float(self: *IrBuilder, tid: usize, val: f64): usize { ret const_node(self, tid, .Float(val)); }
|
||||
fn type_const_char(self: *IrBuilder, tid: usize, val: u8): usize { ret const_node(self, tid, .Char(val)); }
|
||||
fn type_const_fn(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Fn(val)); }
|
||||
fn type_const_bytes(self: *IrBuilder, tid: usize, data: usize): usize { ret const_node(self, tid, .Ptr(data)); }
|
||||
|
||||
// ---- type ops ----
|
||||
|
||||
fn type_bitsizeof(self: *IrBuilder, tid: usize): usize {
|
||||
var ref = builder_alloc(self, .TypeBitsizeof);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn type_sizeof(self: *IrBuilder, tid: usize): usize {
|
||||
var ref = builder_alloc(self, .TypeSizeof);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn type_alignof(self: *IrBuilder, tid: usize): usize {
|
||||
var ref = builder_alloc(self, .TypeAlignof);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn type_offsetof(self: *IrBuilder, tid: usize, field_idx: usize): usize {
|
||||
var ref = builder_alloc(self, .TypeOffsetof);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = field_idx }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn type_field_count(self: *IrBuilder, tid: usize): usize {
|
||||
var ref = builder_alloc(self, .TypeFieldCount);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
// ---- agg ----
|
||||
|
||||
fn agg_construct(self: *IrBuilder, tid: usize): usize {
|
||||
var ref = builder_alloc(self, .AggConstruct);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null {
|
||||
e.body = .AggConstruct(.{ .tid = tid, .fields = Vec.blank(@sizeof(usize), @alignof(usize)) });
|
||||
}
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn agg_construct_field(self: *IrBuilder, node_ref: usize, field: usize) {
|
||||
var e = Ir.node(&self.ir, self.current_fn, node_ref);
|
||||
if e == null { ret; }
|
||||
// 载荷按值绑定:改副本后写回。
|
||||
match e.body {
|
||||
.AggConstruct(a) => {
|
||||
match Vec.push(&a.fields, &field as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
e.body = .AggConstruct(a);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn agg_extract(self: *IrBuilder, tid: usize, field_tid: usize, field_idx: usize, val: usize): usize {
|
||||
var ref = builder_alloc(self, .AggExtract);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null {
|
||||
e.body = .AggExtract(.{ .tid = tid, .field_tid = field_tid, .field_idx = field_idx, .val = val });
|
||||
}
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn agg_insert(self: *IrBuilder, tid: usize, field_idx: usize, agg: usize, field: usize): usize {
|
||||
var ref = builder_alloc(self, .AggInsert);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .AggInsert(.{ .tid = tid, .field_idx = field_idx, .agg = agg, .field = field }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
// ---- control ----
|
||||
|
||||
fn control_select(self: *IrBuilder, tid: usize, cond: usize, tv: usize, fv: usize): usize {
|
||||
var ref = builder_alloc(self, .ControlSelect);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Select(.{ .tid = tid, .cond = cond, .true_val = tv, .false_val = fv }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_br(self: *IrBuilder, cond: usize): usize {
|
||||
var ref = builder_alloc(self, .ControlBr);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Br(.{ .cond = cond, .true_label = 0, .false_label = 0 }); }
|
||||
self.terminated = true;
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_br_fixup(self: *IrBuilder, ref: usize, true_block: usize, false_block: usize) {
|
||||
var f1 = Fix { .node = ref, .slot = .BrTrue, .block = true_block };
|
||||
match Vec.push(&self.fixups, &f1 as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
var f2 = Fix { .node = ref, .slot = .BrFalse, .block = false_block };
|
||||
match Vec.push(&self.fixups, &f2 as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
}
|
||||
|
||||
fn control_jmp(self: *IrBuilder): usize {
|
||||
var ref = builder_alloc(self, .ControlJmp);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Jmp(.{ .label = 0 }); }
|
||||
self.terminated = true;
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_jmp_fixup(self: *IrBuilder, ref: usize, target_block: usize) {
|
||||
var fx = Fix { .node = ref, .slot = .Jmp, .block = target_block };
|
||||
match Vec.push(&self.fixups, &fx as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
}
|
||||
|
||||
fn control_call(self: *IrBuilder, tid: usize, fn_ref: usize): usize {
|
||||
var ref = builder_alloc(self, .ControlCall);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null {
|
||||
e.body = .Call(.{ .tid = tid, .func = fn_ref,
|
||||
.params = Vec.blank(@sizeof(usize), @alignof(usize)) });
|
||||
}
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_call_param(self: *IrBuilder, call_node: usize, arg: usize) {
|
||||
var e = Ir.node(&self.ir, self.current_fn, call_node);
|
||||
if e == null { ret; }
|
||||
// 载荷按值绑定:改副本后写回。
|
||||
match e.body {
|
||||
.Call(c) => {
|
||||
match Vec.push(&c.params, &arg as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
e.body = .Call(c);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn control_param(self: *IrBuilder, tid: usize, idx: usize): usize {
|
||||
var ref = builder_alloc(self, .ControlParam);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Param(.{ .tid = tid, .idx = idx }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_ret(self: *IrBuilder, tid: usize, val: usize): usize {
|
||||
var ref = builder_alloc(self, .ControlRet);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Ret(.{ .tid = tid, .val = val }); }
|
||||
self.terminated = true;
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_unreachable(self: *IrBuilder): usize {
|
||||
var ref = builder_alloc(self, .ControlUnreachable);
|
||||
self.terminated = true;
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_trap(self: *IrBuilder): usize {
|
||||
var ref = builder_alloc(self, .ControlTrap);
|
||||
self.terminated = true;
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn dbg_breakpoint(self: *IrBuilder): usize {
|
||||
ret builder_alloc(self, .DbgBreakpoint);
|
||||
}
|
||||
|
||||
fn dbg_panic(self: *IrBuilder, msg: ByteView): usize {
|
||||
var ref = builder_alloc(self, .DbgPanic);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Panic(.{ .msg = msg }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
|
||||
fn test_builder_emit_and_dump() {
|
||||
var b = builder_new();
|
||||
defer { builder_drop(&b); }
|
||||
|
||||
b.dbg = span_none();
|
||||
var fid = fn_begin(&b, ByteView.from_slice("f"), 3);
|
||||
var blk = block_begin(&b);
|
||||
var p = control_param(&b, 2, 0);
|
||||
var c = type_const_int(&b, 2, 42);
|
||||
var add = arith_add(&b, 2, p, c);
|
||||
control_ret(&b, 2, add);
|
||||
block_end(&b);
|
||||
fn_end(&b);
|
||||
|
||||
var ty = TypeTable.new();
|
||||
var t: TypeTable = undefined;
|
||||
match ty { .Ok(v) => { t = v; }, .Err(e) => { @assert(false, "tt"); } }
|
||||
defer { TypeTable.drop(&t); }
|
||||
var at = Attr.new();
|
||||
var a: Attr = undefined;
|
||||
match at { .Ok(v) => { a = v; }, .Err(e) => { @assert(false, "attr"); } }
|
||||
defer { Attr.drop(&a); }
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
@assert(Ir.dump(&b.ir, &t, &a, &out), "dump ok");
|
||||
var got = ByteBuffer.as_slice(out);
|
||||
@assert(got.len > 0, "dump nonempty");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_builder_emit_and_dump();
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,358 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 目标布局(移植自 stage1/spl_layout.c)。输入是 IR 类型表 TypeTable;
|
||||
// 为每个类型计算 size_bits / align_bits / 字段位偏移,供 ir2vm 使用。
|
||||
// 默认 64 位平台:标量按 1/2/4/8 字节对齐,指针 = usize,聚合按最大字段对齐
|
||||
// 且不允许重排(与 stage1 的默认策略一致)。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type TypeTable = @import("./type_table.spl");
|
||||
type .{ .TypeNode, .Agg, .AggKind, .Member } = @import("./type_table.spl");
|
||||
type .{ .node, .member_at, .make_int, .make_array, .make_agg, .push_member } = @import("./type_table.spl");
|
||||
|
||||
type LayoutInfo = struct {
|
||||
size_bits: usize,
|
||||
align_bits: usize,
|
||||
field_bit_offsets: Vec, // usize
|
||||
field_bit_sizes: Vec, // usize
|
||||
resolved: bool,
|
||||
}
|
||||
|
||||
type Layout = @self;
|
||||
|
||||
infos: Vec, // LayoutInfo
|
||||
ptr_size: usize,
|
||||
ptr_align: usize,
|
||||
|
||||
type LayoutResult = enum {
|
||||
Ok: Layout,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn blank_usizes(): Vec {
|
||||
ret Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
}
|
||||
|
||||
fn new(): LayoutResult {
|
||||
ret .Ok(Layout {
|
||||
.infos = Vec.blank(@sizeof(LayoutInfo), @alignof(LayoutInfo)),
|
||||
.ptr_size = @sizeof(usize),
|
||||
.ptr_align = @alignof(usize),
|
||||
});
|
||||
}
|
||||
|
||||
fn info_at(self: *Layout, id: usize): *LayoutInfo {
|
||||
if id == 0 || id >= self.infos.len { ret null; }
|
||||
match Vec.get(&self.infos, id) {
|
||||
.Ok(ptr) => { ret ptr as *LayoutInfo; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
// 未解析或越界返回 null(对应 stage1 spl_layout_info_of)。
|
||||
fn info_of(self: *Layout, id: usize): *LayoutInfo {
|
||||
var info = info_at(self, id);
|
||||
if info == null || !info.resolved { ret null; }
|
||||
ret info;
|
||||
}
|
||||
|
||||
fn drop(self: *Layout) {
|
||||
var i: usize = 0;
|
||||
while i < self.infos.len {
|
||||
var info = info_at(self, i);
|
||||
if info != null {
|
||||
Vec.drop(&info.field_bit_offsets);
|
||||
Vec.drop(&info.field_bit_sizes);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.infos);
|
||||
}
|
||||
|
||||
fn ensure(self: *Layout, n: usize) {
|
||||
while self.infos.len < n {
|
||||
var info = LayoutInfo {
|
||||
.size_bits = 0,
|
||||
.align_bits = 0,
|
||||
.field_bit_offsets = blank_usizes(),
|
||||
.field_bit_sizes = blank_usizes(),
|
||||
.resolved = false,
|
||||
};
|
||||
match Vec.push(&self.infos, &info as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret; },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_usize(v: *Vec, x: usize) {
|
||||
match Vec.push(v, &x as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn usize_at(v: *Vec, i: usize): usize {
|
||||
match Vec.get(v, i) {
|
||||
.Ok(ptr) => { ret (ptr as *usize).*; },
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// 标量对齐(字节);0 表示没有匹配的标量(即未落地)。
|
||||
fn scalar_align(size_bytes: usize): usize {
|
||||
if size_bytes == 1 { ret 1; }
|
||||
if size_bytes == 2 { ret 2; }
|
||||
if size_bytes == 4 { ret 4; }
|
||||
if size_bytes == 8 { ret 8; }
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn round_up_bits(x: usize, align_bits: usize): usize {
|
||||
if align_bits <= 1 { ret x; }
|
||||
ret (x + align_bits - 1) / align_bits * align_bits;
|
||||
}
|
||||
|
||||
fn layout_scalar(info: *LayoutInfo, size_bits: usize) {
|
||||
var size_bytes = (size_bits + 7) / 8;
|
||||
var a = scalar_align(size_bytes);
|
||||
if a == 0 { ret; }
|
||||
info.size_bits = size_bytes * 8;
|
||||
info.align_bits = a * 8;
|
||||
}
|
||||
|
||||
fn layout_ptr(self: *Layout, info: *LayoutInfo) {
|
||||
if self.ptr_size == 0 { ret; }
|
||||
info.size_bits = self.ptr_size * 8;
|
||||
info.align_bits = self.ptr_align * 8;
|
||||
}
|
||||
|
||||
// enum 与 struct/union/shape 的布局。字段索引约定(与 ir2vm 一致):
|
||||
// enum: 0 = tag,1..n = 各变体载荷(同一偏移);其余按声明顺序。
|
||||
fn layout_agg(self: *Layout, is_enum: bool, is_union: bool, agg: Agg, info: *LayoutInfo) {
|
||||
var n = agg.variants.len;
|
||||
|
||||
if is_enum {
|
||||
if agg.tag_type == 0 { ret; }
|
||||
var tag_info = info_at(self, agg.tag_type);
|
||||
if tag_info == null || !tag_info.resolved { ret; }
|
||||
|
||||
var tag_align_bits = tag_info.align_bits;
|
||||
if tag_align_bits == 0 { tag_align_bits = 8; }
|
||||
var payload_align_bits: usize = 8;
|
||||
var payload_size_bits: usize = 0;
|
||||
|
||||
var i: usize = 0;
|
||||
while i < n {
|
||||
var m = member_at(&agg.variants, i);
|
||||
if m != null && m.type_id != 0 {
|
||||
var fi = info_at(self, m.type_id);
|
||||
if fi == null || !fi.resolved { ret; }
|
||||
var fa = fi.align_bits;
|
||||
if fa == 0 { fa = 8; }
|
||||
if fa > payload_align_bits { payload_align_bits = fa; }
|
||||
if fi.size_bits > payload_size_bits { payload_size_bits = fi.size_bits; }
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var payload_off = round_up_bits(tag_info.size_bits, payload_align_bits);
|
||||
var final_align = tag_align_bits;
|
||||
if payload_align_bits > final_align { final_align = payload_align_bits; }
|
||||
info.size_bits = round_up_bits(payload_off + payload_size_bits, final_align);
|
||||
info.align_bits = final_align;
|
||||
|
||||
push_usize(&info.field_bit_offsets, 0);
|
||||
push_usize(&info.field_bit_sizes, tag_info.size_bits);
|
||||
i = 0;
|
||||
while i < n {
|
||||
var m = member_at(&agg.variants, i);
|
||||
var fsize: usize = 0;
|
||||
if m != null && m.type_id != 0 {
|
||||
var fi = info_at(self, m.type_id);
|
||||
if fi != null { fsize = fi.size_bits; }
|
||||
}
|
||||
push_usize(&info.field_bit_offsets, payload_off);
|
||||
push_usize(&info.field_bit_sizes, fsize);
|
||||
i = i + 1;
|
||||
}
|
||||
ret;
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
info.size_bits = 0;
|
||||
info.align_bits = 8;
|
||||
ret;
|
||||
}
|
||||
|
||||
var max_align_bits: usize = 8;
|
||||
var offset_bits: usize = 0;
|
||||
|
||||
var i: usize = 0;
|
||||
while i < n {
|
||||
var m = member_at(&agg.variants, i);
|
||||
var fsize_bits: usize = 0;
|
||||
var fsize_align: usize = 8;
|
||||
if m != null && m.type_id != 0 {
|
||||
var fi = info_at(self, m.type_id);
|
||||
if fi == null || !fi.resolved { ret; }
|
||||
fsize_bits = fi.size_bits;
|
||||
fsize_align = fi.align_bits;
|
||||
if fsize_align == 0 { fsize_align = 8; }
|
||||
}
|
||||
|
||||
if is_union {
|
||||
push_usize(&info.field_bit_offsets, 0);
|
||||
push_usize(&info.field_bit_sizes, fsize_bits);
|
||||
if fsize_bits > info.size_bits { info.size_bits = fsize_bits; }
|
||||
} else {
|
||||
offset_bits = round_up_bits(offset_bits, fsize_align);
|
||||
push_usize(&info.field_bit_offsets, offset_bits);
|
||||
push_usize(&info.field_bit_sizes, fsize_bits);
|
||||
offset_bits = offset_bits + fsize_bits;
|
||||
}
|
||||
if fsize_align > max_align_bits { max_align_bits = fsize_align; }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
if is_union {
|
||||
info.size_bits = round_up_bits(info.size_bits, max_align_bits);
|
||||
} else {
|
||||
info.size_bits = round_up_bits(offset_bits, max_align_bits);
|
||||
}
|
||||
info.align_bits = max_align_bits;
|
||||
}
|
||||
|
||||
fn resolve_members(self: *Layout, tt: *TypeTable, agg: Agg) {
|
||||
var i: usize = 0;
|
||||
while i < agg.variants.len {
|
||||
var m = member_at(&agg.variants, i);
|
||||
if m != null && m.type_id != 0 { resolve_one(self, tt, m.type_id); }
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_one(self: *Layout, tt: *TypeTable, id: usize) {
|
||||
var info = info_at(self, id);
|
||||
if info == null || info.resolved { ret; }
|
||||
var e = node(tt, id);
|
||||
if e == null { ret; }
|
||||
|
||||
info.resolved = true;
|
||||
|
||||
match e.node {
|
||||
.Error => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Infer => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Forall => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Exists => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Never => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Unit => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Any => { layout_ptr(self, info); },
|
||||
.Bool => { layout_scalar(info, 8); },
|
||||
.Int(x) => { layout_scalar(info, x.bits); },
|
||||
.Float(x) => { layout_scalar(info, x.bits); },
|
||||
.Ptr(x) => { layout_ptr(self, info); },
|
||||
.Array(a) => {
|
||||
resolve_one(self, tt, a.element);
|
||||
var elem = info_at(self, a.element);
|
||||
if elem == null || !elem.resolved { ret; }
|
||||
info.size_bits = elem.size_bits * a.len;
|
||||
info.align_bits = elem.align_bits;
|
||||
},
|
||||
.Struct(agg) => {
|
||||
resolve_members(self, tt, agg);
|
||||
layout_agg(self, false, false, agg, info);
|
||||
},
|
||||
.Union(agg) => {
|
||||
resolve_members(self, tt, agg);
|
||||
layout_agg(self, false, true, agg, info);
|
||||
},
|
||||
.Enum(agg) => {
|
||||
if agg.tag_type != 0 { resolve_one(self, tt, agg.tag_type); }
|
||||
resolve_members(self, tt, agg);
|
||||
layout_agg(self, true, false, agg, info);
|
||||
},
|
||||
.Shape(agg) => {
|
||||
resolve_members(self, tt, agg);
|
||||
layout_agg(self, false, false, agg, info);
|
||||
},
|
||||
.Fn(f) => {
|
||||
var i: usize = 0;
|
||||
while i < f.params.len {
|
||||
resolve_one(self, tt, usize_at(&f.params, i));
|
||||
i = i + 1;
|
||||
}
|
||||
resolve_one(self, tt, f.ret_type);
|
||||
layout_ptr(self, info);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve(self: *Layout, tt: *TypeTable) {
|
||||
ensure(self, tt.table.len);
|
||||
var i: usize = 1;
|
||||
while i < tt.table.len {
|
||||
resolve_one(self, tt, i);
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var tt: TypeTable = undefined;
|
||||
match TypeTable.new() {
|
||||
.Ok(v) => { tt = v; },
|
||||
.Err(error) => { @assert(false, "layout test: tt init"); ret; },
|
||||
}
|
||||
defer { TypeTable.drop(&tt); }
|
||||
|
||||
var i32t = make_int(&tt, 32, true);
|
||||
var u8t = make_int(&tt, 8, false);
|
||||
|
||||
var members = Vec.blank(@sizeof(Member), @alignof(Member));
|
||||
push_member(&members, "a", i32t);
|
||||
push_member(&members, "b", u8t);
|
||||
var agg = Agg { .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, .variants = members };
|
||||
var sid = make_agg(&tt, .Struct, agg);
|
||||
|
||||
var layout: Layout = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { layout = v; },
|
||||
.Err(error) => { @assert(false, "layout test: layout init"); ret; },
|
||||
}
|
||||
defer { drop(&layout); }
|
||||
|
||||
resolve(&layout, &tt);
|
||||
var info = info_of(&layout, sid);
|
||||
if info == null {
|
||||
@assert(false, "struct layout missing");
|
||||
ret;
|
||||
}
|
||||
@assert(info.size_bits == 64, "struct size_bits");
|
||||
@assert(info.align_bits == 32, "struct align_bits");
|
||||
@assert(usize_at(&info.field_bit_offsets, 0) == 0, "field0 off");
|
||||
@assert(usize_at(&info.field_bit_offsets, 1) == 32, "field1 off");
|
||||
|
||||
var arr = make_array(&tt, i32t, 3);
|
||||
resolve(&layout, &tt);
|
||||
var ainfo = info_of(&layout, arr);
|
||||
if ainfo == null {
|
||||
@assert(false, "array layout missing");
|
||||
ret;
|
||||
}
|
||||
@assert(ainfo.size_bits == 96, "array size_bits");
|
||||
@assert(ainfo.align_bits == 32, "array align_bits");
|
||||
|
||||
var pt = TypeTable.make_ptr(&tt, u8t);
|
||||
resolve(&layout, &tt);
|
||||
var pinfo = info_of(&layout, pt);
|
||||
if pinfo == null {
|
||||
@assert(false, "ptr layout missing");
|
||||
ret;
|
||||
}
|
||||
@assert(pinfo.size_bits == @sizeof(usize) * 8, "ptr size_bits");
|
||||
|
||||
ret;
|
||||
}
|
||||
@@ -0,0 +1,723 @@
|
||||
// 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 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 {
|
||||
match Vec.get(&self.insns, i) {
|
||||
.Ok(ptr) => { ret (ptr as *VmIns).*; },
|
||||
.Err(error) => { ret VmIns { .opcode = .Error, .ty = .Void, .imm = 0 }; },
|
||||
}
|
||||
ret VmIns { .opcode = .Error, .ty = .Void, .imm = 0 };
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
fn put_u8(out: *ByteBuffer, b: u8): bool {
|
||||
match ByteBuffer.append_byte(out, b) { .Ok => { ret true; }, .Err(error) => { ret false; } }
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn put_u16(out: *ByteBuffer, v: usize): bool {
|
||||
if !put_u8(out, (v & 255) as u8) { ret false; }
|
||||
ret put_u8(out, ((v >> 8) & 255) as u8);
|
||||
}
|
||||
|
||||
fn put_u64(out: *ByteBuffer, v: usize): bool {
|
||||
var i: usize = 0;
|
||||
while i < 8 {
|
||||
if !put_u8(out, ((v >> (i * 8)) & 255) as u8) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn put_zeros(out: *ByteBuffer, n: usize): bool {
|
||||
var i: usize = 0;
|
||||
while i < n {
|
||||
if !put_u8(out, 0) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn put_view(out: *ByteBuffer, v: ByteView): bool {
|
||||
if v.len == 0 { ret true; }
|
||||
match ByteBuffer.append(out, ByteView.as_slice(v)) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// 写入「带结尾 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);
|
||||
}
|
||||
|
||||
fn store(self: *Prog, path: ByteView): Status {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
|
||||
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);
|
||||
if !put_u16(&out, @enum2int(ins.opcode) as usize) { ret .Err(make(.Io)); }
|
||||
if !put_u16(&out, @enum2int(ins.ty) as usize) { ret .Err(make(.Io)); }
|
||||
if !put_u64(&out, ins.imm) { ret .Err(make(.Io)); }
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
+44
-2
@@ -136,6 +136,48 @@ fn parse_error(self: *Parser, tok: *Token, what: []u8) {
|
||||
Diag.err(self.diag, span_of(self, tok), .ParseExpected, what);
|
||||
}
|
||||
|
||||
// 转义解码(镜像 stage1 spl_decode_escape):去掉引号,处理 \n \t \r \0 \\ \' \" \xHH。
|
||||
fn hex_digit(c: u8): u8 {
|
||||
if c >= '0' && c <= '9' { ret c - '0'; }
|
||||
if c >= 'a' && c <= 'f' { ret (c - 'a') + 10; }
|
||||
if c >= 'A' && c <= 'F' { ret (c - 'A') + 10; }
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn decode_str(t: ByteView): ByteBuffer {
|
||||
@assert(t.len >= 2 && t.ptr != null, "string literal token must include quotes");
|
||||
var out = ByteBuffer.new();
|
||||
if t.len < 2 { ret out; }
|
||||
var i: usize = 1;
|
||||
var end = t.len - 1;
|
||||
while i < end {
|
||||
var c = t.ptr[i];
|
||||
if c == '\\' && i + 1 < end {
|
||||
var e = t.ptr[i + 1];
|
||||
if e == 'x' && i + 3 < end {
|
||||
var hi = hex_digit(t.ptr[i + 2]);
|
||||
var lo = hex_digit(t.ptr[i + 3]);
|
||||
match ByteBuffer.append_byte(&out, (hi * 16 + lo) as u8) {
|
||||
.Ok => {}, .Err(error) => {},
|
||||
}
|
||||
i = i + 4;
|
||||
} else {
|
||||
var d: u8 = e;
|
||||
if e == 'n' { d = '\n'; }
|
||||
else if e == 't' { d = '\t'; }
|
||||
else if e == 'r' { d = '\r'; }
|
||||
else if e == '0' { d = 0; }
|
||||
match ByteBuffer.append_byte(&out, d) { .Ok => {}, .Err(error) => {} }
|
||||
i = i + 2;
|
||||
}
|
||||
} else {
|
||||
match ByteBuffer.append_byte(&out, c) { .Ok => {}, .Err(error) => {} }
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
ret out;
|
||||
}
|
||||
|
||||
fn expect_punct(self: *Parser, punct: Punct, what: []u8): bool {
|
||||
var t = peek(self);
|
||||
if t == null || !kind_is_punct(t.kind, punct) {
|
||||
@@ -355,7 +397,7 @@ fn parse_pattern_atom(self: *Parser): usize {
|
||||
},
|
||||
.Str => {
|
||||
advance(self);
|
||||
ret new_node(self, .ExprStringLit(.{ .data = t.text }), t);
|
||||
ret new_node(self, .ExprStringLit(.{ .data = Ast.push_string(self.ast, decode_str(t.text)) }), t);
|
||||
},
|
||||
.Keyword(k) => {
|
||||
match k {
|
||||
@@ -1293,7 +1335,7 @@ fn parse_primary(self: *Parser): usize {
|
||||
},
|
||||
.Str => {
|
||||
advance(self);
|
||||
ret new_node(self, .ExprStringLit(.{ .data = t.text }), t);
|
||||
ret new_node(self, .ExprStringLit(.{ .data = Ast.push_string(self.ast, decode_str(t.text)) }), t);
|
||||
},
|
||||
.Keyword(k) => {
|
||||
if @enum2int(k) == @enum2int(Keyword.True) {
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 作用域树(移植自 stage1/spl_scope.c)。每个作用域是一张名字 -> Symbol 表,
|
||||
// 节点用 parent 指向父作用域;查找从 current_scope 沿父链上溯(支持遮蔽)。
|
||||
// 名字是 ByteView(借用源/AST);Map 复制键值字节,但 ByteView 指向的字节由
|
||||
// 调用方保证存活。node 字段是 def.spl 的 DefId。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .Map } = @import("utils/map.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .hash_view, .eq_view } = @import("utils/hash.spl");
|
||||
|
||||
type SymbolKind = enum {
|
||||
Error,
|
||||
Var,
|
||||
Member,
|
||||
Fn,
|
||||
Type,
|
||||
}
|
||||
|
||||
type Symbol = struct {
|
||||
name: ByteView,
|
||||
kind: SymbolKind,
|
||||
node: usize, // DefId
|
||||
}
|
||||
|
||||
type ScopeNode = struct {
|
||||
parent: usize,
|
||||
symbols: Map,
|
||||
}
|
||||
|
||||
type Scope = @self;
|
||||
|
||||
scopes: Vec, // ScopeNode
|
||||
root_scope: usize,
|
||||
current_scope: usize,
|
||||
|
||||
type ScopeResult = enum {
|
||||
Ok: Scope,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn new_map(): Map {
|
||||
ret Map.new(@sizeof(ByteView), @alignof(ByteView), @sizeof(Symbol), @alignof(Symbol),
|
||||
hash_view, eq_view);
|
||||
}
|
||||
|
||||
fn new(): ScopeResult {
|
||||
var sc = Scope {
|
||||
.scopes = Vec.blank(@sizeof(ScopeNode), @alignof(ScopeNode)),
|
||||
.root_scope = 0,
|
||||
.current_scope = 0,
|
||||
};
|
||||
var sentinel = ScopeNode { .parent = 0, .symbols = new_map() };
|
||||
match Vec.push(&sc.scopes, &sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&sc.scopes);
|
||||
Map.drop(&sentinel.symbols);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
ret .Ok(sc);
|
||||
}
|
||||
|
||||
fn drop(self: *Scope) {
|
||||
var i: usize = 0;
|
||||
while i < self.scopes.len {
|
||||
var n = node(self, i);
|
||||
if n != null {
|
||||
Map.drop(&n.symbols);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.scopes);
|
||||
self.root_scope = 0;
|
||||
self.current_scope = 0;
|
||||
}
|
||||
|
||||
// id 0 与越界返回 null。
|
||||
fn node(self: *Scope, id: usize): *ScopeNode {
|
||||
if id == 0 || id >= self.scopes.len { ret null; }
|
||||
match Vec.get(&self.scopes, id) {
|
||||
.Ok(ptr) => { ret ptr as *ScopeNode; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
// 新作用域 parent = 当前作用域;不自动切换 current(调用方负责,见 sema)。
|
||||
fn alloc(self: *Scope): usize {
|
||||
var n = ScopeNode { .parent = self.current_scope, .symbols = new_map() };
|
||||
match Vec.push(&self.scopes, &n as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Map.drop(&n.symbols);
|
||||
ret 0;
|
||||
},
|
||||
}
|
||||
ret self.scopes.len - 1;
|
||||
}
|
||||
|
||||
// 插入符号;同名已存在返回 false(不改动)。
|
||||
fn insert(self: *Scope, id: usize, symbol: Symbol): bool {
|
||||
if id == 0 || id >= self.scopes.len { ret false; }
|
||||
var n = node(self, id);
|
||||
if n == null { ret false; }
|
||||
var old = Symbol {
|
||||
.name = ByteView.empty(),
|
||||
.kind = .Error,
|
||||
.node = 0,
|
||||
};
|
||||
match Map.get(&n.symbols, &symbol.name as *u8, &old as *u8) {
|
||||
.Found => { ret false; },
|
||||
.NotFound => {},
|
||||
}
|
||||
match Map.put(&n.symbols, &symbol.name as *u8, &symbol as *u8) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// 从 current_scope 沿父链查找;找到填 out 返回 true。
|
||||
fn find(self: *Scope, name: ByteView, out: *Symbol): bool {
|
||||
if out == null { ret false; }
|
||||
var id = self.current_scope;
|
||||
while id != 0 {
|
||||
var n = node(self, id);
|
||||
if n == null { ret false; }
|
||||
match Map.get(&n.symbols, &name as *u8, out as *u8) {
|
||||
.Found => { ret true; },
|
||||
.NotFound => {},
|
||||
}
|
||||
id = n.parent;
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn sym(name: []u8, kind: SymbolKind, node: usize): Symbol {
|
||||
ret Symbol {
|
||||
.name = ByteView.from_slice(name),
|
||||
.kind = kind,
|
||||
.node = node,
|
||||
};
|
||||
}
|
||||
|
||||
fn test_alloc_and_find() {
|
||||
var sc: Scope = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { sc = v; },
|
||||
.Err(error) => { @assert(false, "scope new failed"); },
|
||||
}
|
||||
defer { drop(&sc); }
|
||||
|
||||
@assert(sc.root_scope == 0 && sc.current_scope == 0, "initial");
|
||||
|
||||
// root
|
||||
var root = alloc(&sc);
|
||||
sc.root_scope = root;
|
||||
sc.current_scope = root;
|
||||
|
||||
// module scope(child of root)
|
||||
var mod = alloc(&sc);
|
||||
sc.current_scope = mod;
|
||||
|
||||
@assert(insert(&sc, mod, sym("x", .Var, 11)), "insert x");
|
||||
@assert(!insert(&sc, mod, sym("x", .Var, 12)), "duplicate x");
|
||||
|
||||
// 函数作用域
|
||||
var fn_scope = alloc(&sc);
|
||||
sc.current_scope = fn_scope;
|
||||
|
||||
var out = sym("", .Error, 0);
|
||||
@assert(find(&sc, ByteView.from_slice("x"), &out), "find x from child");
|
||||
@assert(out.node == 11, "x def id");
|
||||
|
||||
// 遮蔽:函数内层再定义 x
|
||||
@assert(insert(&sc, fn_scope, sym("x", .Fn, 99)), "shadow x");
|
||||
@assert(find(&sc, ByteView.from_slice("x"), &out), "find shadow");
|
||||
@assert(out.node == 99, "shadow def id");
|
||||
|
||||
// 未定义
|
||||
@assert(!find(&sc, ByteView.from_slice("nope"), &out), "missing");
|
||||
|
||||
// 父链结构
|
||||
var m = node(&sc, mod);
|
||||
var f = node(&sc, fn_scope);
|
||||
@assert(m != null && m.parent == root, "mod parent");
|
||||
@assert(f != null && f.parent == mod, "fn parent");
|
||||
}
|
||||
|
||||
fn test_kinds_isolated() {
|
||||
var sc: Scope = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { sc = v; },
|
||||
.Err(error) => { @assert(false, "scope new failed"); },
|
||||
}
|
||||
defer { drop(&sc); }
|
||||
|
||||
var a = alloc(&sc);
|
||||
sc.current_scope = a;
|
||||
@assert(insert(&sc, a, sym("T", .Type, 1)), "type T");
|
||||
@assert(insert(&sc, a, sym("f", .Fn, 2)), "fn f");
|
||||
|
||||
var out = sym("", .Error, 0);
|
||||
@assert(find(&sc, ByteView.from_slice("T"), &out), "find T");
|
||||
var kind_matches = false;
|
||||
match out.kind {
|
||||
.Type => { kind_matches = true; },
|
||||
_ => {},
|
||||
}
|
||||
@assert(kind_matches, "T kind");
|
||||
@assert(find(&sc, ByteView.from_slice("f"), &out), "find f");
|
||||
@assert(out.node == 2, "f def id");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_alloc_and_find();
|
||||
test_kinds_isolated();
|
||||
}
|
||||
+3088
File diff suppressed because it is too large.
Load diff
+578
-12
@@ -1,8 +1,8 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// splc1 (stage2 自举编译器入口)。
|
||||
// 当前仅实现 `--dump lex <file> [out]`:读取源文件,词法分析,按与 stage1
|
||||
// splc0 完全一致的格式打印 token 列表,供 diff 测试逐字节比对。
|
||||
// 当前实现 `--dump <lex|ast|sema|ir|vm> <file> [out]`:读取源文件,按与 stage1
|
||||
// splc0 完全一致的格式打印各阶段产物,供 diff 测试逐字节比对。
|
||||
|
||||
type .{ .ByteView } = @import("./utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("./utils/byte_buffer.spl");
|
||||
@@ -11,11 +11,21 @@ type .{ .Token, .kind_name } = @import("./token.spl");
|
||||
type .{ .Lexer } = @import("./lexer.spl");
|
||||
type Ast = @import("./ast.spl");
|
||||
type Parser = @import("./parser.spl");
|
||||
type Sema = @import("./sema.spl");
|
||||
type .{ .append_usize } = @import("./utils/fmt.spl");
|
||||
type .{ .read_all, .print, .eprint } = @import("./utils/io.spl");
|
||||
type .{ .set_new, .set_add, .set_drop } = @import("./utils/source.spl");
|
||||
type Diag = @import("./diag.spl");
|
||||
type .{ .Sources, .Source, .set_new, .set_add, .set_at, .set_drop } = @import("./utils/source.spl");
|
||||
type .{ .Diag, .Code, .span_none } = @import("./diag.spl");
|
||||
type Logger = @import("./utils/log.spl");
|
||||
type .{ .Loader, .LoadResult } = @import("./sema.spl");
|
||||
type IrBuilder = @import("./ir_builder.spl");
|
||||
type Ir = @import("./ir.spl");
|
||||
type Ast2Ir = @import("./ast2ir.spl");
|
||||
type LayoutM = @import("./layout.spl");
|
||||
type .{ .Layout } = @import("./layout.spl");
|
||||
type Ir2VmM = @import("./ir2vm.spl");
|
||||
type .{ .Ir2Vm } = @import("./ir2vm.spl");
|
||||
type .{ .alloc, .free } = @import("./sys/mem.spl");
|
||||
|
||||
fn token_at(tokens: *Vec, index: usize): Token {
|
||||
match Vec.get(tokens, index) {
|
||||
@@ -209,31 +219,587 @@ fn dump_ast(log: *Logger, path: ByteView): i32 {
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// ---- @import 模块加载器(driver 侧)----
|
||||
|
||||
type ModEntry = struct {
|
||||
path: ByteView,
|
||||
ast: *Ast,
|
||||
ok: bool,
|
||||
}
|
||||
|
||||
type LoaderCtx = struct {
|
||||
sources: *Sources,
|
||||
mods: Vec, // ModEntry
|
||||
buffers: Vec, // ByteBuffer(路径/文本稳定存储)
|
||||
diag: *Diag,
|
||||
}
|
||||
|
||||
fn ignore_bool(b: bool) {}
|
||||
|
||||
fn append_bytes(buf: *ByteBuffer, bytes: []u8): bool {
|
||||
match ByteBuffer.append(buf, bytes) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn append_byte(buf: *ByteBuffer, b: u8): bool {
|
||||
match ByteBuffer.append_byte(buf, b) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn sub(p: ByteView, a: usize, b: usize): []u8 {
|
||||
ret ByteView.as_slice(p)[a..b];
|
||||
}
|
||||
|
||||
// 处理 "..":弹出 out 的最后一个路径分量;无分量可弹(空 / 根 / 末段已是 "..")时,
|
||||
// 退化为追加 ".."(相对路径的语义),绝对根则忽略。这样 "../x" 与规范路径能归一到同一模块。
|
||||
fn normalize_pop(out: *ByteBuffer) {
|
||||
var cur = ByteBuffer.as_slice(out.*);
|
||||
if cur.len == 0 {
|
||||
ignore_bool(append_bytes(out, ".."));
|
||||
ret;
|
||||
}
|
||||
var end = cur.len;
|
||||
if cur.ptr[end - 1] == '/' {
|
||||
if end == 1 { ret; } // 绝对根 "/":忽略
|
||||
end = end - 1;
|
||||
}
|
||||
var start = end;
|
||||
while start > 0 && cur.ptr[start - 1] != '/' { start = start - 1; }
|
||||
if end - start == 2 && cur.ptr[start] == '.' && cur.ptr[start + 1] == '.' {
|
||||
ignore_bool(append_bytes(out, ".."));
|
||||
ret;
|
||||
}
|
||||
var new_len: usize = 0;
|
||||
if start > 0 { new_len = start - 1; }
|
||||
if new_len == 0 && cur.ptr[0] == '/' { new_len = 1; }
|
||||
out.*.storage.len = new_len;
|
||||
}
|
||||
|
||||
// 折叠重复分隔符、解析 "." 与 ".."、'\' 统一成 '/'。
|
||||
fn normalize_into(out: *ByteBuffer, p: ByteView) {
|
||||
if p.len > 0 && (p.ptr[0] == '/' || p.ptr[0] == '\\') {
|
||||
ignore_bool(append_byte(out, '/'));
|
||||
}
|
||||
var i: usize = 0;
|
||||
while i < p.len {
|
||||
while i < p.len && (p.ptr[i] == '/' || p.ptr[i] == '\\') { i = i + 1; }
|
||||
if i >= p.len { break; }
|
||||
var start = i;
|
||||
while i < p.len && p.ptr[i] != '/' && p.ptr[i] != '\\' { i = i + 1; }
|
||||
var seg = sub(p, start, i);
|
||||
if seg.len == 1 && seg.ptr[0] == '.' { continue; }
|
||||
if seg.len == 2 && seg.ptr[0] == '.' && seg.ptr[1] == '.' {
|
||||
normalize_pop(out);
|
||||
continue;
|
||||
}
|
||||
var cur = ByteBuffer.as_slice(out.*);
|
||||
if cur.len > 0 && cur.ptr[cur.len - 1] != '/' {
|
||||
ignore_bool(append_byte(out, '/'));
|
||||
}
|
||||
ignore_bool(append_bytes(out, seg));
|
||||
}
|
||||
}
|
||||
|
||||
// 把 path 拼到 from 所在目录,并规范化;绝对路径直通。
|
||||
fn resolve_path(from: ByteView, path: ByteView): ByteBuffer {
|
||||
var out = ByteBuffer.new();
|
||||
var absolute = false;
|
||||
if path.len > 0 && (path.ptr[0] == '/' || path.ptr[0] == '\\') { absolute = true; }
|
||||
if path.len >= 2 && path.ptr[1] == ':' { absolute = true; }
|
||||
if absolute {
|
||||
normalize_into(&out, path);
|
||||
ret out;
|
||||
}
|
||||
var last: isize = -1;
|
||||
var i: usize = 0;
|
||||
while i < from.len {
|
||||
if from.ptr[i] == '/' || from.ptr[i] == '\\' { last = i as isize; }
|
||||
i = i + 1;
|
||||
}
|
||||
if last >= 0 {
|
||||
ignore_bool(append_bytes(&out, sub(from, 0, (last as usize) + 1)));
|
||||
}
|
||||
ignore_bool(append_bytes(&out, ByteView.as_slice(path)));
|
||||
var norm = ByteBuffer.new();
|
||||
normalize_into(&norm, ByteView.from_slice(ByteBuffer.as_slice(out)));
|
||||
ByteBuffer.drop(&out);
|
||||
ret norm;
|
||||
}
|
||||
|
||||
fn mod_at(ctx: *LoaderCtx, i: usize): *ModEntry {
|
||||
match Vec.get(&ctx.mods, i) {
|
||||
.Ok(ptr) => { ret ptr as *ModEntry; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn driver_load(user: *any, from: ByteView, path: ByteView, diag: *Diag): LoadResult {
|
||||
var ctx = user as *LoaderCtx;
|
||||
var absbuf = resolve_path(from, path);
|
||||
var abs = ByteView.from_slice(ByteBuffer.as_slice(absbuf));
|
||||
|
||||
var i: usize = 0;
|
||||
while i < ctx.mods.len {
|
||||
var m = mod_at(ctx, i);
|
||||
if m != null && ByteView.eq(m.path, abs) {
|
||||
ByteBuffer.drop(&absbuf);
|
||||
if !m.ok {
|
||||
Diag.err(diag, span_none(), .CircularDep, "@import: circular import");
|
||||
ret .Missing;
|
||||
}
|
||||
ret .Ok(m.ast);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var midx = ctx.mods.len;
|
||||
var placeholder = ModEntry { .path = abs, .ast = null, .ok = false };
|
||||
match Vec.push(&ctx.mods, &placeholder as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&absbuf); ret .Missing; },
|
||||
}
|
||||
match Vec.push(&ctx.buffers, &absbuf as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&absbuf); ret .Missing; },
|
||||
}
|
||||
|
||||
var content = ByteBuffer.new();
|
||||
match read_all(abs, &content) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Diag.err(diag, span_none(), .LexUnexpected, "@import: cannot open module");
|
||||
ByteBuffer.drop(&content);
|
||||
ret .Missing;
|
||||
},
|
||||
}
|
||||
var text = ByteView.from_slice(ByteBuffer.as_slice(content));
|
||||
var sidx: usize = 0;
|
||||
match set_add(ctx.sources, abs, text) {
|
||||
.Ok(index) => { sidx = index; },
|
||||
.Err(error) => { ByteBuffer.drop(&content); ret .Missing; },
|
||||
}
|
||||
match Vec.push(&ctx.buffers, &content as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&content); ret .Missing; },
|
||||
}
|
||||
|
||||
var tokens = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||
match Lexer.lex(text, diag, 0) {
|
||||
.Ok(v) => { tokens = v; },
|
||||
.Err(error) => { Vec.drop(&tokens); ret .Missing; },
|
||||
}
|
||||
|
||||
var ast_val: Ast = undefined;
|
||||
match Ast.new(ctx.sources) {
|
||||
.Ok(v) => { ast_val = v; },
|
||||
.Err(error) => { Vec.drop(&tokens); ret .Missing; },
|
||||
}
|
||||
|
||||
var p = Parser.new(&ast_val, tokens, diag);
|
||||
p.file = sidx;
|
||||
Parser.parse(&p);
|
||||
Vec.drop(&tokens);
|
||||
if Diag.has_error(diag.*) || p.failed {
|
||||
Ast.drop(&ast_val);
|
||||
ret .Missing;
|
||||
}
|
||||
|
||||
var raw = alloc(@sizeof(Ast));
|
||||
if raw == null {
|
||||
Ast.drop(&ast_val);
|
||||
ret .Missing;
|
||||
}
|
||||
var ap = raw as *Ast;
|
||||
ap.* = ast_val;
|
||||
|
||||
var slot = mod_at(ctx, midx);
|
||||
if slot != null {
|
||||
slot.ast = ap;
|
||||
slot.ok = true;
|
||||
}
|
||||
ret .Ok(ap);
|
||||
}
|
||||
|
||||
fn loader_ctx_drop(ctx: *LoaderCtx) {
|
||||
var i: usize = 0;
|
||||
while i < ctx.mods.len {
|
||||
var m = mod_at(ctx, i);
|
||||
if m != null && m.ok && m.ast != null {
|
||||
Ast.drop(m.ast);
|
||||
free(m.ast as *any);
|
||||
m.ast = null;
|
||||
m.ok = false;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&ctx.mods);
|
||||
|
||||
var j: usize = 0;
|
||||
while j < ctx.buffers.len {
|
||||
match Vec.get(&ctx.buffers, j) {
|
||||
.Ok(ptr) => { ByteBuffer.drop(ptr as *ByteBuffer); },
|
||||
.Err(error) => {},
|
||||
}
|
||||
j = j + 1;
|
||||
}
|
||||
Vec.drop(&ctx.buffers);
|
||||
}
|
||||
|
||||
fn dump_sema(log: *Logger, path: ByteView): i32 {
|
||||
var content = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&content); }
|
||||
match read_all(path, &content) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot read input file");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
var text = ByteView.from_slice(ByteBuffer.as_slice(content));
|
||||
|
||||
var sources = set_new();
|
||||
defer { set_drop(&sources); }
|
||||
match set_add(&sources, path, text) {
|
||||
.Ok(index) => {},
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot register source");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
var diag = Diag.new(&sources);
|
||||
defer { Diag.drop(&diag); }
|
||||
|
||||
var tokens = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||
match Lexer.lex(text, &diag, 0) {
|
||||
.Ok(v) => { tokens = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "lex failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Vec.drop(&tokens); }
|
||||
|
||||
var ast: Ast = undefined;
|
||||
match Ast.new(&sources) {
|
||||
.Ok(v) => { ast = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "ast init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Ast.drop(&ast); }
|
||||
|
||||
var p = Parser.new(&ast, tokens, &diag);
|
||||
Parser.parse(&p);
|
||||
|
||||
if Diag.has_error(diag) || p.failed {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
|
||||
var ctx = LoaderCtx {
|
||||
.sources = &sources,
|
||||
.mods = Vec.blank(@sizeof(ModEntry), @alignof(ModEntry)),
|
||||
.buffers = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)),
|
||||
.diag = &diag,
|
||||
};
|
||||
defer { loader_ctx_drop(&ctx); }
|
||||
var loader = Loader { .load = driver_load, .user = &ctx as *any };
|
||||
|
||||
var sema: Sema = undefined;
|
||||
match Sema.new(&ast, &diag) {
|
||||
.Ok(v) => { sema = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "sema init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Sema.drop(&sema); }
|
||||
sema.sources = &sources;
|
||||
sema.loader = &loader;
|
||||
|
||||
Sema.run(&sema);
|
||||
|
||||
if Diag.has_error(diag) {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !Sema.dump(&sema, &out) {
|
||||
Logger.fatal(log, "out of memory while dumping sema");
|
||||
ret 1;
|
||||
}
|
||||
if !Ast.dump(&ast, &out) {
|
||||
Logger.fatal(log, "out of memory while dumping ast");
|
||||
ret 1;
|
||||
}
|
||||
match print(ByteBuffer.as_slice(out)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 1; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
type PipeMode = enum {
|
||||
Ir, // 只 dump IR
|
||||
Vm, // 继续 layout + ir2vm,dump VM
|
||||
Compile, // 继续 layout + ir2vm,写 outpath
|
||||
State, // 只逐步打日志(诊断“卡在哪一步”)
|
||||
}
|
||||
|
||||
fn step(mode: PipeMode, s: []u8) {
|
||||
if @enum2int(mode) == @enum2int(PipeMode.State) {
|
||||
match eprint(s) { .Ok => {}, .Err(error) => {} }
|
||||
}
|
||||
}
|
||||
|
||||
// 完整前端流水线:lex -> parse -> sema -> ast2ir ->(可选)layout + ir2vm。
|
||||
fn run_pipeline(log: *Logger, path: ByteView, mode: PipeMode, outpath: ByteView): i32 {
|
||||
var content = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&content); }
|
||||
match read_all(path, &content) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot read input file");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
var text = ByteView.from_slice(ByteBuffer.as_slice(content));
|
||||
step(mode, "state: read ok\n");
|
||||
|
||||
var sources = set_new();
|
||||
defer { set_drop(&sources); }
|
||||
var main_idx: usize = 0;
|
||||
match set_add(&sources, path, text) {
|
||||
.Ok(index) => { main_idx = index; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot register source");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
var diag = Diag.new(&sources);
|
||||
defer { Diag.drop(&diag); }
|
||||
|
||||
var tokens = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||
match Lexer.lex(text, &diag, 0) {
|
||||
.Ok(v) => { tokens = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "lex failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Vec.drop(&tokens); }
|
||||
|
||||
var ast: Ast = undefined;
|
||||
match Ast.new(&sources) {
|
||||
.Ok(v) => { ast = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "ast init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Ast.drop(&ast); }
|
||||
|
||||
var p = Parser.new(&ast, tokens, &diag);
|
||||
p.file = main_idx;
|
||||
Parser.parse(&p);
|
||||
|
||||
if Diag.has_error(diag) || p.failed {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} }
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
step(mode, "state: parse ok\n");
|
||||
|
||||
var ctx = LoaderCtx {
|
||||
.sources = &sources,
|
||||
.mods = Vec.blank(@sizeof(ModEntry), @alignof(ModEntry)),
|
||||
.buffers = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)),
|
||||
.diag = &diag,
|
||||
};
|
||||
defer { loader_ctx_drop(&ctx); }
|
||||
var loader = Loader { .load = driver_load, .user = &ctx as *any };
|
||||
|
||||
var sema: Sema = undefined;
|
||||
match Sema.new(&ast, &diag) {
|
||||
.Ok(v) => { sema = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "sema init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Sema.drop(&sema); }
|
||||
sema.sources = &sources;
|
||||
sema.loader = &loader;
|
||||
|
||||
Sema.run(&sema);
|
||||
if Diag.has_error(diag) {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} }
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
|
||||
step(mode, "state: sema ok\n");
|
||||
|
||||
var builder = IrBuilder.builder_new();
|
||||
defer { IrBuilder.builder_drop(&builder); }
|
||||
var a2ir = Ast2Ir.new(&builder, &sema, &diag);
|
||||
defer { Ast2Ir.drop(&a2ir); }
|
||||
|
||||
var asts = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
defer { Vec.drop(&asts); }
|
||||
var ap: *Ast = *
|
||||
match Vec.push(&asts, &ap as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
var mi: usize = 0;
|
||||
while mi < ctx.mods.len {
|
||||
var m = mod_at(&ctx, mi);
|
||||
if m != null && m.ok && m.ast != null {
|
||||
var mp: *Ast = m.ast;
|
||||
match Vec.push(&asts, &mp as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
}
|
||||
mi = mi + 1;
|
||||
}
|
||||
|
||||
Ast2Ir.run(&a2ir, asts);
|
||||
if Diag.has_error(diag) {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} }
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
|
||||
step(mode, "state: ast2ir ok\n");
|
||||
|
||||
if @enum2int(mode) == @enum2int(PipeMode.Ir) {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !Ir.dump(&builder.ir, &sema.types.ir, &sema.attr, &out) {
|
||||
Logger.fatal(log, "out of memory while dumping ir");
|
||||
ret 1;
|
||||
}
|
||||
match print(ByteBuffer.as_slice(out)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 1; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// 类型就绪后解析布局,再做 IR -> VM。
|
||||
var layout: Layout = undefined;
|
||||
match LayoutM.new() {
|
||||
.Ok(v) => { layout = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "layout init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { LayoutM.drop(&layout); }
|
||||
LayoutM.resolve(&layout, &sema.types.ir);
|
||||
step(mode, "state: layout ok\n");
|
||||
|
||||
var ir2vm: Ir2Vm = undefined;
|
||||
Ir2VmM.init(&ir2vm, &builder.ir, &sema.types.ir, &layout, &sema.attr);
|
||||
defer { Ir2VmM.drop(&ir2vm); }
|
||||
ignore_i32(Ir2VmM.run(&ir2vm));
|
||||
step(mode, "state: ir2vm ok\n");
|
||||
|
||||
if @enum2int(mode) == @enum2int(PipeMode.Vm) {
|
||||
var vout = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&vout); }
|
||||
if !Ir2VmM.dump(&ir2vm, &vout) {
|
||||
Logger.fatal(log, "out of memory while dumping vm");
|
||||
ret 1;
|
||||
}
|
||||
match print(ByteBuffer.as_slice(vout)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 1; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
if @enum2int(mode) == @enum2int(PipeMode.State) {
|
||||
ret 0;
|
||||
}
|
||||
|
||||
if !Ir2VmM.store(&ir2vm, outpath) {
|
||||
Logger.error(log, "cannot write output file");
|
||||
ret 1;
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn ignore_i32(v: i32) {}
|
||||
|
||||
fn main(argc: i32, argv: **u8): i32 {
|
||||
// 无参数:自测运行(`nob test stage2`)视为成功。
|
||||
if argc <= 1 { ret 0; }
|
||||
|
||||
var log = Logger.new(ByteView.from_slice("splc1"));
|
||||
|
||||
if argc < 4 {
|
||||
Logger.fatal(&log, "usage: splc1 --dump <lex|ast> <file> [out]");
|
||||
ret 1;
|
||||
var arg1 = ByteView.from_cstr(argv[1]);
|
||||
|
||||
if !ByteView.eq(arg1, ByteView.from_slice("--dump")) {
|
||||
// 编译模式:splc1 <in> <out>
|
||||
if argc < 3 {
|
||||
Logger.fatal(&log, "usage: splc1 <in> <out> | splc1 --dump <lex|ast|sema|ir|vm> <file>");
|
||||
ret 1;
|
||||
}
|
||||
var in = ByteView.from_cstr(argv[1]);
|
||||
var out = ByteView.from_cstr(argv[2]);
|
||||
ret run_pipeline(&log, in, .Compile, out);
|
||||
}
|
||||
|
||||
var arg1 = ByteView.from_cstr(argv[1]);
|
||||
if argc < 4 {
|
||||
Logger.fatal(&log, "usage: splc1 --dump <lex|ast|sema|ir|vm> <file> [out]");
|
||||
ret 1;
|
||||
}
|
||||
var mode = ByteView.from_cstr(argv[2]);
|
||||
var path = ByteView.from_cstr(argv[3]);
|
||||
|
||||
if !ByteView.eq(arg1, ByteView.from_slice("--dump")) {
|
||||
Logger.fatal(&log, "usage: splc1 --dump <lex|ast> <file> [out]");
|
||||
ret 1;
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("lex")) {
|
||||
ret dump_lex(&log, path);
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("ast")) {
|
||||
ret dump_ast(&log, path);
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("sema")) {
|
||||
ret dump_sema(&log, path);
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("ir")) {
|
||||
ret run_pipeline(&log, path, .Ir, ByteView.empty());
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("vm")) {
|
||||
ret run_pipeline(&log, path, .Vm, ByteView.empty());
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("state")) {
|
||||
ret run_pipeline(&log, path, .State, ByteView.empty());
|
||||
}
|
||||
Logger.fatal(&log, "dump mode not implemented");
|
||||
ret 1;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 时间/参考量接口(原生 vm_timestamp,case 探测风格,不假设一定可用)。
|
||||
// available() -> 1 = 时间可用;0 = 不可用(其余 time 相关返回值恒为 0)
|
||||
// sec() -> 当前秒
|
||||
// ns() -> 当前纳秒部分
|
||||
// steps() -> VM 已执行指令数(参考量)
|
||||
// now_ns() -> 完整纳秒时间戳(单次调用;纪元纳秒 < 2^64,无需 i128)
|
||||
|
||||
#extern(vm) fn vm_timestamp(kind: usize): usize;
|
||||
|
||||
fn available(): usize {
|
||||
ret vm_timestamp(0);
|
||||
}
|
||||
|
||||
fn sec(): usize {
|
||||
ret vm_timestamp(1);
|
||||
}
|
||||
|
||||
fn ns(): usize {
|
||||
ret vm_timestamp(2);
|
||||
}
|
||||
|
||||
fn steps(): usize {
|
||||
ret vm_timestamp(3);
|
||||
}
|
||||
|
||||
fn now_ns(): usize {
|
||||
ret vm_timestamp(4);
|
||||
}
|
||||
|
||||
fn main(): void {
|
||||
var has = available();
|
||||
if has != 0 {
|
||||
var a = now_ns();
|
||||
var b = now_ns();
|
||||
@assert(b >= a, "now monotonic");
|
||||
}
|
||||
|
||||
var s0 = steps();
|
||||
var i: usize = 0;
|
||||
while i < 1000 { i = i + 1; }
|
||||
var s1 = steps();
|
||||
@assert(s1 >= s0, "steps monotonic");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
type spl_vm_val_t = u64; // usize
|
||||
|
||||
#extern(vm) fn vm_new(): *any;
|
||||
#extern(vm) fn vm_drop(vm: *any): void;
|
||||
|
||||
#extern(vm) fn vm_load(vm: *any, fpath: *u8): void;
|
||||
#extern(vm) fn vm_load_from_buffer(vm: *any, buffer: []u8): void;
|
||||
|
||||
#extern(vm) fn vm_push(vm: *any, val: spl_vm_val_t): void;
|
||||
// TODO
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,917 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// IR 类型表(移植自 stage1/spl_type.c)。类型按结构 hash-consing(intern),
|
||||
// id 0 为保留哨兵(Error)。节点借用式持有:名称是 ByteView(指向源/AST),
|
||||
// 不复制字符串;聚合成员与函数参数的 Vec 由本表拥有,drop 时统一释放。
|
||||
//
|
||||
// hash 只用于选桶;相等永远由结构比较决定。桶以「完整 hash -> 链头 id」的
|
||||
// Map 存储,节点自带 hash_next 串链(对应 stage1 的 spl_fold)。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .Map } = @import("utils/map.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .append_usize } = @import("utils/fmt.spl");
|
||||
type .{ .hash_usize, .eq_usize } = @import("utils/hash.spl");
|
||||
|
||||
type LayoutMode = enum {
|
||||
Auto,
|
||||
ExternC,
|
||||
Packed,
|
||||
}
|
||||
|
||||
type Member = struct {
|
||||
name: ByteView,
|
||||
type_id: usize,
|
||||
ast_node_ref: usize,
|
||||
}
|
||||
|
||||
type Agg = struct {
|
||||
mode: LayoutMode,
|
||||
fixed_align_bits: usize,
|
||||
tag_type: usize,
|
||||
variants: Vec, // Member
|
||||
}
|
||||
|
||||
type AggKind = enum {
|
||||
Struct,
|
||||
Union,
|
||||
Enum,
|
||||
Shape,
|
||||
}
|
||||
|
||||
// 与 stage1 spl_type_node_kind_t 一一对应;Forall/Exists 保留(暂打印 quant)。
|
||||
type TypeNode = enum {
|
||||
Error,
|
||||
Never,
|
||||
Any,
|
||||
Unit,
|
||||
Infer,
|
||||
Bool,
|
||||
Int: struct { bits: usize, is_signed: bool },
|
||||
Float: struct { bits: usize },
|
||||
Ptr: struct { pointee: usize },
|
||||
Array: struct { element: usize, len: usize },
|
||||
Struct: Agg,
|
||||
Union: Agg,
|
||||
Enum: Agg,
|
||||
Fn: struct { params: Vec, ret_type: usize }, // params: usize
|
||||
Forall,
|
||||
Exists,
|
||||
Shape: Agg,
|
||||
}
|
||||
|
||||
// 表项 = 节点 + intern 桶链指针(hash_next 不参与结构相等)。
|
||||
type TypeEntry = struct {
|
||||
hash_next: usize,
|
||||
node: TypeNode,
|
||||
}
|
||||
|
||||
type TypeTable = @self;
|
||||
|
||||
table: Vec, // TypeEntry
|
||||
fold: Map, // usize hash -> usize chain head
|
||||
|
||||
type TypeTableResult = enum {
|
||||
Ok: TypeTable,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
// ---- hash(FNV 风格;值不必与 stage1 相同,只要结构一致即可)----
|
||||
|
||||
fn seed(): usize {
|
||||
ret 14695981039346656037;
|
||||
}
|
||||
|
||||
fn mix(h: usize, x: usize): usize {
|
||||
ret (h ^ x) * 1099511628211;
|
||||
}
|
||||
|
||||
fn mode_code(mode: LayoutMode): usize {
|
||||
var m: LayoutMode = mode;
|
||||
ret @enum2int(m) as usize;
|
||||
}
|
||||
|
||||
fn hash_agg(h0: usize, agg: Agg): usize {
|
||||
var h = mix(h0, mode_code(agg.mode));
|
||||
h = mix(h, agg.fixed_align_bits);
|
||||
h = mix(h, agg.tag_type);
|
||||
h = mix(h, agg.variants.len);
|
||||
var i: usize = 0;
|
||||
while i < agg.variants.len {
|
||||
var m = member_at(&agg.variants, i);
|
||||
if m != null {
|
||||
h = mix(h, m.type_id);
|
||||
h = mix_bytes(h, m.name);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret h;
|
||||
}
|
||||
|
||||
fn mix_bytes(h0: usize, view: ByteView): usize {
|
||||
var h = h0;
|
||||
var i: usize = 0;
|
||||
while i < view.len {
|
||||
h = mix(h, view.ptr[i] as usize);
|
||||
i = i + 1;
|
||||
}
|
||||
ret mix(h, 0); // 终止符参与混合,避免名称拼接歧义
|
||||
}
|
||||
|
||||
fn hash_node(n: *TypeNode): usize {
|
||||
var h = seed();
|
||||
match n.* {
|
||||
.Error => { h = mix(h, 0); },
|
||||
.Never => { h = mix(h, 1); },
|
||||
.Any => { h = mix(h, 2); },
|
||||
.Unit => { h = mix(h, 3); },
|
||||
.Infer => { h = mix(h, 4); },
|
||||
.Bool => { h = mix(h, 5); },
|
||||
.Int(x) => {
|
||||
h = mix(h, 6);
|
||||
h = mix(h, x.bits);
|
||||
h = mix(h, if x.is_signed { 1 } else { 0 });
|
||||
},
|
||||
.Float(x) => {
|
||||
h = mix(h, 7);
|
||||
h = mix(h, x.bits);
|
||||
},
|
||||
.Ptr(x) => {
|
||||
h = mix(h, 8);
|
||||
h = mix(h, x.pointee);
|
||||
},
|
||||
.Array(x) => {
|
||||
h = mix(h, 9);
|
||||
h = mix(h, x.element);
|
||||
h = mix(h, x.len);
|
||||
},
|
||||
.Struct(x) => { h = hash_agg(mix(h, 10), x); },
|
||||
.Union(x) => { h = hash_agg(mix(h, 11), x); },
|
||||
.Enum(x) => { h = hash_agg(mix(h, 12), x); },
|
||||
.Fn(x) => {
|
||||
h = mix(h, 13);
|
||||
h = mix(h, x.ret_type);
|
||||
h = mix(h, x.params.len);
|
||||
var i: usize = 0;
|
||||
while i < x.params.len {
|
||||
h = mix(h, usize_at(x.params, i));
|
||||
i = i + 1;
|
||||
}
|
||||
},
|
||||
.Forall => { h = mix(h, 14); },
|
||||
.Exists => { h = mix(h, 15); },
|
||||
.Shape(x) => { h = hash_agg(mix(h, 16), x); },
|
||||
}
|
||||
ret h;
|
||||
}
|
||||
|
||||
fn tag(n: *TypeNode): usize {
|
||||
match n.* {
|
||||
.Error => { ret 0; },
|
||||
.Never => { ret 1; },
|
||||
.Any => { ret 2; },
|
||||
.Unit => { ret 3; },
|
||||
.Infer => { ret 4; },
|
||||
.Bool => { ret 5; },
|
||||
.Int(x) => { ret 6; },
|
||||
.Float(x) => { ret 7; },
|
||||
.Ptr(x) => { ret 8; },
|
||||
.Array(x) => { ret 9; },
|
||||
.Struct(x) => { ret 10; },
|
||||
.Union(x) => { ret 11; },
|
||||
.Enum(x) => { ret 12; },
|
||||
.Fn(x) => { ret 13; },
|
||||
.Forall => { ret 14; },
|
||||
.Exists => { ret 15; },
|
||||
.Shape(x) => { ret 16; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn agg_eq(x: Agg, y: Agg): bool {
|
||||
if mode_code(x.mode) != mode_code(y.mode) { ret false; }
|
||||
if x.fixed_align_bits != y.fixed_align_bits { ret false; }
|
||||
if x.tag_type != y.tag_type { ret false; }
|
||||
if x.variants.len != y.variants.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < x.variants.len {
|
||||
var ma = member_at(&x.variants, i);
|
||||
var mb = member_at(&y.variants, i);
|
||||
if ma == null || mb == null { ret false; }
|
||||
if ma.type_id != mb.type_id { ret false; }
|
||||
if !ByteView.eq(ma.name, mb.name) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn node_eq(a: *TypeNode, b: *TypeNode): bool {
|
||||
if tag(a) != tag(b) { ret false; }
|
||||
match a.* {
|
||||
.Int(x) => {
|
||||
match b.* {
|
||||
.Int(y) => { ret x.bits == y.bits && x.is_signed == y.is_signed; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Float(x) => {
|
||||
match b.* {
|
||||
.Float(y) => { ret x.bits == y.bits; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Ptr(x) => {
|
||||
match b.* {
|
||||
.Ptr(y) => { ret x.pointee == y.pointee; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Array(x) => {
|
||||
match b.* {
|
||||
.Array(y) => { ret x.element == y.element && x.len == y.len; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Struct(x) => {
|
||||
match b.* {
|
||||
.Struct(y) => { ret agg_eq(x, y); },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Union(x) => {
|
||||
match b.* {
|
||||
.Union(y) => { ret agg_eq(x, y); },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Enum(x) => {
|
||||
match b.* {
|
||||
.Enum(y) => { ret agg_eq(x, y); },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Fn(x) => {
|
||||
match b.* {
|
||||
.Fn(y) => {
|
||||
if x.ret_type != y.ret_type { ret false; }
|
||||
if x.params.len != y.params.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < x.params.len {
|
||||
if usize_at(x.params, i) != usize_at(y.params, i) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
},
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Shape(x) => {
|
||||
match b.* {
|
||||
.Shape(y) => { ret agg_eq(x, y); },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
_ => { ret true; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// 释放节点内部拥有的 Vec(仅聚合变体与函数参数)。
|
||||
fn free_payload(n: *TypeNode) {
|
||||
match n.* {
|
||||
.Struct(x) => { Vec.drop(&x.variants); },
|
||||
.Union(x) => { Vec.drop(&x.variants); },
|
||||
.Enum(x) => { Vec.drop(&x.variants); },
|
||||
.Shape(x) => { Vec.drop(&x.variants); },
|
||||
.Fn(x) => { Vec.drop(&x.params); },
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 底层访问 ----
|
||||
|
||||
fn usize_at(v: Vec, i: usize): usize {
|
||||
match Vec.get(&v, i) {
|
||||
.Ok(ptr) => { ret (ptr as *usize).*; },
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn member_at(v: *Vec, i: usize): *Member {
|
||||
match Vec.get(v, i) {
|
||||
.Ok(ptr) => { ret ptr as *Member; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn push_member(v: *Vec, name: []u8, type_id: usize) {
|
||||
var m = Member {
|
||||
.name = ByteView.from_slice(name),
|
||||
.type_id = type_id,
|
||||
.ast_node_ref = 0,
|
||||
};
|
||||
match Vec.push(v, &m as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "push member"); },
|
||||
}
|
||||
}
|
||||
|
||||
// id 0 与越界均返回 null(对应 stage1 spl_type_node)。
|
||||
fn node(self: *TypeTable, id: usize): *TypeEntry {
|
||||
if id == 0 || id >= self.table.len { ret null; }
|
||||
match Vec.get(&self.table, id) {
|
||||
.Ok(ptr) => { ret ptr as *TypeEntry; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
// ---- 生命周期 ----
|
||||
|
||||
fn new(): TypeTableResult {
|
||||
var tt = TypeTable {
|
||||
.table = Vec.blank(@sizeof(TypeEntry), @alignof(TypeEntry)),
|
||||
.fold = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize),
|
||||
hash_usize, eq_usize),
|
||||
};
|
||||
var sentinel = TypeEntry { .hash_next = 0, .node = .Error };
|
||||
match Vec.push(&tt.table, &sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&tt.table);
|
||||
Map.drop(&tt.fold);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
ret .Ok(tt);
|
||||
}
|
||||
|
||||
fn drop(self: *TypeTable) {
|
||||
var i: usize = 0;
|
||||
while i < self.table.len {
|
||||
var n = node(self, i);
|
||||
if n != null {
|
||||
free_payload(&n.node);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.table);
|
||||
Map.drop(&self.fold);
|
||||
}
|
||||
|
||||
// 预留一个空 ERROR 槽位作为身份锚点(不写 fold、不参与 hash-consing)。
|
||||
// 用于自引用聚合:先拿到稳定 id,稍后用 define 填内容。
|
||||
fn reserve(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Error;
|
||||
match Vec.push(&self.table, &n as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret self.table.len - 1;
|
||||
}
|
||||
|
||||
// 把 src 的内容填进已 reserve 的槽位;src 载荷所有权转移,之后 src 失效。
|
||||
fn define(self: *TypeTable, id: usize, src: *TypeNode) {
|
||||
if id == 0 { ret; }
|
||||
var slot = node(self, id);
|
||||
if slot == null { ret; }
|
||||
slot.node = src.*;
|
||||
src.* = .Error;
|
||||
}
|
||||
|
||||
// ---- intern ----
|
||||
|
||||
fn fold_find(self: *TypeTable, hash: usize, probe: *TypeNode): usize {
|
||||
var head: usize = 0;
|
||||
match Map.get(&self.fold, &hash as *u8, &head as *u8) {
|
||||
.Found => {},
|
||||
.NotFound => { ret 0; },
|
||||
}
|
||||
var cur = head;
|
||||
while cur != 0 {
|
||||
var n = node(self, cur);
|
||||
if n == null { ret 0; }
|
||||
if node_eq(&n.node, probe) { ret cur; }
|
||||
cur = n.hash_next;
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn fold_insert(self: *TypeTable, hash: usize, id: usize) {
|
||||
var old: usize = 0;
|
||||
match Map.get(&self.fold, &hash as *u8, &old as *u8) {
|
||||
.Found => {},
|
||||
.NotFound => { old = 0; },
|
||||
}
|
||||
var n = node(self, id);
|
||||
if n == null { ret; }
|
||||
n.hash_next = old;
|
||||
match Map.put(&self.fold, &hash as *u8, &id as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
}
|
||||
|
||||
// intern 一个 probe:命中则释放 probe 载荷并返回已有 id;否则复制入表。
|
||||
// probe 在返回后失效(载荷所有权转移,probe 被置为 Error)。失败返回 0。
|
||||
fn push(self: *TypeTable, probe: *TypeNode): usize {
|
||||
var hash = hash_node(probe);
|
||||
var existing = fold_find(self, hash, probe);
|
||||
if existing != 0 {
|
||||
free_payload(probe);
|
||||
probe.* = .Error;
|
||||
ret existing;
|
||||
}
|
||||
var entry = TypeEntry { .hash_next = 0, .node = probe.* };
|
||||
probe.* = .Error; // 载荷所有权已转移给 entry
|
||||
var id = self.table.len;
|
||||
match Vec.push(&self.table, &entry as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
free_payload(&entry.node);
|
||||
ret 0;
|
||||
},
|
||||
}
|
||||
fold_insert(self, hash, id);
|
||||
ret id;
|
||||
}
|
||||
|
||||
// ---- builders ----
|
||||
|
||||
fn make_error(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Error;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_never(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Never;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_any(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Any;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_unit(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Unit;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_infer(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Infer;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_bool(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Bool;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_forall(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Forall;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_exists(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Exists;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_int(self: *TypeTable, bits: usize, is_signed: bool): usize {
|
||||
var n: TypeNode = .Int(.{ .bits = bits, .is_signed = is_signed });
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_float(self: *TypeTable, bits: usize): usize {
|
||||
var n: TypeNode = .Float(.{ .bits = bits });
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_ptr(self: *TypeTable, pointee: usize): usize {
|
||||
var n: TypeNode = .Ptr(.{ .pointee = pointee });
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_array(self: *TypeTable, element: usize, len: usize): usize {
|
||||
var n: TypeNode = .Array(.{ .element = element, .len = len });
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
// params 所有权转移进表;返回后调用方不得再 drop。
|
||||
fn make_fn(self: *TypeTable, ret_tid: usize, params: Vec): usize {
|
||||
var n: TypeNode = .Fn(.{ .params = params, .ret_type = ret_tid });
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
// agg 所有权转移进表;返回后调用方不得再 drop。
|
||||
fn make_agg(self: *TypeTable, kind: AggKind, agg: Agg): usize {
|
||||
var n: TypeNode = .Error;
|
||||
match kind {
|
||||
.Struct => { n = .Struct(agg); },
|
||||
.Union => { n = .Union(agg); },
|
||||
.Enum => { n = .Enum(agg); },
|
||||
.Shape => { n = .Shape(agg); },
|
||||
}
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
// ---- 查询 ----
|
||||
|
||||
fn is_subtype(self: *TypeTable, sub: usize, super: usize): bool {
|
||||
if sub == 0 || super == 0 { ret false; }
|
||||
if sub == super { ret true; }
|
||||
|
||||
var ns = node(self, sub);
|
||||
var np = node(self, super);
|
||||
if ns == null || np == null { ret false; }
|
||||
|
||||
match ns.node {
|
||||
.Never => { ret true; },
|
||||
_ => {},
|
||||
}
|
||||
match np.node {
|
||||
.Any => { ret true; },
|
||||
_ => {},
|
||||
}
|
||||
|
||||
match ns.node {
|
||||
.Int(x) => {
|
||||
match np.node {
|
||||
.Int(y) => {
|
||||
if x.bits == 0 { ret true; }
|
||||
if x.is_signed == y.is_signed { ret x.bits <= y.bits; }
|
||||
if !x.is_signed && y.is_signed { ret y.bits > x.bits; }
|
||||
ret false;
|
||||
},
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Float(x) => {
|
||||
match np.node {
|
||||
.Float(y) => {
|
||||
if x.bits == 0 { ret true; }
|
||||
ret x.bits <= y.bits;
|
||||
},
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn fn_param_tid(self: *TypeTable, fn_tid: usize, idx: usize): usize {
|
||||
var n = node(self, fn_tid);
|
||||
if n == null { ret 0; }
|
||||
match n.node {
|
||||
.Fn(x) => {
|
||||
if idx >= x.params.len { ret 0; }
|
||||
ret usize_at(x.params, idx);
|
||||
},
|
||||
_ => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn agg_find(agg: Agg, name: ByteView, out_idx: *usize): bool {
|
||||
var i: usize = 0;
|
||||
while i < agg.variants.len {
|
||||
var m = member_at(&agg.variants, i);
|
||||
if m != null && ByteView.eq(m.name, name) {
|
||||
if out_idx != null { out_idx.* = i; }
|
||||
ret true;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn agg_member_idx(self: *TypeTable, tid: usize, name: ByteView, out_idx: *usize): bool {
|
||||
var n = node(self, tid);
|
||||
if n == null { ret false; }
|
||||
match n.node {
|
||||
.Struct(x) => { ret agg_find(x, name, out_idx); },
|
||||
.Union(x) => { ret agg_find(x, name, out_idx); },
|
||||
.Enum(x) => { ret agg_find(x, name, out_idx); },
|
||||
.Shape(x) => { ret agg_find(x, name, out_idx); },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// ---- dump(镜像 stage1 spl_type_pure_dump)----
|
||||
|
||||
fn push_out(buf: *ByteBuffer, bytes: []u8): bool {
|
||||
match ByteBuffer.append(buf, bytes) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_usize(buf: *ByteBuffer, value: usize): bool {
|
||||
match append_usize(buf, value, 10, false) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn dump_rec(self: *TypeTable, id: usize, out: *ByteBuffer, stack: *Vec): bool {
|
||||
var i: usize = 0;
|
||||
while i < stack.len {
|
||||
if usize_at(stack.*, i) == id {
|
||||
if !push_out(out, "<rec#") { ret false; }
|
||||
if !push_usize(out, id) { ret false; }
|
||||
ret push_out(out, ">");
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
if stack.len > 32 {
|
||||
ret push_out(out, "...");
|
||||
}
|
||||
match Vec.push(stack, &id as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
|
||||
var n = node(self, id);
|
||||
if n == null {
|
||||
if !push_out(out, "none") { ret false; }
|
||||
var popped: usize = 0;
|
||||
match Vec.pop(stack, &popped as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
var ok = true;
|
||||
match n.node {
|
||||
.Error => { ok = push_out(out, "error"); },
|
||||
.Never => { ok = push_out(out, "never"); },
|
||||
.Any => { ok = push_out(out, "any"); },
|
||||
.Unit => { ok = push_out(out, "unit"); },
|
||||
.Infer => { ok = push_out(out, "infer"); },
|
||||
.Bool => { ok = push_out(out, "bool"); },
|
||||
.Forall => { ok = push_out(out, "quant"); },
|
||||
.Exists => { ok = push_out(out, "quant"); },
|
||||
.Int(x) => {
|
||||
if x.bits == 0 {
|
||||
ok = push_out(out, "comptime_int");
|
||||
} else {
|
||||
if !push_out(out, if x.is_signed { "i" } else { "u" }) { ok = false; }
|
||||
if ok && !push_usize(out, x.bits) { ok = false; }
|
||||
}
|
||||
},
|
||||
.Float(x) => {
|
||||
if x.bits == 0 {
|
||||
ok = push_out(out, "comptime_float");
|
||||
} else {
|
||||
if !push_out(out, "f") { ok = false; }
|
||||
if ok && !push_usize(out, x.bits) { ok = false; }
|
||||
}
|
||||
},
|
||||
.Ptr(x) => {
|
||||
if !push_out(out, "*") { ok = false; }
|
||||
if ok { ok = dump_rec(self, x.pointee, out, stack); }
|
||||
},
|
||||
.Array(x) => {
|
||||
if !push_out(out, "[") { ok = false; }
|
||||
if ok && !push_usize(out, x.len) { ok = false; }
|
||||
if ok && !push_out(out, "]") { ok = false; }
|
||||
if ok { ok = dump_rec(self, x.element, out, stack); }
|
||||
},
|
||||
.Struct(x) => { ok = dump_agg(self, "struct", x, out, stack); },
|
||||
.Union(x) => { ok = dump_agg(self, "union", x, out, stack); },
|
||||
.Enum(x) => { ok = dump_agg(self, "enum", x, out, stack); },
|
||||
.Shape(x) => { ok = dump_agg(self, "shape", x, out, stack); },
|
||||
.Fn(x) => {
|
||||
if !push_out(out, "fn(") { ok = false; }
|
||||
var j: usize = 0;
|
||||
while ok && j < x.params.len {
|
||||
if j != 0 && !push_out(out, ",") { ok = false; }
|
||||
if ok { ok = dump_rec(self, usize_at(x.params, j), out, stack); }
|
||||
j = j + 1;
|
||||
}
|
||||
if ok && !push_out(out, ")") { ok = false; }
|
||||
if ok { ok = dump_rec(self, x.ret_type, out, stack); }
|
||||
},
|
||||
}
|
||||
|
||||
var dropped: usize = 0;
|
||||
match Vec.pop(stack, &dropped as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
ret ok;
|
||||
}
|
||||
|
||||
fn dump_agg(self: *TypeTable, kw: []u8, agg: Agg, out: *ByteBuffer, stack: *Vec): bool {
|
||||
if !push_out(out, kw) { ret false; }
|
||||
if !push_out(out, "{") { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < agg.variants.len {
|
||||
if i != 0 && !push_out(out, ", ") { ret false; }
|
||||
var m = member_at(&agg.variants, i);
|
||||
if m == null { ret false; }
|
||||
if m.name.len == 0 {
|
||||
if !push_out(out, "?") { ret false; }
|
||||
} else {
|
||||
if !push_out(out, ByteView.as_slice(m.name)) { ret false; }
|
||||
}
|
||||
if !push_out(out, ":") { ret false; }
|
||||
if !dump_rec(self, m.type_id, out, stack) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret push_out(out, "}");
|
||||
}
|
||||
|
||||
fn pure_dump(self: *TypeTable, id: usize, out: *ByteBuffer): bool {
|
||||
var stack = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
defer { Vec.drop(&stack); }
|
||||
ret dump_rec(self, id, out, &stack);
|
||||
}
|
||||
|
||||
fn pure_dump_text(self: *TypeTable, id: usize): ByteBuffer {
|
||||
var out = ByteBuffer.new();
|
||||
if !pure_dump(self, id, &out) {
|
||||
ByteBuffer.clear(&out);
|
||||
}
|
||||
ret out;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_intern_simple() {
|
||||
var tt: TypeTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { tt = v; },
|
||||
.Err(error) => { @assert(false, "type table new failed"); },
|
||||
}
|
||||
defer { drop(&tt); }
|
||||
|
||||
var i32a = make_int(&tt, 32, true);
|
||||
var i32b = make_int(&tt, 32, true);
|
||||
var u32 = make_int(&tt, 32, false);
|
||||
@assert(i32a != 0 && i32a == i32b, "i32 interned");
|
||||
@assert(u32 != i32a, "u32 distinct");
|
||||
|
||||
var p1 = make_ptr(&tt, i32a);
|
||||
var p2 = make_ptr(&tt, i32a);
|
||||
@assert(p1 == p2, "ptr interned");
|
||||
@assert(make_ptr(&tt, u32) != p1, "ptr pointee distinct");
|
||||
|
||||
var a1 = make_array(&tt, i32a, 3);
|
||||
var a2 = make_array(&tt, i32a, 3);
|
||||
var a3 = make_array(&tt, i32a, 4);
|
||||
@assert(a1 == a2 && a1 != a3, "array interned");
|
||||
|
||||
var params = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
match Vec.push(¶ms, &i32a as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "push param"); },
|
||||
}
|
||||
var f1 = make_fn(&tt, u32, params);
|
||||
var params2 = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
match Vec.push(¶ms2, &i32a as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "push param"); },
|
||||
}
|
||||
var f2 = make_fn(&tt, u32, params2);
|
||||
@assert(f1 == f2, "fn interned");
|
||||
}
|
||||
|
||||
fn test_agg_intern_and_member() {
|
||||
var tt: TypeTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { tt = v; },
|
||||
.Err(error) => { @assert(false, "type table new failed"); },
|
||||
}
|
||||
defer { drop(&tt); }
|
||||
|
||||
var u8t = make_int(&tt, 8, false);
|
||||
var i32t = make_int(&tt, 32, true);
|
||||
|
||||
var m1 = Vec.blank(@sizeof(Member), @alignof(Member));
|
||||
push_member(&m1, "a", u8t);
|
||||
push_member(&m1, "b", i32t);
|
||||
|
||||
var s1 = make_agg(&tt, .Struct, .{
|
||||
.mode = .Auto,
|
||||
.fixed_align_bits = 0,
|
||||
.tag_type = 0,
|
||||
.variants = m1,
|
||||
});
|
||||
|
||||
var m2 = Vec.blank(@sizeof(Member), @alignof(Member));
|
||||
push_member(&m2, "a", u8t);
|
||||
push_member(&m2, "b", i32t);
|
||||
var s2 = make_agg(&tt, .Struct, .{
|
||||
.mode = .Auto,
|
||||
.fixed_align_bits = 0,
|
||||
.tag_type = 0,
|
||||
.variants = m2,
|
||||
});
|
||||
@assert(s1 != 0, "s1 nonzero");
|
||||
@assert(s2 != 0, "s2 nonzero");
|
||||
@assert(pure_dump_str(&tt, s1, "struct{a:u8, b:i32}"), "s1 shape");
|
||||
@assert(pure_dump_str(&tt, s2, "struct{a:u8, b:i32}"), "s2 shape");
|
||||
@assert(s1 == s2, "struct interned");
|
||||
|
||||
var idx: usize = 0;
|
||||
@assert(agg_member_idx(&tt, s1, ByteView.from_slice("b"), &idx), "find b");
|
||||
@assert(idx == 1, "b index");
|
||||
@assert(!agg_member_idx(&tt, s1, ByteView.from_slice("z"), &idx), "missing z");
|
||||
}
|
||||
|
||||
fn test_subtype() {
|
||||
var tt: TypeTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { tt = v; },
|
||||
.Err(error) => { @assert(false, "type table new failed"); },
|
||||
}
|
||||
defer { drop(&tt); }
|
||||
|
||||
var i8t = make_int(&tt, 8, true);
|
||||
var i32t = make_int(&tt, 32, true);
|
||||
var u32t = make_int(&tt, 32, false);
|
||||
var u64t = make_int(&tt, 64, false);
|
||||
var ci = make_int(&tt, 0, true);
|
||||
var never = make_never(&tt);
|
||||
var anyt = make_any(&tt);
|
||||
var f32t = make_float(&tt, 32);
|
||||
var f64t = make_float(&tt, 64);
|
||||
|
||||
@assert(is_subtype(&tt, i8t, i32t), "i8 <= i32");
|
||||
@assert(!is_subtype(&tt, i32t, i8t), "i32 !<= i8");
|
||||
@assert(!is_subtype(&tt, i32t, u32t), "i32 !<= u32");
|
||||
@assert(is_subtype(&tt, u32t, u64t), "u32 <= u64");
|
||||
@assert(!is_subtype(&tt, u32t, i32t), "u32 !<= i32");
|
||||
@assert(is_subtype(&tt, u32t, make_int(&tt, 64, true)), "u32 <= i64");
|
||||
@assert(is_subtype(&tt, ci, i32t), "comptime_int <= any int");
|
||||
@assert(is_subtype(&tt, never, i32t), "never <= all");
|
||||
@assert(is_subtype(&tt, i32t, anyt), "all <= any");
|
||||
@assert(is_subtype(&tt, f32t, f64t), "f32 <= f64");
|
||||
@assert(!is_subtype(&tt, f64t, f32t), "f64 !<= f32");
|
||||
}
|
||||
|
||||
fn test_dump() {
|
||||
var tt: TypeTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { tt = v; },
|
||||
.Err(error) => { @assert(false, "type table new failed"); },
|
||||
}
|
||||
defer { drop(&tt); }
|
||||
|
||||
@assert(pure_dump_str(&tt, make_int(&tt, 32, true), "i32"), "dump i32");
|
||||
@assert(pure_dump_str(&tt, make_int(&tt, 0, true), "comptime_int"), "dump comptime_int");
|
||||
@assert(pure_dump_str(&tt, make_float(&tt, 64), "f64"), "dump f64");
|
||||
@assert(pure_dump_str(&tt, make_ptr(&tt, 0), "*none"), "dump ptr none");
|
||||
|
||||
var u8t = make_int(&tt, 8, false);
|
||||
@assert(pure_dump_str(&tt, make_array(&tt, u8t, 3), "[3]u8"), "dump array");
|
||||
|
||||
var params = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
match Vec.push(¶ms, &u8t as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "push param"); },
|
||||
}
|
||||
var fn_t = make_fn(&tt, 0, params);
|
||||
@assert(pure_dump_str(&tt, fn_t, "fn(u8)none"), "dump fn");
|
||||
|
||||
var members = Vec.blank(@sizeof(Member), @alignof(Member));
|
||||
push_member(&members, "x", u8t);
|
||||
var st = make_agg(&tt, .Struct, .{
|
||||
.mode = .Auto,
|
||||
.fixed_align_bits = 0,
|
||||
.tag_type = 0,
|
||||
.variants = members,
|
||||
});
|
||||
@assert(pure_dump_str(&tt, st, "struct{x:u8}"), "dump struct");
|
||||
}
|
||||
|
||||
fn pure_dump_str(tt: *TypeTable, id: usize, expected: []u8): bool {
|
||||
var out = pure_dump_text(tt, id);
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
var got = ByteBuffer.as_slice(out);
|
||||
if got.len != expected.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
if got.ptr[i] != expected.ptr[i] { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_intern_simple();
|
||||
test_agg_intern_and_member();
|
||||
test_subtype();
|
||||
test_dump();
|
||||
}
|
||||
@@ -24,6 +24,12 @@ fn new(): ByteBuffer {
|
||||
ret ByteBuffer { .storage = Vec.blank(1, 1) };
|
||||
}
|
||||
|
||||
fn with_capacity(cap: usize): ByteBuffer {
|
||||
var b = new();
|
||||
match reserve(&b, cap) { .Ok => {}, .Err(error) => {} }
|
||||
ret b;
|
||||
}
|
||||
|
||||
fn size(self: ByteBuffer): usize {
|
||||
ret self.storage.len;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
|
||||
#extern(vm) fn vm_memcpy(dst: *any, src: *any, n: usize): *any;
|
||||
|
||||
fn ignore_ptr(p: *any) {}
|
||||
|
||||
type SizeResult = enum {
|
||||
Ok: usize,
|
||||
Err: Error,
|
||||
@@ -69,6 +73,18 @@ fn move(dst: []u8, src: []u8): Status {
|
||||
}
|
||||
if dst_addr == src_addr { ret .Ok; }
|
||||
|
||||
// 非重叠且较大时走原生 memcpy(Vec 扩容复制是常见场景)。
|
||||
var non_overlap = false;
|
||||
if dst_addr < src_addr {
|
||||
if src_addr - dst_addr >= count { non_overlap = true; }
|
||||
} else {
|
||||
if dst_addr - src_addr >= count { non_overlap = true; }
|
||||
}
|
||||
if non_overlap && count >= 16 {
|
||||
ignore_ptr(vm_memcpy(dst.ptr as *any, src.ptr as *any, count));
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
if dst_addr < src_addr {
|
||||
var i: usize = 0;
|
||||
while i < count {
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 全量热点/耗时记录器(配合 @file_name()/@line()/@col() 与 sys/time.spl)。
|
||||
// 每个调用点聚合:count、独占(self)与包含(total)的 steps/ns。
|
||||
// - total_* 含被调用者(嵌套会重复,跨站点求和会超过墙钟)
|
||||
// - self_* = total_* - 子区间之和,用于定位真正的热点
|
||||
// 用法(严格 LIFO,defer 正好满足):
|
||||
// var tok = Profile.begin(&prof, @file_name(), @line(), @col());
|
||||
// defer { Profile.end(&prof, tok); }
|
||||
|
||||
type .{ .Error } = @import("error.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
type .{ .append_usize } = @import("fmt.spl");
|
||||
type .{ .open_cstr, .write_all } = @import("io.spl");
|
||||
type File = @import("../sys/file.spl");
|
||||
type Time = @import("../sys/time.spl");
|
||||
|
||||
type Site = struct {
|
||||
fname: ByteView,
|
||||
line: usize,
|
||||
col: usize,
|
||||
count: usize,
|
||||
self_steps: usize,
|
||||
total_steps: usize,
|
||||
self_ns: usize,
|
||||
total_ns: usize,
|
||||
}
|
||||
|
||||
type SpanRec = struct {
|
||||
site: usize,
|
||||
t0_ns: usize,
|
||||
t0_steps: usize,
|
||||
child_steps: usize,
|
||||
child_ns: usize,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
type Profile = @self;
|
||||
|
||||
sites: Vec, // Site
|
||||
spans: Vec, // SpanRec
|
||||
stack: Vec, // usize(活动 span 的 token,LIFO)
|
||||
has_time: bool,
|
||||
|
||||
fn new(): Profile {
|
||||
var has = false;
|
||||
if Time.available() != 0 { has = true; }
|
||||
ret Profile {
|
||||
.sites = Vec.blank(@sizeof(Site), @alignof(Site)),
|
||||
.spans = Vec.blank(@sizeof(SpanRec), @alignof(SpanRec)),
|
||||
.stack = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.has_time = has,
|
||||
};
|
||||
}
|
||||
|
||||
fn drop(self: *Profile) {
|
||||
Vec.drop(&self.stack);
|
||||
Vec.drop(&self.spans);
|
||||
Vec.drop(&self.sites);
|
||||
}
|
||||
|
||||
fn site_at(self: *Profile, i: usize): *Site {
|
||||
match Vec.get(&self.sites, i) {
|
||||
.Ok(ptr) => { ret ptr as *Site; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn span_at(self: *Profile, i: usize): *SpanRec {
|
||||
match Vec.get(&self.spans, i) {
|
||||
.Ok(ptr) => { ret ptr as *SpanRec; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn usize_at(v: *Vec, i: usize): usize {
|
||||
match Vec.get(v, i) {
|
||||
.Ok(ptr) => { ret (ptr as *usize).*; },
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn find_site(self: *Profile, fname: []u8, line: usize, col: usize): usize {
|
||||
var fv = ByteView.from_slice(fname);
|
||||
var i: usize = 0;
|
||||
while i < self.sites.len {
|
||||
var s = site_at(self, i);
|
||||
if s != null && s.line == line && s.col == col && ByteView.eq(s.fname, fv) {
|
||||
ret i;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
var fresh = Site {
|
||||
.fname = fv,
|
||||
.line = line,
|
||||
.col = col,
|
||||
.count = 0,
|
||||
.self_steps = 0,
|
||||
.total_steps = 0,
|
||||
.self_ns = 0,
|
||||
.total_ns = 0,
|
||||
};
|
||||
match Vec.push(&self.sites, &fresh as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret self.sites.len - 1;
|
||||
}
|
||||
|
||||
// 仅计数(轻量点)。
|
||||
fn hit(self: *Profile, fname: []u8, line: usize, col: usize) {
|
||||
var site = find_site(self, fname, line, col);
|
||||
var s = site_at(self, site);
|
||||
if s != null { s.count = s.count + 1; }
|
||||
}
|
||||
|
||||
fn begin(self: *Profile, fname: []u8, line: usize, col: usize): usize {
|
||||
var site = find_site(self, fname, line, col);
|
||||
var s = site_at(self, site);
|
||||
if s != null { s.count = s.count + 1; }
|
||||
|
||||
var t_ns: usize = 0;
|
||||
if self.has_time { t_ns = Time.now_ns(); }
|
||||
var rec = SpanRec {
|
||||
.site = site,
|
||||
.t0_ns = t_ns,
|
||||
.t0_steps = Time.steps(),
|
||||
.child_steps = 0,
|
||||
.child_ns = 0,
|
||||
.active = true,
|
||||
};
|
||||
match Vec.push(&self.spans, &rec as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
var token = self.spans.len - 1;
|
||||
match Vec.push(&self.stack, &token as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
ret token;
|
||||
}
|
||||
|
||||
fn end(self: *Profile, token: usize) {
|
||||
var rec = span_at(self, token);
|
||||
if rec == null || !rec.active { ret; }
|
||||
|
||||
var e_steps = Time.steps() - rec.t0_steps;
|
||||
var e_ns: usize = 0;
|
||||
if self.has_time { e_ns = Time.now_ns() - rec.t0_ns; }
|
||||
|
||||
var s = site_at(self, rec.site);
|
||||
if s != null {
|
||||
s.total_steps = s.total_steps + e_steps;
|
||||
s.self_steps = s.self_steps + (e_steps - rec.child_steps);
|
||||
if self.has_time {
|
||||
s.total_ns = s.total_ns + e_ns;
|
||||
s.self_ns = s.self_ns + (e_ns - rec.child_ns);
|
||||
}
|
||||
}
|
||||
rec.active = false;
|
||||
|
||||
// 弹栈并把区间计入父 span(LIFO)。
|
||||
var popped: usize = 0;
|
||||
match Vec.pop(&self.stack, &popped as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
var n = self.stack.len;
|
||||
if n > 0 {
|
||||
var p = span_at(self, usize_at(&self.stack, n - 1));
|
||||
if p != null {
|
||||
p.child_steps = p.child_steps + e_steps;
|
||||
p.child_ns = p.child_ns + e_ns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- dump / save ----
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 列:count self_steps total_steps self_ns total_ns avg_self location;按 self_steps 降序。
|
||||
fn dump(self: *Profile, out: *ByteBuffer): bool {
|
||||
if !out_s(out, "profile sites=") { ret false; }
|
||||
if !out_u(out, self.sites.len) { ret false; }
|
||||
if !out_s(out, " has_time=") { ret false; }
|
||||
if !out_u(out, if self.has_time { 1 } else { 0 }) { ret false; }
|
||||
if !out_s(out, "\n") { ret false; }
|
||||
if !out_s(out, "count\tself_steps\ttotal_steps\tself_ns\ttotal_ns\tavg_self\tlocation\n") {
|
||||
ret false;
|
||||
}
|
||||
|
||||
var idx = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
defer { Vec.drop(&idx); }
|
||||
var i: usize = 0;
|
||||
while i < self.sites.len {
|
||||
match Vec.push(&idx, &i as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 1;
|
||||
while i < idx.len {
|
||||
var j = i;
|
||||
while j > 0 {
|
||||
var a = site_at(self, usize_at(&idx, j - 1));
|
||||
var b = site_at(self, usize_at(&idx, j));
|
||||
var swap = false;
|
||||
if a != null && b != null {
|
||||
if b.self_steps > a.self_steps { swap = true; }
|
||||
}
|
||||
if !swap { break; }
|
||||
var tmp = usize_at(&idx, j - 1);
|
||||
match Vec.get(&idx, j - 1) { .Ok(p) => { (p as *usize).* = usize_at(&idx, j); }, .Err(e) => {} }
|
||||
match Vec.get(&idx, j) { .Ok(p) => { (p as *usize).* = tmp; }, .Err(e) => {} }
|
||||
j = j - 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < idx.len {
|
||||
var s = site_at(self, usize_at(&idx, i));
|
||||
if s != null {
|
||||
if !out_u(out, s.count) { ret false; }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if !out_u(out, s.self_steps) { ret false; }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if !out_u(out, s.total_steps) { ret false; }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if !out_u(out, s.self_ns) { ret false; }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if !out_u(out, s.total_ns) { ret false; }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if s.count != 0 { if !out_u(out, s.self_steps / s.count) { ret false; } }
|
||||
else { if !out_s(out, "0") { ret false; } }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if !out_s(out, ByteView.as_slice(s.fname)) { ret false; }
|
||||
if !out_s(out, ":") { ret false; }
|
||||
if !out_u(out, s.line) { ret false; }
|
||||
if !out_s(out, ":") { ret false; }
|
||||
if !out_u(out, s.col) { ret false; }
|
||||
if !out_s(out, "\n") { ret false; }
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn save(self: *Profile, path: ByteView): bool {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !dump(self, &out) { ret false; }
|
||||
|
||||
var f = open_cstr(path, ByteView.from_slice("wb"));
|
||||
if !File.valid(&f) { ret false; }
|
||||
var w = write_all(&f, ByteBuffer.as_slice(out));
|
||||
var closed = File.close(&f);
|
||||
match w {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
if closed != 0 { ret false; }
|
||||
ret true;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn spin(n: usize): usize {
|
||||
var i: usize = 0;
|
||||
var s: usize = 0;
|
||||
while i < n { s = s + i; i = i + 1; }
|
||||
ret s;
|
||||
}
|
||||
|
||||
fn test_profile() {
|
||||
var p = new();
|
||||
defer { drop(&p); }
|
||||
|
||||
var tok = begin(&p, @file_name(), @line(), @col());
|
||||
var v = spin(1000);
|
||||
end(&p, tok);
|
||||
|
||||
@assert(p.sites.len >= 1, "sites");
|
||||
var s = site_at(&p, 0);
|
||||
@assert(s != null && s.count >= 1, "count");
|
||||
@assert(s.total_steps > 0, "steps");
|
||||
@assert(s.self_steps > 0, "self steps");
|
||||
if v == 0 { ignore_usize(v + 1); }
|
||||
}
|
||||
|
||||
fn ignore_usize(v: usize) {}
|
||||
|
||||
fn main() {
|
||||
test_profile();
|
||||
}
|
||||
+39
-12
@@ -49,14 +49,17 @@ fn new(elem_size: usize, elem_align: usize): VecResult {
|
||||
ret .Ok(blank(elem_size, elem_align));
|
||||
}
|
||||
|
||||
fn valid(self: Vec): bool {
|
||||
if self.elem_size == 0 || !valid_alignment(self.elem_align) { ret false; }
|
||||
// 按指针接收,避免每次调用复制整个 Vec;内联对齐检查,省一次函数调用。
|
||||
fn valid(self: *Vec): bool {
|
||||
if self.elem_size == 0 { ret false; }
|
||||
var a = self.elem_align;
|
||||
if a == 0 || (a & (a - 1)) != 0 { ret false; }
|
||||
if self.len > self.cap { ret false; }
|
||||
if self.cap == 0 {
|
||||
ret self.raw == null && self.data == null && self.len == 0;
|
||||
}
|
||||
if self.raw == null || self.data == null { ret false; }
|
||||
ret (self.data as usize & (self.elem_align - 1)) == 0;
|
||||
ret (self.data as usize & (a - 1)) == 0;
|
||||
}
|
||||
|
||||
fn status_error(kind: Kind): Status {
|
||||
@@ -108,9 +111,8 @@ fn allocate(capacity: usize, elem_size: usize, elem_align: usize): VecResult {
|
||||
});
|
||||
}
|
||||
|
||||
// 保证至少可容纳 need 个元素。所有检查在原对象改变前完成。
|
||||
fn reserve(self: *Vec, need: usize): Status {
|
||||
if !valid(self.*) { ret status_error(.InvalidArgument); }
|
||||
// 与 reserve 相同,但假定调用方已校验 self(内部热路径用,省去重复 valid)。
|
||||
fn reserve_raw(self: *Vec, need: usize): Status {
|
||||
if need <= self.cap { ret .Ok; }
|
||||
|
||||
var new_cap = grow_capacity(self.cap, 8, need);
|
||||
@@ -140,14 +142,25 @@ fn reserve(self: *Vec, need: usize): Status {
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 保证至少可容纳 need 个元素(外部入口,含校验)。
|
||||
fn reserve(self: *Vec, need: usize): Status {
|
||||
if !valid(self) { ret status_error(.InvalidArgument); }
|
||||
ret reserve_raw(self, need);
|
||||
}
|
||||
|
||||
// 预分配恰好 capacity 个元素(等价“with_capacity”)。
|
||||
fn with_capacity(elem_size: usize, elem_align: usize, capacity: usize): VecResult {
|
||||
ret allocate(capacity, elem_size, elem_align);
|
||||
}
|
||||
|
||||
fn get(self: *Vec, index: usize): PtrResult {
|
||||
if !valid(self.*) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if !valid(self) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if index >= self.len { ret .Err(Error.make(.OutOfRange)); }
|
||||
ret .Ok(self.data + index * self.elem_size);
|
||||
}
|
||||
|
||||
fn set(self: *Vec, index: usize, elem: *u8): Status {
|
||||
if !valid(self.*) || elem == null { ret status_error(.InvalidArgument); }
|
||||
if !valid(self) || elem == null { ret status_error(.InvalidArgument); }
|
||||
if index >= self.len { ret status_error(.OutOfRange); }
|
||||
var dst = self.data + index * self.elem_size;
|
||||
ret move(dst[0..self.elem_size], elem[0..self.elem_size]);
|
||||
@@ -155,7 +168,7 @@ fn set(self: *Vec, index: usize, elem: *u8): Status {
|
||||
|
||||
// 追加 count 个连续元素。elems 可以指向 self 当前已初始化的元素区。
|
||||
fn append(self: *Vec, elems: *u8, count: usize): Status {
|
||||
if !valid(self.*) { ret status_error(.InvalidArgument); }
|
||||
if !valid(self) { ret status_error(.InvalidArgument); }
|
||||
if count == 0 { ret .Ok; }
|
||||
if elems == null { ret status_error(.InvalidArgument); }
|
||||
|
||||
@@ -190,7 +203,7 @@ fn append(self: *Vec, elems: *u8, count: usize): Status {
|
||||
}
|
||||
}
|
||||
|
||||
match reserve(self, new_len) {
|
||||
match reserve_raw(self, new_len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
@@ -209,9 +222,23 @@ fn push(self: *Vec, elem: *u8): Status {
|
||||
ret append(self, elem, 1);
|
||||
}
|
||||
|
||||
// 热路径:假定 self 有效且已有容量时直接写入;容量不足才回退到 append。
|
||||
fn push_raw(self: *Vec, elem: *u8): bool {
|
||||
if self.len < self.cap {
|
||||
copy(self.data + self.len * self.elem_size, elem, self.elem_size);
|
||||
self.len = self.len + 1;
|
||||
ret true;
|
||||
}
|
||||
match append(self, elem, 1) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// 将末元素复制到 out,然后缩短数组。out 可以与元素区重叠。
|
||||
fn pop(self: *Vec, out: *u8): Status {
|
||||
if !valid(self.*) || out == null { ret status_error(.InvalidArgument); }
|
||||
if !valid(self) || out == null { ret status_error(.InvalidArgument); }
|
||||
if self.len == 0 { ret status_error(.OutOfRange); }
|
||||
var source = self.data + (self.len - 1) * self.elem_size;
|
||||
match move(out[0..self.elem_size], source[0..self.elem_size]) {
|
||||
@@ -229,7 +256,7 @@ fn clear(self: *Vec) {
|
||||
// 成功时先释放 out 原有存储再提交副本;失败保持 out 不变。
|
||||
// out 与 src 指向同一个 Vec 也允许。
|
||||
fn clone(out: *Vec, src: Vec): Status {
|
||||
if !valid(out.*) || !valid(src) { ret status_error(.InvalidArgument); }
|
||||
if !valid(out) || !valid(&src) { ret status_error(.InvalidArgument); }
|
||||
if out.elem_size != src.elem_size || out.elem_align != src.elem_align {
|
||||
ret status_error(.InvalidArgument);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user