329 lines
11 KiB
Plaintext
329 lines
11 KiB
Plaintext
// 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] == '@' {
|
||
ret ByteView { .ptr = name.ptr + 1, .len = name.len - 1 };
|
||
}
|
||
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();
|
||
}
|