Files
spl/stage2/attr.spl
T

424 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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, &reg_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; }
ret (self.table.data as *AttrNode) + id;
}
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));
registry_add(self, make_info("", "export", .Ir, .Decl, .Optional, 1, 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 == 8, "1 sentinel + 7 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();
}