327 lines
11 KiB
Plaintext
327 lines
11 KiB
Plaintext
// Expect retcode = 0
|
|
|
|
// 内建注册表:按 Intrin 查描述信息,按名字/字段/`as` 规则反查 Intrin。新增内建只改
|
|
// 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);
|
|
}
|
|
|
|
// 枚举顺序即 kind 数值(与 splc0 表一致)。
|
|
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();
|
|
}
|