Files
spl/stage2/intrin.spl
T

329 lines
11 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
// 内建(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();
}