Compare commits

..
41 Commits
Author SHA1 Message Date
zzy fc2d8f8db4 stage2 修复语义问题 2026-10-08 22:46:31 +08:00
zzy 76f980654c stage2 修复大量AI生成的风格问题 stage1 修复对于vm abi的错误生成 2026-10-03 15:05:55 +08:00
zzy 0e38858785 stage2 支持左值的引用绑定 2026-09-29 23:00:24 +08:00
zzy 40cffa9cb5 stage0 新增内存复制指令 重构vm交互 2026-09-29 21:11:15 +08:00
zzy 0b8f649818 stage2 修复全局变量未完成的问题 提供错误处理解析 2026-09-25 13:46:07 +08:00
zzy c0a86ea552 stage0 重构vm使其依赖自己的地址空间不再共用宿主机 修改vm的ABI 修正相关nob构建问题 2026-09-22 23:12:15 +08:00
zzy fae017b52d stage2 解决类型重复导入的问题 2026-09-20 18:35:21 +08:00
zzy 9ecb92cb03 stage2 迁移nob 修复nob的问题 继续修复类型导入问题 2026-09-20 18:24:25 +08:00
zzy 70085355db stage2 解决类型重复导入的问题 2026-09-20 18:07:47 +08:00
zzy d10578d267 stage2 解决类型重复导入的问题 2026-09-20 18:03:56 +08:00
zzy 51d408d524 stage2 提供c后端 提交stage1的测试 2026-09-20 17:56:11 +08:00
zzy 846f441e3a stage2 大量优化代码使其自举编译时间大幅缩短 2026-09-20 09:33:20 +08:00
zzy e02b0c633a stage2 提供近乎完整的功能但出现严重性能问题 添加stage0的原生性能记录 stage1添加更多内置函数 nob重构代码使其更加健壮 2026-09-19 18:08:43 +08:00
zzy b5a7a811b3 stage2 迁移ast解析器 stage1 防止错误死循环 2026-09-18 22:32:57 +08:00
zzy bb9d0a9f18 stage2 提供词法分析器通过与stage1的差异测试 stage1提供数组到切片的隐式转换 2026-09-18 19:40:10 +08:00
zzy c30143609a stage2 更多的工具库 2026-09-18 11:17:33 +08:00
zzy b3e38ba7e4 stage2 更多的工具库 stage1 修复问题 2026-09-18 10:10:53 +08:00
zzy 16a1f030d4 stage2 更多的工具库 2026-09-17 22:32:36 +08:00
zzy 4141e7d574 stage2 更多底层函数 stage1 layout正确使用 内置函数重新编写 2026-09-17 22:03:23 +08:00
zzy fb0ff9b663 stage1 解决重构哈希 nob优化支持头文件缓存以及win下exe缓存 2026-09-17 20:55:21 +08:00
zzy 70ac7380e9 stage1 支持函数指针 2026-09-17 20:13:24 +08:00
zzy 73c9a40abd stage1 实现短路逻辑 2026-09-17 19:37:33 +08:00
zzy b8e2bba140 stage2 更多的工具函数 stage1支持更多的内置函数 2026-09-17 18:47:56 +08:00
zzy 1a6df012cd stage2 提供sys包装 解决stage1的bug 2026-09-17 17:25:55 +08:00
zzy 5de4937637 stage1 解决defer实现的缺陷 2026-09-17 16:44:50 +08:00
zzy 2594a81034 stage1 解决@self无法在文件内直接使用的问题 2026-09-17 16:30:38 +08:00
zzy acfbbeee8b stage1 支持更多语法 修复更多问题 暂时stage1功能结束 2026-09-17 13:37:59 +08:00
zzy 54425a50c7 stage1 重构代码 语法上重新实现解包语法 2026-09-17 00:33:03 +08:00
zzy 2a84b61e3d stage1 大量重构 2026-09-16 20:09:24 +08:00
zzy e4347cc3ea stage1 重写进行代码设计并重构 提供diag替换老旧错误处理 2026-09-16 15:21:59 +08:00
zzy 7cbe2d2b9a stage1 重制类型转换系统 修复问题 提供更多表达式 2026-09-16 11:36:29 +08:00
zzy f6a0bce9f0 stage1 vm abi更新 修复更多问题 2026-09-16 10:35:36 +08:00
zzy 09d05e3a34 stage1 指针计算类型的错误修复 as运算符优先级的修改 ir2vm的bug修复 2026-09-15 21:24:58 +08:00
zzy 06f3884c30 stage1 支持指针运算 提供新的类型检查计算 2026-09-15 19:11:01 +08:00
zzy be41a82d55 stage1 支持defer 支持null 支持指针运算 支持*any指针 2026-09-15 11:08:44 +08:00
zzy 983746c797 stage1 支持label break, continue 2026-09-14 19:35:58 +08:00
zzy 3175e04e6e stage1 支持tuple 修复悬垂指针问题 2026-09-14 19:01:33 +08:00
zzy a621e34d51 stage1 支持任意顺序声明 使用eval求值的机制 2026-09-14 18:11:58 +08:00
zzy a1e684f6d4 stage0 修复部分问题 2026-09-14 16:01:33 +08:00
zzy 5073c2b3ea stage1 支持聚合类型方法 2026-09-14 15:36:43 +08:00
zzy 9bdb7f892e stage1 支持结构体初始化 2026-09-14 10:09:32 +08:00
173 changed files with 45613 additions and 5164 deletions

No files matched your search

+98 -48
View File
@@ -1,4 +1,3 @@
// 顶层容器
Root = { SOI ~ ContainerMembers ~ EOI }
ContainerMembers = { ExternalHeaderList ~ (ContainerDeclaration ~ (COMMA | SEMICOLON)*)* }
@@ -22,7 +21,6 @@ ContainerDeclaration = {
ComptimeDecl = { KEYWORD_comptime ~ (Block | Expr) }
// 函数声明(可带 comptime 前缀,支持修饰符)
FnDecl = {
DeclHeader ~ KEYWORD_comptime? ~ KEYWORD_fn ~ IDENTIFIER ~ LPAREN ~ ParamDeclList ~ RPAREN ~ (COLON ~ TypeExpr)? ~ (SEMICOLON | Block)
}
@@ -30,35 +28,38 @@ FnDecl = {
ParamDeclList = { (ParamDecl ~ COMMA)* ~ (ParamDecl | DOT3)? }
ParamDecl = { HeaderList? ~ BindingIdent ~ COLON ~ TypeExpr }
// 变量声明(支持修饰符、元组解构与类型推导)
VarDecl = { DeclHeader ~ KEYWORD_var ~ VarBody }
ConstDecl = { DeclHeader ~ KEYWORD_const ~ VarBody }
VarBody = { VarPattern ~ (COLON ~ TypeExpr)? ~ (EQUAL ~ Expr)? }
VarBody = { Pattern ~ (COLON ~ TypeExpr)? ~ (EQUAL ~ Expr)? }
// 类型声明(支持修饰符)
TypeDecl = { DeclHeader ~ KEYWORD_type ~ IDENTIFIER ~ EQUAL ~ TypeExpr }
TypeDecl = { DeclHeader ~ KEYWORD_type ~ Pattern ~ EQUAL ~ TypeExpr }
// 容器成员(支持修饰符)
MemberDecl = { DeclHeader ~ IDENTIFIER ~ (COLON ~ TypeExpr)? ~ (EQUAL ~ Expr)? }
// 变量声明左值模式(允许单独标识符)
VarPattern = { VarPatternAtom ~ (COMMA ~ VarPatternAtom)* }
VarPatternAtom = {
BindingIdent
| DOT ~ IDENTIFIER ~ (LBRACKET ~ VarPattern ~ RBRACKET)?
| LPAREN ~ VarPattern ~ RPAREN
}
// 匹配模式(不允许单独标识符)
MatchPattern = { MatchPatternAtom ~ (COMMA ~ MatchPatternAtom)* }
MatchPatternAtom = {
Pattern = { PatternAtom ~ (COMMA ~ PatternAtom)* }
PatternAtom = {
UNDERSCORE
| Expr
| DOT ~ IDENTIFIER ~ (LBRACKET ~ MatchPattern ~ RBRACKET)?
| LPAREN ~ MatchPattern ~ RPAREN
| AnonymousAggregatePattern
| DOT ~ IDENTIFIER ~ (LPAREN ~ Pattern? ~ RPAREN)?
| LPAREN ~ Pattern? ~ RPAREN
| PatternLiteral
| IDENTIFIER
}
AnonymousAggregatePattern = { DOT ~ LBRACE ~ PatternFieldList? ~ RBRACE }
PatternFieldList = { PatternField ~ ((COMMA | SEMICOLON) ~ PatternField)* ~ (COMMA | SEMICOLON)? }
PatternField = { DOT ~ IDENTIFIER ~ (EQUAL ~ PatternAtom)? }
PatternLiteral = {
FLOAT_LITERAL
| INTEGER_LITERAL
| CHAR_LITERAL
| STRING_LITERAL
| KEYWORD_true
| KEYWORD_false
| KEYWORD_null
}
// 块内语句
Block = { LBRACE ~ Statement* ~ Expr? ~ RBRACE }
Statement = {
RetStatement
@@ -70,7 +71,6 @@ Statement = {
| ComptimeStatement
}
// 声明语句(内部声明已包含 HeaderList)
DeclStatement = { (VarDecl | ConstDecl | TypeDecl) ~ (COMMA | SEMICOLON)? }
ExprStatement = { Expr ~ (COMMA | SEMICOLON)? }
ComptimeStatement = { ComptimeDecl ~ (COMMA | SEMICOLON)? }
@@ -80,11 +80,8 @@ BreakStatement = { KEYWORD_break ~ Label? ~ Expr? ~ (COMMA | SEMICOLON)? }
ContinueStatement = { KEYWORD_continue ~ Label? ~ (COMMA | SEMICOLON)? }
DeferStatement = { KEYWORD_defer ~ (Block | Statement) ~ (COMMA | SEMICOLON)? }
// 标签定义($ 前缀)
Label = { DOLLAR ~ IDENTIFIER }
// ============ 表达式 ============
Expr = { AssignExpr }
AssignExpr = { BoolOrExpr ~ (AssignOp ~ AssignExpr)? }
@@ -101,16 +98,21 @@ ArrowExpr = { RangeExpr ~ ((THIN_R_ARROW | THIN_L_ARROW | FAT_D_ARROW | THIN_D_A
RangeExpr = { ShiftExpr ~ (DOT2 ~ ShiftExpr?)? }
ShiftExpr = { AddExpr ~ ((L_ARROW2_EQ | L_ARROW2 | R_ARROW2_EQ | R_ARROW2) ~ AddExpr)* }
AddExpr = { MulExpr ~ ((PLUS | MINUS) ~ MulExpr)* }
MulExpr = { PrefixExpr ~ ((ASTERISK | SLASH | PERCENT) ~ PrefixExpr)* }
MulExpr = { CastExpr ~ ((ASTERISK | SLASH | PERCENT) ~ CastExpr)* }
CastExpr = { PrefixExpr ~ (KEYWORD_as ~ TypeExpr)* }
PrefixExpr = { PrefixOp* ~ PostfixExpr }
PrefixOp = { MINUS | BANG | TILDE | AMPERSAND }
PostfixExpr = {
PrimaryExpr ~ (LPAREN ~ ExprList ~ RPAREN | DOT ~ IDENTIFIER | DOT ~ ASTERISK | LBRACKET ~ Expr ~ RBRACKET | LBRACKET ~ Expr ~ DOT2 ~ Expr? ~ RBRACKET | KEYWORD_as ~ TypeExpr)*
PrimaryExpr ~ (LPAREN ~ ExprList ~ RPAREN | DOT ~ IDENTIFIER | DOT ~ ASTERISK | LBRACKET ~ Expr ~ RBRACKET | LBRACKET ~ Expr ~ DOT2 ~ Expr? ~ RBRACKET)*
}
ExprList = { (Expr ~ COMMA)* ~ Expr? }
// 注:类型即表达式**仅限 comptime 上下文**(`comptime { ... }`、`comptime expr`、
// `comptime fn` 的函数体)内。在这些上下文中,PrimaryExpr 额外接受
// `AggregateTypeExpr`(如 `struct { ... }`)以及 `type` 元类型;其它上下文
// 中类型只允许出现在 TypeExpr 位置。
PrimaryExpr = {
FLOAT_LITERAL
| INTEGER_LITERAL
@@ -124,8 +126,8 @@ PrimaryExpr = {
| AnonymousAggregateLiteral
| ArrayLiteral
| AnonymousArrayLiteral
| ComptimeCallExpr // @name(args)
| VariantConstruct
| ComptimeCallExpr
| IDENTIFIER
| Block
| IfExpr
@@ -139,26 +141,74 @@ PrimaryExpr = {
TupleLiteral = { ParenExpr }
// ============ 聚合字面量 ============
AggregateLiteral = { IDENTIFIER ~ LBRACE ~ StructInitList? ~ RBRACE }
AnonymousAggregateLiteral = { DOT ~ LBRACE ~ StructInitList? ~ RBRACE }
StructInitList = { StructInit ~ ((COMMA | SEMICOLON) ~ StructInit)* ~ (COMMA | SEMICOLON)? }
StructInit = { DOT ~ IDENTIFIER ~ (EQUAL ~ Expr)? }
// ============ 数组/切片字面量 ============
VariantConstruct = { DOT ~ IDENTIFIER ~ (LPAREN ~ ExprList? ~ RPAREN)? }
ArrayLiteral = {
// 切片字面量: []T[1,2,3]
// 数组字面量: [N]T[1,2,3] 或 [_]T[1,2,3]
LBRACKET ~ (INTEGER_LITERAL | UNDERSCORE)? ~ RBRACKET ~ TypeExpr ~ LBRACKET ~ ExprList? ~ RBRACKET
LBRACKET ~ RBRACKET ~ TypeExpr ~ LBRACKET ~ ExprList? ~ RBRACKET
| LBRACKET ~ (INTEGER_LITERAL | UNDERSCORE) ~ RBRACKET ~ TypeExpr ~ LBRACKET ~ ExprList? ~ RBRACKET
}
AnonymousArrayLiteral = {
DOT ~ LBRACKET ~ ExprList? ~ RBRACKET
}
// ============ 类型表达式(独立,限制语法) ============
ExprNoStruct = { AssignExprNoStruct }
AssignExprNoStruct = { BoolOrExprNoStruct ~ (AssignOp ~ AssignExprNoStruct)? }
BoolOrExprNoStruct = { BoolAndExprNoStruct ~ (BOOL_OR ~ BoolAndExprNoStruct)* }
BoolAndExprNoStruct = { BitOrExprNoStruct ~ (BOOL_AND ~ BitOrExprNoStruct)* }
BitOrExprNoStruct = { BitXorExprNoStruct ~ (PIPE ~ BitXorExprNoStruct)* }
BitXorExprNoStruct = { BitAndExprNoStruct ~ (CARET ~ BitAndExprNoStruct)* }
BitAndExprNoStruct = { CmpEqExprNoStruct ~ (AMPERSAND ~ CmpEqExprNoStruct)* }
CmpEqExprNoStruct = { CmpExprNoStruct ~ ((EQ_EQ | BANG_EQUAL) ~ CmpExprNoStruct)* }
CmpExprNoStruct = { ArrowExprNoStruct ~ ((L_ARROW_EQ | L_ARROW | R_ARROW_EQ | R_ARROW) ~ ArrowExprNoStruct)* }
ArrowExprNoStruct = { RangeExprNoStruct ~ ((THIN_R_ARROW | THIN_L_ARROW | FAT_D_ARROW | THIN_D_ARROW) ~ RangeExprNoStruct)* }
RangeExprNoStruct = { ShiftExprNoStruct ~ (DOT2 ~ ShiftExprNoStruct?)? }
ShiftExprNoStruct = { AddExprNoStruct ~ ((L_ARROW2_EQ | L_ARROW2 | R_ARROW2_EQ | R_ARROW2) ~ AddExprNoStruct)* }
AddExprNoStruct = { MulExprNoStruct ~ ((PLUS | MINUS) ~ MulExprNoStruct)* }
MulExprNoStruct = { CastExprNoStruct ~ ((ASTERISK | SLASH | PERCENT) ~ CastExprNoStruct)* }
CastExprNoStruct = { PrefixExprNoStruct ~ (KEYWORD_as ~ TypeExpr)* }
PrefixExprNoStruct = { PrefixOp* ~ PostfixExprNoStruct }
PostfixExprNoStruct = {
PrimaryExprNoStruct ~ (LPAREN ~ ExprList ~ RPAREN | DOT ~ IDENTIFIER | DOT ~ ASTERISK | LBRACKET ~ Expr ~ RBRACKET | LBRACKET ~ Expr ~ DOT2 ~ Expr? ~ RBRACKET)*
}
PrimaryExprNoStruct = {
FLOAT_LITERAL
| INTEGER_LITERAL
| CHAR_LITERAL
| STRING_LITERAL
| KEYWORD_true
| KEYWORD_false
| KEYWORD_null
| TupleLiteral
| AnonymousAggregateLiteral
| ArrayLiteral
| AnonymousArrayLiteral
| VariantConstruct
| ComptimeCallExpr
| IDENTIFIER
| Block
| IfExpr
| IfVarExpr
| WhileExpr
| LoopExpr
| ForExpr
| MatchExpr
| FnTypeExpr
}
VarBodyNoStruct = { Pattern ~ (COLON ~ TypeExpr)? ~ (EQUAL ~ ExprNoStruct)? }
TypeExpr = { HeaderList? ~ TypePrefixExpr }
TypePrefixExpr = { ASTERISK* ~ TypePostfixExpr }
@@ -168,7 +218,9 @@ TypePostfixExpr = {
}
TypePrimary = {
IDENTIFIER
AT ~ IDENTIFIER // @self 等自引用/内置类型
| ComptimeCallExpr // @import("...") 等导入即类型
| IDENTIFIER
| KEYWORD_i8
| KEYWORD_u8
| KEYWORD_i16
@@ -184,6 +236,7 @@ TypePrimary = {
| KEYWORD_bool
| KEYWORD_void
| KEYWORD_ptr
| KEYWORD_type // `type` 元类型(编译期类型的值类型)
| FnTypeExpr
| AggregateTypeExpr
| TypeParenExpr
@@ -207,26 +260,23 @@ FnTypeExpr = { KEYWORD_comptime? ~ KEYWORD_fn ~ LPAREN ~ TypeExprList? ~ RPAREN
AggregateTypeExpr = { (KEYWORD_struct | KEYWORD_union | KEYWORD_enum | KEYWORD_shape) ~ LBRACE ~ ContainerMembers ~ RBRACE }
// ============ 控制流 ============
IfExpr = { KEYWORD_if ~ Expr ~ Block ~ (KEYWORD_else ~ (Block | IfExpr | IfVarExpr))? }
IfVarExpr = { KEYWORD_if ~ VarBody ~ Block ~ (KEYWORD_else ~ (Block | IfExpr | IfVarExpr))? }
IfExpr = { KEYWORD_if ~ ExprNoStruct ~ Block ~ (KEYWORD_else ~ (Block | IfExpr | IfVarExpr))? }
IfVarExpr = { KEYWORD_if ~ KEYWORD_var ~ VarBodyNoStruct ~ Block ~ (KEYWORD_else ~ (Block | IfExpr | IfVarExpr))? }
WhileExpr = { Label? ~ KEYWORD_while ~ Expr ~ Block ~ (KEYWORD_else ~ (Block | Expr))? }
WhileExpr = { Label? ~ KEYWORD_while ~ ExprNoStruct ~ Block ~ (KEYWORD_else ~ (Block | Expr))? }
LoopExpr = { Label? ~ KEYWORD_loop ~ Block }
ForExpr = { Label? ~ KEYWORD_for ~ Expr ~ (COMMA ~ Expr)* ~ KEYWORD_as ~ BindingIdent ~ (COMMA ~ BindingIdent)* ~ Block ~ (KEYWORD_else ~ (Block | Expr))? }
ForExpr = { Label? ~ KEYWORD_for ~ Pattern ~ COLON ~ ExprNoStruct ~ (COMMA ~ ExprNoStruct)* ~ Block ~ (KEYWORD_else ~ (Block | Expr))? }
MatchExpr = { KEYWORD_match ~ Expr ~ LBRACE ~ MatchArm* ~ RBRACE }
MatchArm = { MatchPattern ~ (COMMA ~ MatchPattern)* ~ FAT_R_ARROW ~ Block }
MatchExpr = { KEYWORD_match ~ ExprNoStruct ~ LBRACE ~ MatchArm* ~ RBRACE }
MatchArm = { Pattern ~ FAT_R_ARROW ~ Block ~ COMMA? }
// ============ 其他表达式 ============
ComptimeCallExpr = { AT ~ IDENTIFIER ~ LPAREN ~ ExprList? ~ RPAREN }
ParenExpr = {
LPAREN ~ RPAREN
| LPAREN ~ Expr ~ COMMA ~ (Expr ~ COMMA)* ~ Expr? ~ RPAREN
| LPAREN ~ Expr ~ RPAREN
}
// ============ Token 定义 ============
AssignOp = { EQUAL | PLUS_EQ | MINUS_EQ | ASTERISK_EQ | SLASH_EQ | PERCENT_EQ | AMPERSAND_EQ | PIPE_EQ | CARET_EQ | L_ARROW2_EQ | R_ARROW2_EQ }
BOOL_OR = { PIPE ~ PIPE }
BOOL_AND = { AMPERSAND ~ AMPERSAND }
+369 -150
View File
@@ -1,11 +1,13 @@
#define SPL_CFLAGS "-Wall", "-Wextra", "-O0", "-g", "-D_CRT_SECURE_NO_WARNINGS"
#define COMMON_CFLAGS "-Wall", "-Wextra", "-D_CRT_SECURE_NO_WARNINGS"
#define SPL_CFLAGS COMMON_CFLAGS, "-O0", "-g"
#define SPL_OPTIMIZATION_FLAG COMMON_CFLAGS, "-O2"
#define nob_cc_flags(cmd) nob_cmd_append(cmd, SPL_CFLAGS)
#undef UNICODE
#define NOB_IMPLEMENTATION
#include "nob.h"
#include "nob/nob.h"
#undef NOB_IMPLEMENTATION
#include "nob_popen.h"
#include "nob/nob_popen.h"
#ifdef _WIN32
#include <consoleapi2.h>
#endif
@@ -17,47 +19,7 @@
// object files get rebuilt.
static const char *CFLAGS_STAMP = BUILD_FOLDER ".cflags";
/* clang-format off */
#define VM_SRCS "stage0/spl_mcode.c", "stage0/spl_syscall.c", "stage0/spl_vm.c",
static const char *spl_cli_srcs[] = {"stage0/spl_cli.c", VM_SRCS};
static const char *test_srcs[] = {"stage0/test_spl_vm.c", VM_SRCS};
static const char *splc0_srcs[] = {
"stage1/splc0.c",
"stage1/spl_lexer.c",
"stage1/spl_ast.c",
"stage1/spl_dumptree.c",
"stage1/spl_type.c",
"stage1/spl_attr.c",
"stage1/spl_sema.c",
"stage1/spl_intrin.c",
"stage1/spl_ir.c",
"stage1/spl_ast2ir.c",
"stage1/spl_layout.c",
"stage1/spl_ir2vm.c",
"stage0/spl_mcode.c",
};
/* clang-format on */
typedef struct {
const char *name;
const char **srcs;
size_t srcs_count;
} target_t;
static target_t exe_targets[] = {
{"spl_cli", spl_cli_srcs, NOB_ARRAY_LEN(spl_cli_srcs)},
{"test", test_srcs, NOB_ARRAY_LEN(test_srcs)},
{"splc0", splc0_srcs, NOB_ARRAY_LEN(splc0_srcs)},
};
static const target_t *find_exe_target(const char *name) {
for (size_t i = 0; i < NOB_ARRAY_LEN(exe_targets); ++i) {
if (strcmp(exe_targets[i].name, name) == 0) {
return &exe_targets[i];
}
}
return NULL;
}
#include "nob/nob_data.h"
static const char *obj_path(const char *src) {
// "stage0/spl_vm.c" -> "build/stage0_spl_vm.o"
@@ -73,6 +35,22 @@ static const char *obj_path(const char *src) {
return out;
}
// Windows 上 clang/gcc 会输出 build/foo.exe;这里统一带上后缀,
// 否则 nob_needs_rebuild 找不到输出会每次重新链接。
static const char *exe_path(const char *name) {
#ifdef _WIN32
return nob_temp_sprintf(BUILD_FOLDER "%s.exe", name);
#else
return nob_temp_sprintf(BUILD_FOLDER "%s", name);
#endif
}
static const char *sir_path(const char *name) {
return nob_temp_sprintf(BUILD_FOLDER "%s.sir", name);
}
static const char *c_path(const char *name) { return nob_temp_sprintf(BUILD_FOLDER "%s.c", name); }
static bool sync_cflags_stamp(void) {
Nob_String_View cflags = nob_sv_from_cstr(CFLAGS_STR);
Nob_String_Builder old = {0};
@@ -93,77 +71,188 @@ static bool sync_cflags_stamp(void) {
static bool prepare_build(void) {
if (!nob_mkdir_if_not_exists(BUILD_FOLDER))
return false;
return sync_cflags_stamp();
if (!sync_cflags_stamp())
return false;
return true;
}
static bool build_object(const char *src) {
const char *obj = obj_path(src);
if (!nob_file_exists(src)) {
nob_log(NOB_ERROR, "source file `%s` does not exist", src);
return false;
typedef struct {
const char **items;
size_t count;
size_t capacity;
} str_da_t;
bool run_target(const char *name, Nob_Cmd *args);
const char *build_target(const char *name);
static const char *build_exe(build_t *target) {
Nob_Cmd cmd = {0};
str_da_t objs = {0};
str_da_t deps = {0};
for (size_t i = 0; i < target->exe.srcs_count; ++i) {
const char *src = target->exe.srcs[i];
const char *obj = obj_path(src);
if (!nob_file_exists(src)) {
nob_log(NOB_ERROR, "source file `%s` does not exist", src);
goto defer;
}
da_append(&objs, obj);
da_append(&deps, src);
da_append(&deps, CFLAGS_STAMP);
for (size_t j = 0; j < target->exe.headers_count; ++j) {
da_append(&deps, target->exe.headers[j]);
}
int needs = nob_needs_rebuild(obj, deps.items, deps.count);
deps.count = 0; // 清空 deps,下一轮循环重新收集
if (needs < 0)
goto defer;
if (needs == 0)
continue;
nob_cc(&cmd);
nob_cc_flags(&cmd);
nob_cmd_append(&cmd, "-c");
nob_cc_inputs(&cmd, src);
nob_cc_output(&cmd, obj);
if (!nob_cmd_run(&cmd)) {
nob_log(NOB_ERROR, "failed to compile `%s`", src);
goto defer;
}
cmd.count = 0; // 清空 cmd,下一轮循环重新构建
}
const char *inputs[] = {src, CFLAGS_STAMP};
int needs = nob_needs_rebuild(obj, inputs, NOB_ARRAY_LEN(inputs));
const char *exe = exe_path(target->name);
int needs = nob_needs_rebuild(exe, objs.items, objs.count);
if (needs < 0)
return false;
goto defer;
if (needs == 0)
return true;
return exe;
Nob_Cmd cmd = {0};
nob_cc(&cmd);
nob_cc_flags(&cmd);
nob_cmd_append(&cmd, "-c");
nob_cc_inputs(&cmd, src);
nob_cc_output(&cmd, obj);
return nob_cmd_run(&cmd);
}
static bool link_exe(const target_t *target) {
const char *exe = nob_temp_sprintf(BUILD_FOLDER "%s", target->name);
const char **objs = (const char **)nob_temp_alloc(sizeof(const char *) * target->srcs_count);
for (size_t i = 0; i < target->srcs_count; ++i) {
objs[i] = obj_path(target->srcs[i]);
}
int needs = nob_needs_rebuild(exe, objs, target->srcs_count);
if (needs < 0)
return false;
if (needs == 0)
return true;
Nob_Cmd cmd = {0};
nob_cc(&cmd);
nob_cc_flags(&cmd);
for (size_t i = 0; i < target->srcs_count; ++i) {
nob_cc_inputs(&cmd, objs[i]);
nob_cmd_append(&cmd, SPL_OPTIMIZATION_FLAG);
for (size_t i = 0; i < objs.count; ++i) {
nob_cc_inputs(&cmd, objs.items[i]);
}
nob_cc_output(&cmd, exe);
return nob_cmd_run(&cmd);
if (!nob_cmd_run(&cmd)) {
goto defer;
}
return exe;
defer:
nob_cmd_free(cmd);
da_free(objs);
da_free(deps);
return NULL;
}
static bool build_target(const char *name) {
const target_t *target = find_exe_target(name);
if (target == NULL) {
nob_log(NOB_ERROR, "unknown target `%s`", name);
const char *build_target(const char *name) {
if (!prepare_build())
return NULL;
for (size_t i = 0; i < NOB_ARRAY_LEN(targets); ++i) {
if (name != NULL && strcmp(targets[i].name, name) != 0) {
continue;
}
build_t *target = &targets[i];
switch (target->kind) {
case BUILD_EXE_WITH_OPTIMIZATION:
case BUILD_EXE: {
return build_exe(target);
}
case BUILD_SIR: {
Nob_Cmd cmd = {0};
const char *target_sir = sir_path(name);
nob_cmd_append(&cmd, target->sir.src, target_sir);
if (!run_target(target->sir.compiler, &cmd)) {
nob_log(NOB_ERROR, "failed to build `%s`", name);
nob_cmd_free(cmd);
return NULL;
}
nob_cmd_free(cmd);
return target_sir;
}
case BUILD_NATIVE: {
Nob_Cmd cmd = {0};
const char *target_exe = exe_path(name);
const char *target_c = c_path(name);
// TODO need_rebuild
int needs = nob_needs_rebuild(target_c, target->native.deps, target->native.deps_count);
if (needs < 0)
return NULL;
if (needs != 0) {
nob_cmd_append(&cmd, "--dump", "c", target->native.src, target_c);
if (!run_target(target->native.compiler, &cmd)) {
nob_log(NOB_ERROR, "failed to build `%s`", name);
nob_cmd_free(cmd);
return NULL;
}
cmd.count = 0;
}
needs = nob_needs_rebuild(target_exe, target->native.crt_deps,
target->native.crt_deps_count);
if (needs < 0)
return NULL;
if (needs == 0)
return target_exe;
nob_cc(&cmd);
nob_cmd_append(&cmd, "-I./stage0", "-O2", "-D_CRT_SECURE_NO_WARNINGS");
nob_cc_inputs(&cmd, target_c);
nob_cc_output(&cmd, target_exe);
if (!nob_cmd_run(&cmd)) {
nob_log(NOB_ERROR, "failed to build `%s`", name);
nob_cmd_free(cmd);
return NULL;
}
nob_cmd_free(cmd);
return target_exe;
}
default:
NOB_UNREACHABLE("build_t.kind");
}
}
nob_log(NOB_ERROR, "unknown build target `%s`", name);
return NULL;
}
bool run_target_dry(const char *name, Nob_Cmd *args, Nob_Cmd *result) {
for (size_t i = 0; i < NOB_ARRAY_LEN(runs); ++i) {
if (strcmp(runs[i].name, name) != 0) {
continue;
}
const char *exe = build_target(runs[i].name);
const char *interpreter = runs[i].interpreter;
if (interpreter) {
const char *inter = build_target(interpreter);
if (!exe) {
nob_log(NOB_ERROR, "failed to build interpreter `%s` for `%s`", interpreter, name);
return false;
}
nob_cmd_append(result, inter);
}
nob_cmd_append(result, exe);
nob_cmd_extend(result, args);
return true;
}
nob_log(NOB_ERROR, "unknown run target `%s`", name);
return false;
}
bool run_target(const char *name, Nob_Cmd *args) {
Nob_Cmd result = {0};
if (!run_target_dry(name, args, &result)) {
return false;
}
for (size_t i = 0; i < target->srcs_count; ++i) {
if (!build_object(target->srcs[i])) {
return false;
}
}
return link_exe(target);
}
static bool build_all(void) {
for (size_t i = 0; i < NOB_ARRAY_LEN(exe_targets); ++i) {
if (!build_target(exe_targets[i].name)) {
return false;
}
}
return true;
nob_log(NOB_INFO, "running `%s`", name);
bool ok = nob_cmd_run(&result);
nob_cmd_free(result);
return ok;
}
static void parse_expect(const char *name, Nob_Cmd_Result *expect) {
@@ -220,7 +309,7 @@ static bool test_once(const char *fname, void *user_data) {
}
cmd.count = 0;
nob_cmd_append(&cmd, nob_temp_sprintf(BUILD_FOLDER "%s", compiler_path));
nob_cmd_append(&cmd, exe_path(compiler_path));
nob_cmd_append(&cmd, fname, target_sir);
if (!nob_cmd_run(&cmd)) {
return false;
@@ -230,7 +319,7 @@ static bool test_once(const char *fname, void *user_data) {
return 1;
}
cmd.count = 0;
nob_cmd_append(&cmd, nob_temp_sprintf(BUILD_FOLDER "%s", cli_path));
nob_cmd_append(&cmd, exe_path(cli_path));
nob_cmd_append(&cmd, target_sir);
// nob_cmd_run(&cmd);
@@ -262,38 +351,160 @@ static bool test_once_with_dump(const char *fname, void *user_data) {
if (!build_target(target_name))
return 1;
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, nob_temp_sprintf(BUILD_FOLDER "%s", target_name));
nob_cmd_append(&cmd, exe_path(target_name));
nob_cmd_append(&cmd, "--dump", (char *)user_data, fname, BUILD_FOLDER "t.sir");
return nob_cmd_run(&cmd);
}
typedef bool (*test_fn_t)(const char *fname, void *user_data);
static bool run_test(Nob_Files *files, test_fn_t test_once, void *user_data) {
const char *test_folder = "./tests/stage1/";
// 由源路径派生构建产物路径:build/<basename>.sir
static const char *artifact_sir(const char *src) {
const char *base = src;
for (const char *p = src; *p; ++p)
if (*p == '/' || *p == '\\')
base = p + 1;
const char *dot = strrchr(base, '.');
size_t len = dot ? (size_t)(dot - base) : strlen(base);
return nob_temp_sprintf(BUILD_FOLDER "%.*s.sir", (int)len, base);
}
if (!prepare_build()) {
return 1;
static bool run_capture(Nob_Cmd *cmd, Nob_Cmd_Result *result) {
Nob_String_Builder rendered = {0};
nob_cmd_render(*cmd, &rendered);
Nob_String_View sv = nob_sb_to_sv(rendered);
bool ok = nob_cmd_capture_popen(nob_temp_sv_to_cstr(sv), result);
nob_sb_free(rendered);
return ok;
}
// diff 测试:对同一文件跑 `splc0 --dump <mode>` 与 `spl_cli splc1.sir --dump <mode>`,
// 逐字节比较 stdout。
static bool test_once_diff(const char *fname, void *user_data) {
const char *mode = (const char *)user_data;
if (!mode) {
nob_log(NOB_ERROR, "diff test requires a mode: lex|ast|sema|ir|vm");
return false;
}
if (!build_target("splc0"))
return false;
if (!build_target("spl_cli"))
return false;
if (!build_target("splc1"))
return false;
Nob_Cmd c_cmd = {0};
nob_cmd_append(&c_cmd, exe_path("splc0"), "--dump", (char *)mode, (char *)fname,
BUILD_FOLDER "diff_c.sir");
Nob_Cmd_Result c_res = {0};
if (!run_capture(&c_cmd, &c_res)) {
nob_cmd_result_free(c_res);
return false;
}
Nob_File_Paths paths = {0};
nob_read_entire_dir(test_folder, &paths);
for (int i = 0; i < files->count; i += 1) {
for (int j = 0; j < paths.count; j += 1) {
String_View sv = sv_from_cstr(paths.items[j]);
// printf("--- test " SV_Fmt " ---\n", SV_Arg(sv));
if (!strstr(paths.items[j], files->items[i])) {
continue;
Nob_Cmd s_cmd = {0};
nob_cmd_append(&s_cmd, exe_path("spl_cli"), BUILD_FOLDER "splc1.sir", "--dump", (char *)mode,
(char *)fname);
Nob_Cmd_Result s_res = {0};
if (!run_capture(&s_cmd, &s_res)) {
nob_cmd_result_free(c_res);
nob_cmd_result_free(s_res);
return false;
}
Nob_String_View c_out = nob_sb_to_sv(c_res.out);
Nob_String_View s_out = nob_sb_to_sv(s_res.out);
bool same = nob_sv_eq(c_out, s_out);
if (!same) {
nob_log(NOB_ERROR, "diff mismatch: %s (c=%zu bytes, spl=%zu bytes)", fname, c_out.count,
s_out.count);
// 打印首个不相同行(按行比较)。
Nob_String_View ca = c_out;
Nob_String_View sa = s_out;
size_t lineno = 1;
while (ca.count > 0 || sa.count > 0) {
Nob_String_View cl = nob_sv_chop_by_delim(&ca, '\n');
Nob_String_View sl = nob_sv_chop_by_delim(&sa, '\n');
if (!nob_sv_eq(cl, sl)) {
nob_log(NOB_ERROR, " line %zu:", lineno);
nob_log(NOB_ERROR, " c : %.*s", (int)cl.count, cl.data);
nob_log(NOB_ERROR, " spl: %.*s", (int)sl.count, sl.data);
break;
}
const char *tname = nob_temp_sprintf("%s%s", test_folder, paths.items[j]);
printf("--- test %s ---\n", tname);
if (test_once(tname, user_data) == false)
return false;
lineno++;
}
nob_log(NOB_ERROR, "--- c stderr/stdout merged above; exit=%d vs spl exit=%d ---",
c_res.exit_code, s_res.exit_code);
}
nob_cmd_result_free(c_res);
nob_cmd_result_free(s_res);
return same;
}
static bool is_stage_name(const char *s) {
return strcmp(s, "stage1") == 0 || strcmp(s, "stage2") == 0 || strcmp(s, "diff") == 0;
}
static bool is_dump_mode(const char *s) {
return strcmp(s, "lex") == 0 || strcmp(s, "ast") == 0 || strcmp(s, "sema") == 0 ||
strcmp(s, "ir") == 0 || strcmp(s, "vm") == 0;
}
static const char *stage_dir_of(const char *stage) {
if (strcmp(stage, "stage2") == 0)
return "./stage2/";
return "./tests/stage1/";
}
typedef bool (*test_fn_t)(const char *fname, void *user_data);
typedef struct {
Nob_Files *files;
test_fn_t test_once;
void *user_data;
} test_walk_ctx_t;
static bool test_walk_entry(Nob_Walk_Entry entry) {
if (entry.type != NOB_FILE_REGULAR)
return true;
const char *dot = strrchr(entry.path, '.');
if (!dot || strcmp(dot, ".spl") != 0)
return true;
test_walk_ctx_t *ctx = (test_walk_ctx_t *)entry.data;
bool run_all = (ctx->files->count == 0);
bool matched = run_all;
for (int i = 0; !matched && i < ctx->files->count; i += 1) {
if (strstr(entry.path, ctx->files->items[i]))
matched = true;
}
if (!matched)
return true;
printf("--- test %s ---\n", entry.path);
if (ctx->test_once(entry.path, ctx->user_data) == false)
return false;
return true;
}
static bool run_test(const char *stage, Nob_Files *files, test_fn_t test_once, void *user_data) {
const char *test_folder = stage_dir_of(stage);
if (!prepare_build())
return false;
if (!nob_file_exists(test_folder)) {
nob_log(NOB_INFO, "%s does not exist, skip", test_folder);
return true;
}
test_walk_ctx_t ctx = {
.files = files,
.test_once = test_once,
.user_data = user_data,
};
return nob_walk_dir(test_folder, test_walk_entry, .data = &ctx);
}
static bool delete_walk_entry(Nob_Walk_Entry entry) { return nob_delete_file(entry.path); }
static bool delete_directory_recursively(const char *dir_path) {
@@ -308,22 +519,18 @@ static void print_help(const char *program_name) {
nob_log(NOB_INFO, "Commands:");
nob_log(NOB_INFO, " build [targets...] build all targets or the given ones");
nob_log(NOB_INFO, " run <target> [args] build and run a target");
nob_log(NOB_INFO, " test build and run VM unit tests");
nob_log(NOB_INFO, " test <kind> build and run tests");
nob_log(NOB_INFO, " clean remove build artifacts");
nob_log(NOB_INFO, " list list all targets");
nob_log(NOB_INFO, " help/-h/--help print this help message");
nob_log(NOB_INFO, "%s", " ");
nob_log(NOB_INFO, "Targets:");
for (size_t i = 0; i < NOB_ARRAY_LEN(exe_targets); ++i) {
nob_log(NOB_INFO, " %s", exe_targets[i].name);
}
}
int main(int argc, char **argv) {
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
#endif
NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "nob.h", "nob_popen.h");
NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "nob/nob.h", "nob/nob_popen.h", "nob/nob_data.h");
set_log_handler(nob_cancer_log_handler);
const char *program_name = nob_shift(argv, argc);
@@ -342,7 +549,7 @@ int main(int argc, char **argv) {
return 1;
}
} else {
if (!build_all())
if (!build_target(NULL))
return 1;
}
return 0;
@@ -354,38 +561,53 @@ int main(int argc, char **argv) {
return 1;
}
const char *target_name = nob_shift(argv, argc);
if (!prepare_build())
return 1;
if (!build_target(target_name))
return 1;
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, nob_temp_sprintf(BUILD_FOLDER "%s", target_name));
Nob_Cmd runcmd = {0};
while (argc > 0) {
nob_cmd_append(&cmd, nob_shift(argv, argc));
nob_cmd_append(&runcmd, nob_shift(argv, argc));
}
return nob_cmd_run(&cmd) ? 0 : 1;
return !run_target(target_name, &runcmd);
}
if (strcmp(command_name, "test") == 0) {
Nob_Files files = {0};
// 1. stage(可选,默认 stage1)
const char *stage = "stage1";
if (argc > 0 && is_stage_name(argv[0])) {
stage = nob_shift(argv, argc);
}
// 2. mode(可选)
test_fn_t tfn = test_once;
const char *mode_name = NULL;
if (argc > 0) {
mode_name = nob_shift(argv, argc);
if (strcmp(mode_name, "lex") == 0 || strcmp(mode_name, "ast") == 0 ||
strcmp(mode_name, "sema") == 0 || strcmp(mode_name, "ir") == 0 ||
strcmp(mode_name, "vm") == 0) {
if (is_dump_mode(argv[0])) {
tfn = test_once_with_dump;
} else {
da_append(&files, mode_name);
mode_name = nob_shift(argv, argc);
} else if (strcmp(argv[0], "run") == 0) {
nob_shift(argv, argc);
// HACK:只对 stage1 有意义,跑上次生成的 build/test.sir
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, BUILD_FOLDER "spl_cli", "-d");
nob_cmd_append(&cmd, BUILD_FOLDER "test.sir");
return nob_cmd_run(&cmd) ? 0 : 1;
}
}
// diff 需要显式 mode,且使用专门的双编译器比较函数。
if (strcmp(stage, "diff") == 0) {
if (!mode_name) {
nob_log(NOB_ERROR, "diff requires a mode: lex|ast|sema|ir|vm");
return 1;
}
tfn = test_once_diff;
}
// 3. 剩下的都当 pattern
Nob_Files files = {0};
while (argc > 0) {
da_append(&files, nob_shift(argv, argc));
}
return run_test(&files, tfn, (void *)mode_name) ? 0 : 1;
return run_test(stage, &files, tfn, (void *)mode_name) ? 0 : 1;
}
if (strcmp(command_name, "clean") == 0) {
@@ -398,11 +620,8 @@ int main(int argc, char **argv) {
}
if (strcmp(command_name, "list") == 0) {
for (size_t i = 0; i < NOB_ARRAY_LEN(exe_targets); ++i) {
nob_log(NOB_INFO, "%s:", exe_targets[i].name);
for (size_t j = 0; j < exe_targets[i].srcs_count; ++j) {
nob_log(NOB_INFO, " %s", exe_targets[i].srcs[j]);
}
for (size_t i = 0; i < NOB_ARRAY_LEN(targets); ++i) {
nob_log(NOB_INFO, "%s:", targets[i].name);
}
return 0;
}
View File
File renamed without changes.
+298
View File
@@ -0,0 +1,298 @@
#ifndef __NOB_DATA_H__
#define __NOB_DATA_H__
/* clang-format off */
#define VM_SRCS "stage0/spl_mcode.c", "stage0/spl_syscall.c", "stage0/spl_vm.c",
static const char *stage0_headers[] = {
"stage0/spl_asp.h", "stage0/spl_mcode.h", "stage0/spl_syscall.h",
"stage0/spl_vm.h", "stage0/spl_rt_core.h", "stage0/spl_rt.h",
"stage0/spl_vm_embed.h",
};
static const char *spl_cli_srcs[] = {"stage0/spl_cli.c", VM_SRCS};
static const char *vm_test_srcs[] = {"stage0/test_spl_vm.c", VM_SRCS};
static const char *splc0_srcs[] = {
"stage1/splc0.c",
"stage1/spl_diag.c",
"stage1/spl_tok.c",
"stage1/spl_lexer.c",
"stage1/spl_ast.c",
"stage1/spl_parser.c",
"stage1/spl_dumptree.c",
"stage1/spl_type.c",
"stage1/spl_fold.c",
"stage1/spl_lang_type.c",
"stage1/spl_attr.c",
"stage1/spl_scope.c",
"stage1/spl_sema.c",
"stage1/spl_intrin.c",
"stage1/spl_ir.c",
"stage1/spl_ast2ir.c",
"stage1/spl_layout.c",
"stage1/spl_ir2vm.c",
"stage1/ir_opt.c",
"stage1/vm_opt.c",
"stage0/spl_mcode.c",
};
static const char *splc1_crt_deps[] ={
"stage0/spl_rt.h",
"stage0/spl_vm_embed.h",
// TODO
};
static const char* splc1_deps[] = {
"stage2/sys/exit.spl",
"stage2/sys/file.spl",
"stage2/sys/mem.spl",
"stage2/sys/time.spl",
"stage2/sys/vm.spl",
"stage2/utils/algorithm.spl",
"stage2/utils/arena.spl",
"stage2/utils/ascii.spl",
"stage2/utils/byte_buffer.spl",
"stage2/utils/byte_view.spl",
"stage2/utils/error.spl",
"stage2/utils/fmt.spl",
"stage2/utils/hash.spl",
"stage2/utils/io.spl",
"stage2/utils/json.spl",
"stage2/utils/log.spl",
"stage2/utils/lsp_frame.spl",
"stage2/utils/map.spl",
"stage2/utils/memory.spl",
"stage2/utils/mp.spl",
"stage2/utils/path.spl",
"stage2/utils/profile.spl",
"stage2/utils/set.spl",
"stage2/utils/source.spl",
"stage2/utils/str.spl",
"stage2/utils/utf8.spl",
"stage2/utils/value.spl",
"stage2/utils/vec.spl",
"stage2/ast.spl",
"stage2/ast2ir.spl",
"stage2/attr.spl",
"stage2/def.spl",
"stage2/diag.spl",
"stage2/eval.spl",
"stage2/intrin.spl",
"stage2/ir.spl",
"stage2/ir2c.spl",
"stage2/ir2vm.spl",
"stage2/ir_builder.spl",
"stage2/lang_type.spl",
"stage2/layout.spl",
"stage2/lexer.spl",
"stage2/mcode.spl",
"stage2/parser.spl",
"stage2/scope.spl",
"stage2/sema.spl",
"stage2/splc1.spl",
"stage2/token.spl",
"stage2/type_table.spl",
};
static const char* splc2_deps[] = {
/* stage3 */
"stage3/builtin.spl",
"stage3/hir.spl",
"stage3/hir2ir.spl",
"stage3/lang_type.spl",
"stage3/lower.spl",
"stage3/scope.spl",
"stage3/sema.spl",
"stage3/splc2.spl",
"stage3/tag.spl",
/* stage2 */
"stage2/sys/exit.spl",
"stage2/sys/file.spl",
"stage2/sys/mem.spl",
"stage2/sys/time.spl",
"stage2/sys/vm.spl",
"stage2/utils/algorithm.spl",
"stage2/utils/arena.spl",
"stage2/utils/ascii.spl",
"stage2/utils/byte_buffer.spl",
"stage2/utils/byte_view.spl",
"stage2/utils/error.spl",
"stage2/utils/fmt.spl",
"stage2/utils/hash.spl",
"stage2/utils/io.spl",
"stage2/utils/json.spl",
"stage2/utils/log.spl",
"stage2/utils/lsp_frame.spl",
"stage2/utils/map.spl",
"stage2/utils/memory.spl",
"stage2/utils/mp.spl",
"stage2/utils/path.spl",
"stage2/utils/profile.spl",
"stage2/utils/set.spl",
"stage2/utils/source.spl",
"stage2/utils/str.spl",
"stage2/utils/utf8.spl",
"stage2/utils/value.spl",
"stage2/utils/vec.spl",
"stage2/ast.spl",
"stage2/ast2ir.spl",
"stage2/attr.spl",
"stage2/def.spl",
"stage2/diag.spl",
"stage2/eval.spl",
"stage2/intrin.spl",
"stage2/ir.spl",
"stage2/ir2c.spl",
"stage2/ir2vm.spl",
"stage2/ir_builder.spl",
"stage2/lang_type.spl",
"stage2/layout.spl",
"stage2/lexer.spl",
"stage2/mcode.spl",
"stage2/parser.spl",
"stage2/scope.spl",
"stage2/sema.spl",
"stage2/splc1.spl",
"stage2/token.spl",
"stage2/type_table.spl",
};
static const char *splc2_crt_deps[] = {
"stage0/spl_rt.h", "stage0/spl_rt_core.h", "stage0/spl_asp.h",
"stage0/spl_mcode.h", "stage0/spl_vm.h", "stage0/spl_syscall.h",
"stage0/spl_vm_embed.h",
};
/* clang-format on */
typedef struct build {
const char *name;
enum {
BUILD_EXE,
BUILD_EXE_WITH_OPTIMIZATION,
BUILD_SIR,
BUILD_NATIVE,
} kind;
union {
struct {
const char **srcs;
size_t srcs_count;
const char **headers;
size_t headers_count;
} exe;
struct {
const char *src;
const char *compiler;
const char **deps;
size_t deps_count;
} sir;
struct {
const char *src;
const char *compiler;
const char **deps;
size_t deps_count;
const char **crt_deps;
size_t crt_deps_count;
} native;
};
} build_t;
typedef struct run {
const char *name;
const char *interpreter;
} run_t;
static build_t targets[] = {
{
"spl_cli",
BUILD_EXE_WITH_OPTIMIZATION,
.exe = {.srcs = spl_cli_srcs,
.srcs_count = NOB_ARRAY_LEN(spl_cli_srcs),
.headers = stage0_headers,
.headers_count = NOB_ARRAY_LEN(stage0_headers)},
},
{
"spl_cli_g",
BUILD_EXE,
.exe = {.srcs = spl_cli_srcs,
.srcs_count = NOB_ARRAY_LEN(spl_cli_srcs),
.headers = stage0_headers,
.headers_count = NOB_ARRAY_LEN(stage0_headers)},
},
{
"vm_test",
BUILD_EXE,
.exe = {.srcs = vm_test_srcs,
.srcs_count = NOB_ARRAY_LEN(vm_test_srcs),
.headers = stage0_headers,
.headers_count = NOB_ARRAY_LEN(stage0_headers)},
},
{
"splc0",
BUILD_EXE,
.exe = {.srcs = splc0_srcs,
.srcs_count = NOB_ARRAY_LEN(splc0_srcs),
.headers = stage0_headers,
.headers_count = NOB_ARRAY_LEN(stage0_headers)},
},
{
"splc1",
BUILD_SIR,
.sir = {.src = "stage2/splc1.spl",
.compiler = "splc0",
.deps = splc1_deps,
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
},
{
"splc1s",
BUILD_SIR,
.sir = {.src = "stage2/splc1.spl",
.compiler = "splc1",
.deps = splc1_deps,
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
},
{
"splc1r",
BUILD_SIR,
.sir = {.src = "stage2/splc1.spl",
.compiler = "splc1s",
.deps = splc1_deps,
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
},
{
"splc1n",
BUILD_NATIVE,
.native = {.src = "stage2/splc1.spl",
.compiler = "splc1",
.deps = splc1_deps,
.deps_count = NOB_ARRAY_LEN(splc1_deps),
.crt_deps = splc1_crt_deps,
.crt_deps_count = NOB_ARRAY_LEN(splc1_crt_deps)},
},
{
"splc2",
BUILD_SIR,
.sir = {.src = "stage3/splc2.spl",
.compiler = "splc1n",
.deps = splc2_deps,
.deps_count = NOB_ARRAY_LEN(splc2_deps)},
},
{
"splc2n",
BUILD_NATIVE,
.native = {.src = "stage3/splc2.spl",
.compiler = "splc1n",
.deps = splc2_deps,
.deps_count = NOB_ARRAY_LEN(splc2_deps),
.crt_deps = splc2_crt_deps,
.crt_deps_count = NOB_ARRAY_LEN(splc2_crt_deps)},
},
};
static run_t runs[] = {
{"spl_cli", NULL}, {"vm_test", NULL}, {"splc0", NULL},
{"splc1", "spl_cli"}, {"splc1r", "spl_cli"}, {"splc1s", "spl_cli"},
{"splc1n", NULL}, {"splc2", "spl_cli"}, {"splc2n", NULL},
};
#endif /* __NOB_DATA_H__ */
View File
File renamed without changes.
+9 -28
View File
@@ -9,8 +9,16 @@
#ifndef nullptr
#define nullptr NULL
#endif
#ifndef SPL_USIZE_TYPEDEF
#define SPL_USIZE_TYPEDEF
typedef uintptr_t usize;
#endif
#ifndef SPL_ISIZE_TYPEDEF
#define SPL_ISIZE_TYPEDEF
typedef intptr_t isize;
#endif
#define MAP_TYPEOF __typeof__
@@ -165,7 +173,7 @@ static inline usize map_hash_str(const char *s) {
while ((map).data[_idx].state != __MAP_SLOT_EMPTY) { \
if ((map).data[_idx].state == __MAP_SLOT_OCCUPIED && \
(map).cmp((map).data[_idx].key, _key) == 0) { \
break; /* 已存在,不更新 */ \
break; /* 已存在,不更新 */ \
} \
if ((map).data[_idx].state == __MAP_SLOT_DELETED && _first_del == (usize) - 1) \
_first_del = _idx; \
@@ -202,33 +210,6 @@ static inline usize map_hash_str(const char *s) {
_found; \
}))
/**
* 查询(续探):首次调用前将 *in_out_idx 置为 (usize)-1,
* 则从键自身的 hash 桶开始查找;命中后 *out_val 被赋值、返回 1,
* 且 *in_out_idx 更新为命中槽位,下次调用从该槽的下一个继续探测(碰撞续探)。
* 扫描到空槽返回 0。用于 key 碰撞后需要回表二次比较的场景。
*/
#define map_get_continue(map, _key, out_val, in_out_idx) \
(({ \
int _found = 0; \
if ((map).cap > 0) { \
usize _mask = (map).cap - 1; \
usize _idx = (*(in_out_idx) == (usize) - 1) ? (map).hash(_key) & _mask \
: (*(in_out_idx) + 1) & _mask; \
while ((map).data[_idx].state != __MAP_SLOT_EMPTY) { \
if ((map).data[_idx].state == __MAP_SLOT_OCCUPIED && \
(map).cmp((map).data[_idx].key, _key) == 0) { \
*out_val = (map).data[_idx].val; \
*(in_out_idx) = _idx; \
_found = 1; \
break; \
} \
_idx = (_idx + 1) & _mask; \
} \
} \
_found; \
}))
/**
* 删除指定键
*/
+4 -1
View File
@@ -26,7 +26,10 @@
#ifndef nullptr
#define nullptr NULL
#endif
typedef size_t usize;
#ifndef SPL_USIZE_TYPEDEF
#define SPL_USIZE_TYPEDEF
typedef uintptr_t usize;
#endif
#define __vec_realloc realloc
#define __vec_free free
#define __vec_memcpy memcpy
+38
View File
@@ -0,0 +1,38 @@
#ifndef __SPL_ASP_H__
#define __SPL_ASP_H__
/* spl_asp.h - 地址空间抽象(Address SPace)
*
* SPL 侧指针语义由 spl_asp_t 统一描述:
* - native(编译器自身):identity,指针就是宿主地址。
* - VM:句柄/偏移,offset = (handle<<32) | 区内字节偏移,0 为 null。
*
* runtime(spl_rt_core.h)的解引用/分配一律经此接口,从而同一份 SPL
* 源的 Vec/Map/mem 在两种后端下都自洽。
*/
#include <stdint.h>
#include <stddef.h>
#ifndef SPL_USIZE_TYPEDEF
#define SPL_USIZE_TYPEDEF
typedef uintptr_t usize;
#endif
typedef struct spl_asp spl_asp_t;
struct spl_asp {
void *ctx;
/* offset -> 宿主地址;size>0 时做边界检查;失败返回 NULL。 */
unsigned char *(*xlate)(void *ctx, uintptr_t off, uintptr_t size);
/* 分配 size 字节,返回 offset(0=失败)。 */
uintptr_t (*alloc)(void *ctx, uintptr_t size);
/* 重新分配 offset@old,返回新 offset(0=失败)。 */
uintptr_t (*realloc)(void *ctx, uintptr_t off, uintptr_t size);
/* 释放 offset。 */
void (*free)(void *ctx, uintptr_t off);
/* 宿主地址 -> offset;size>0 时校验范围。不属于该空间返回 0。 */
uintptr_t (*to_off)(void *ctx, const void *host, uintptr_t size);
};
#endif /* __SPL_ASP_H__ */
+49 -2
View File
@@ -30,6 +30,10 @@ typedef struct {
int debug_cli;
int trace;
int disasm;
int profile;
int profile_interval;
const char *profile_out;
int stats;
const char *entry;
const char *path; /* file.sir */
const char **args; /* spl 程序参数(file.sir 之后) */
@@ -43,6 +47,9 @@ static void usage(void) {
" --disasm disasm dump sir\n"
" --entry <name> entry function (default: main)\n"
" --trace print every instruction\n"
" --profile[=N] native sampling profiler (every N insns, default 10000)\n"
" --profile-out <p> profile output prefix (default build/profile)\n"
" --stats print executed instructions / call count at exit\n"
" -h, --help show help\n");
}
@@ -67,6 +74,19 @@ static int parse_args(int argc, const char **argv, cli_opts_t *o) {
o->trace = 1;
} else if (strcmp(a, "--disasm") == 0) {
o->disasm = 1;
} else if (strcmp(a, "--stats") == 0) {
o->stats = 1;
} else if (strcmp(a, "--profile") == 0) {
o->profile = 1;
} else if (strncmp(a, "--profile=", 10) == 0) {
o->profile = 1;
o->profile_interval = (int)strtol(a + 10, NULL, 10);
} else if (strcmp(a, "--profile-out") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "spl_cli: --profile-out needs a prefix\n");
return -1;
}
o->profile_out = argv[++i];
} else if (strcmp(a, "--entry") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "spl_cli: --entry needs a name\n");
@@ -637,16 +657,27 @@ int main(int argc, const char **argv) {
spl_vm_t vm;
spl_vm_init(&vm);
if (o.debug)
if (o.debug) {
fprintf(stderr, "spl_cli: debug mode on\n");
spl_vm_set_debug(&vm, 1);
if (o.trace)
} else {
spl_vm_set_debug(&vm, 0);
}
if (o.trace) {
fprintf(stderr, "spl_cli: trace mode on\n");
spl_vm_set_trace(&vm, 1);
}
if (spl_vm_load_prog(&vm, &prog) != 0) {
fprintf(stderr, "spl_cli: failed to load prog\n");
spl_vm_drop(&vm);
spl_prog_drop(&prog);
return 1;
}
if (o.profile) {
usize interval = o.profile_interval > 0 ? (usize)o.profile_interval : 10000;
spl_vm_profile_enable(&vm, interval);
fprintf(stderr, "spl_cli: profile mode on (interval=%zu)\n", interval);
}
/* spl 程序 argv:argv[0]=file.sir(程序名),argv[1..]=args */
const char **spl_argv = (const char **)malloc(sizeof(char *) * (size_t)(o.nargs + 1));
@@ -685,6 +716,22 @@ int main(int argc, const char **argv) {
fprintf(stderr, "spl_cli: hit breakpoint (run with -g to debug)\n");
}
if (o.stats) {
fprintf(stderr, "spl_cli: stats steps=%llu calls=%llu\n", (unsigned long long)vm.steps,
(unsigned long long)vm.ncalls);
}
if (o.profile) {
const char *prefix = o.profile_out ? o.profile_out : "build/profile";
char folded[1024];
char table[1024];
snprintf(folded, sizeof folded, "%s.folded", prefix);
snprintf(table, sizeof table, "%s.table", prefix);
spl_vm_profile_dump_folded(&vm, folded);
spl_vm_profile_dump_table(&vm, table);
fprintf(stderr, "spl_cli: profile written to %s / %s\n", folded, table);
}
free(spl_argv);
spl_vm_drop(&vm);
spl_prog_drop(&prog);
+47 -38
View File
@@ -19,6 +19,7 @@ void spl_prog_init(spl_prog_t *prog) {
vec_init(prog->natives);
vec_init(prog->strtab);
vec_init(prog->gdata);
vec_push(prog->gdata, (spl_vm_gdata_t){0});
map_init(prog->symtab, MAP_HASH_STR, MAP_CMP_STR);
prog->debug = NULL;
}
@@ -126,44 +127,20 @@ static inline void wr64(unsigned char **p, spl_vm_val_t v) {
/* Round n up to next multiple of 8 */
#define ALIGN8(n) (((n) + 7) & ~7)
int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
FILE *f;
unsigned char *data;
long len;
static int spl_prog_parse(const unsigned char *data, usize len, spl_prog_t *prog) {
const unsigned char *p;
spl_vm_val_t nfuncs, ninsns, nnatives, nstrs, ndata;
if (!fname || !prog)
if (!data || !prog)
return -1;
f = fopen(fname, "rb");
if (!f)
if (len < 48)
return -1;
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 48) {
fclose(f);
return -1;
}
data = (unsigned char *)malloc((size_t)len);
if (!data) {
fclose(f);
return -1;
}
if (fread(data, 1, (size_t)len, f) != (size_t)len) {
free(data);
fclose(f);
return -1;
}
fclose(f);
p = data;
/* magic */
if (p[0] != 'S' || p[1] != 'P' || p[2] != 'L' || p[3] != 'B' || p[4] != 'I' || p[5] != 'N' ||
p[6] != '\0' || p[7] != '\0') {
free(data);
return -1;
}
p += 8;
@@ -185,7 +162,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
func.name = (char *)malloc((usize)nlen);
if (!func.name) {
free(data);
return -1;
}
memcpy(func.name, p, (usize)nlen);
@@ -201,7 +177,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
/* ---- instructions ---- */
for (spl_vm_val_t i = 0; i < ninsns; i++) {
if ((size_t)(p - data) + 12 > (size_t)len) {
free(data);
return -1;
}
spl_vm_ins_t ins;
@@ -220,7 +195,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
nat.name = (char *)malloc((usize)nlen);
if (!nat.name) {
free(data);
return -1;
}
memcpy(nat.name, p, (usize)nlen);
@@ -237,7 +211,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
usize pad = ALIGN8((usize)slen) - (usize)slen;
char *s = (char *)malloc((usize)slen + 1);
if (!s) {
free(data);
return -1;
}
memcpy(s, p, (usize)slen);
@@ -253,7 +226,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
usize pad = ALIGN8(entry.size) - entry.size;
entry.data = (unsigned char *)malloc(entry.size);
if (!entry.data) {
free(data);
return -1;
}
memcpy(entry.data, p, entry.size);
@@ -278,10 +250,47 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
}
}
free(data);
return 0;
}
int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
FILE *f;
unsigned char *data;
long len;
int rc;
if (!fname || !prog)
return -1;
f = fopen(fname, "rb");
if (!f)
return -1;
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 48) {
fclose(f);
return -1;
}
data = (unsigned char *)malloc((size_t)len);
if (!data) {
fclose(f);
return -1;
}
if (fread(data, 1, (size_t)len, f) != (size_t)len) {
free(data);
fclose(f);
return -1;
}
fclose(f);
rc = spl_prog_parse(data, (usize)len, prog);
free(data);
return rc;
}
int spl_prog_load_from_buffer(const char *buffer, usize len, spl_prog_t *prog) {
return spl_prog_parse((const unsigned char *)buffer, len, prog);
}
int spl_prog_store_to_file(const char *fname, spl_prog_t *prog) {
unsigned char *buf, *p;
spl_vm_val_t i;
@@ -296,7 +305,7 @@ int spl_prog_store_to_file(const char *fname, spl_prog_t *prog) {
ninsns = vec_size(prog->insns);
nnatives = vec_size(prog->natives);
nstrs = vec_size(prog->strtab);
ndata = vec_size(prog->gdata);
ndata = vec_size(prog->gdata) - 1; /* jump 0 node */
/* Calculate total size */
total = 8 /* magic */
@@ -325,7 +334,7 @@ int spl_prog_store_to_file(const char *fname, spl_prog_t *prog) {
}
/* gdata */
for (i = 0; i < ndata; i++) {
for (i = 1; i < vec_size(prog->gdata); i++) {
usize dsize = vec_at(prog->gdata, i).size;
total += 8 + ALIGN8(dsize); /* dsize + data(pad) */
}
@@ -398,7 +407,7 @@ int spl_prog_store_to_file(const char *fname, spl_prog_t *prog) {
}
/* ---- global data ---- */
for (i = 0; i < ndata; i++) {
for (i = 1; i <= ndata; i++) {
spl_vm_gdata_t *entry = &vec_at(prog->gdata, i);
pad = ALIGN8(entry->size) - entry->size;
wr64(&p, entry->size);
@@ -452,7 +461,7 @@ int spl_prog_add_data(spl_prog_t *prog, void *ptr, usize size) {
memcpy(entry.data, ptr, size);
entry.size = size;
vec_push(prog->gdata, entry);
return vec_size(prog->gdata);
return (int)(vec_size(prog->gdata) - 1);
}
spl_vm_func_t *spl_prog_get_func(spl_prog_t *prog, const char *name) {
@@ -528,4 +537,4 @@ void spl_vm_ins_dump(spl_vm_ins_t *ins, spl_vm_val_t addr) {
printf(" %s", spl_vm_type_kind_name((spl_vm_kind_t)ins->type));
printf(" %zd:%zx", ins->imm, ins->imm);
printf("\n");
}
}
+29 -29
View File
@@ -9,8 +9,15 @@
#include <stddef.h>
#include <stdint.h>
#ifndef SPL_USIZE_TYPEDEF
#define SPL_USIZE_TYPEDEF
typedef uintptr_t usize;
#endif
#ifndef SPL_ISIZE_TYPEDEF
#define SPL_ISIZE_TYPEDEF
typedef intptr_t isize;
#endif
typedef enum {
SPL_VOID,
@@ -90,6 +97,7 @@ typedef enum {
/* 间接内存访问 */ \
X(SPL_LOAD, "load", 0, 1, 1, "load sizeof(type)-bits zero-extended") \
X(SPL_STORE, "store", 0, 2, 0, "store low sizeof(type)-bits") \
X(SPL_MEMCPY, "memcpy", 0, 3, 0, "copy n bytes: pop dst, src, n (VM address space)") \
/* 原生接口 */ \
X(SPL_NCALL, "ncall", 1, 0, 1, "call native function by index") \
X(SPL_NLIB, "nlib", 1, 0, 0, "dlopen library (name idx)") \
@@ -103,6 +111,7 @@ typedef enum {
#define X(opcode, name, argc, pop, push, desc) opcode,
SPL_OPCODES(X)
#undef X
SPL_OPCODE_COUNT,
} spl_vm_opcode_t;
/*
@@ -115,38 +124,26 @@ typedef enum {
* [strtab] each: slen, str(pad8)
* [gdata] each: dsize(8), data(dsize bytes, padded to 8)
*
* === Calling Convention ===
* === Aggregate Return ABI (sret) ===
*
* 凡返回值类型为聚合(struct / union / enum / array / shape),
* 采用调用者分配 / 被调用者填充的间接返回约定:
*
* Before CALL:
* - args pushed left-to-right
* - target address pushed last
* - caller 在自己的帧里 alloca 一块 RetTy 大小的空间
* - 该空间的地址作为第 0 个实参先压栈
* - 其余实参照常从左到右压栈
* - target 地址最后压栈
* - CALL imm = 普通实参个数 + 1
*
* CALL (imm = nargs):
* 1. pop target address
* 2. callstack[cp++] = {saved_fp, saved_ip, nargs}
* 3. fp = sp - nargs (fp points to arg0)
* 4. ip = target address
* Callee 进入后:
* - fp+0 存放 sret 指针
* - 形参 i 位于 fp + 8*(i+1)
* - 返回值直接写入 *(fp+0),然后执行 RET void
*
* ALLOC k:
* sp += k (slots zeroed; local[j] = stacks.data[fp + nargs + j])
*
* LADDR imm:
* push &stacks.data[fp + imm]
* (imm < nargs accesses args; imm >= nargs accesses locals)
*
* GADDR imm:
* push prog->gdata[imm] (pointer to global data blob)
*
* RET:
* 1. pop retval if non-void type
* 2. sp = fp
* 3. pop frame; fp = saved_fp, ip = saved_ip
* 4. push retval if non-void type
*
* CALLI (indirect call):
* - stack before: ..., arg0, ..., argN-1, nargs, func_addr
* 1. pop func_addr, then pop nargs
* 2. same as CALL steps 2-4
* Caller 侧:
* - 调用完成后,CALL 结果槽保存 sret 指针;
* 后续对聚合值的所有读取都通过该指针进行
*/
#define SPL_BINFMT_MAGIC "SPLBIN\0\0"
@@ -170,7 +167,9 @@ typedef struct spl_vm_func {
typedef VEC(spl_vm_func_t) spl_vm_func_vec_t;
/* Native function pointer type */
typedef spl_vm_val_t (*spl_vm_fn_t)(int nargs, spl_vm_val_t *args);
struct spl_vm;
typedef struct spl_vm spl_vm_t;
typedef spl_vm_val_t (*spl_vm_fn_t)(spl_vm_t *vm, int nargs, spl_vm_val_t *args);
typedef struct spl_vm_native {
char *name;
spl_vm_val_t idx_of_strtab;
@@ -202,6 +201,7 @@ void spl_prog_drop(spl_prog_t *prog);
void spl_prog_dump(spl_prog_t *prog);
int spl_prog_load_from_file(const char *fname, spl_prog_t *prog);
int spl_prog_load_from_buffer(const char *buffer, usize len, spl_prog_t *prog);
int spl_prog_store_to_file(const char *fname, spl_prog_t *prog);
int spl_prog_add_instr(spl_prog_t *prog, uint8_t opcode, uint8_t type, spl_vm_val_t imm);
+90
View File
@@ -0,0 +1,90 @@
#ifndef __SPL_RT_H__
#define __SPL_RT_H__
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#define SPL_FN static inline
#elif defined(__GNUC__) || defined(__clang__)
#define SPL_FN static __inline__
#else
#define SPL_FN static
#endif
#include <stdint.h>
typedef uint8_t u8;
typedef int8_t i8;
typedef uint16_t u16;
typedef int16_t i16;
typedef uint32_t u32;
typedef int32_t i32;
typedef uint64_t u64;
typedef int64_t i64;
#ifndef SPL_USIZE_TYPEDEF
#define SPL_USIZE_TYPEDEF
typedef u64 usize;
#endif
#ifndef SPL_ISIZE_TYPEDEF
#define SPL_ISIZE_TYPEDEF
typedef i64 isize;
#endif
SPL_FN void spl_panic(const char *msg) {
fprintf(stderr, "%s\n", msg ? msg : "panic");
exit(1);
}
SPL_FN void spl_trap(void) { abort(); }
#include "spl_rt_core.h"
SPL_FN void *vm_fopen(void *path, void *mode) {
return (void *)fopen((const char *)path, (const char *)mode);
}
SPL_FN i32 vm_fclose(void *f) { return f ? fclose((FILE *)f) : 0; }
SPL_FN u64 vm_fread(void *buf, u64 sz, u64 n, void *f) {
return f ? (u64)fread(buf, (size_t)sz, (size_t)n, (FILE *)f) : 0;
}
SPL_FN u64 vm_fwrite(void *buf, u64 sz, u64 n, void *f) {
return f ? (u64)fwrite(buf, (size_t)sz, (size_t)n, (FILE *)f) : 0;
}
SPL_FN u64 vm_fsize(void *f) {
long cur, end;
if (!f)
return 0;
cur = ftell((FILE *)f);
if (cur < 0)
return 0;
if (fseek((FILE *)f, 0, SEEK_END) != 0)
return 0;
end = ftell((FILE *)f);
fseek((FILE *)f, cur, SEEK_SET);
return end < 0 ? 0 : (u64)end;
}
SPL_FN void *vm_stdin(void) { return (void *)stdin; }
SPL_FN void *vm_stdout(void) { return (void *)stdout; }
SPL_FN void *vm_stderr(void) { return (void *)stderr; }
SPL_FN i32 vm_putchar(i32 c) { return fputc(c, stdout); }
SPL_FN i32 vm_getchar(void) { return fgetc(stdin); }
SPL_FN void vm_exit(i32 code) { exit(code); }
SPL_FN u64 vm_timestamp(u64 kind) {
time_t sec = time(NULL);
if (kind == 1)
return (u64)sec;
if (kind == 2)
return 0;
if (kind == 4)
return (u64)sec * 1000000000ull;
return 0;
}
#define SPL_VM_EMBED_IMPLIMENTATION
#include "spl_vm_embed.h"
#endif /* __SPL_RT_H__ */
+691
View File
@@ -0,0 +1,691 @@
#ifndef __SPL_RT_CORE_H__
#define __SPL_RT_CORE_H__
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "spl_asp.h"
#ifndef SPL_CORE_FN
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#define SPL_CORE_FN static inline
#elif defined(__GNUC__) || defined(__clang__)
#define SPL_CORE_FN static __inline__
#else
#define SPL_CORE_FN static
#endif
#endif
/* ================================================================
* 地址空间:identity(native/编译器自身)
* ================================================================ */
SPL_CORE_FN unsigned char *spl_asp_id_xlate(void *ctx, uintptr_t off, uintptr_t size) {
(void)ctx;
(void)size;
return (unsigned char *)off;
}
SPL_CORE_FN uintptr_t spl_asp_id_alloc(void *ctx, uintptr_t size) {
(void)ctx;
return (uintptr_t)malloc((size_t)size);
}
SPL_CORE_FN uintptr_t spl_asp_id_realloc(void *ctx, uintptr_t off, uintptr_t size) {
(void)ctx;
return (uintptr_t)realloc((void *)off, (size_t)size);
}
SPL_CORE_FN void spl_asp_id_free(void *ctx, uintptr_t off) {
(void)ctx;
free((void *)off);
}
SPL_CORE_FN uintptr_t spl_asp_id_to_off(void *ctx, const void *host, uintptr_t size) {
(void)ctx;
(void)size;
return (uintptr_t)host;
}
static const spl_asp_t spl_asp_identity = {
NULL, spl_asp_id_xlate, spl_asp_id_alloc, spl_asp_id_realloc, spl_asp_id_free,
spl_asp_id_to_off,
};
#define SPL_ASP_ID ((spl_asp_t *)&spl_asp_identity)
/* 偏移 -> 宿主(不做边界检查)。 */
SPL_CORE_FN unsigned char *spl_x(spl_asp_t *asp, uintptr_t off) {
return asp->xlate(asp->ctx, off, 0);
}
SPL_CORE_FN void *spl_xp(spl_asp_t *asp, uintptr_t off, uintptr_t size) {
return asp->xlate(asp->ctx, off, size);
}
/* ---------------- mem ---------------- */
SPL_CORE_FN void *spl_alloc_ex(spl_asp_t *asp, uintptr_t sz) {
if (sz == 0)
return NULL;
return (void *)asp->alloc(asp->ctx, sz);
}
SPL_CORE_FN void spl_free_ex(spl_asp_t *asp, void *p) {
if (p)
asp->free(asp->ctx, (uintptr_t)p);
}
SPL_CORE_FN void *spl_realloc_ex(spl_asp_t *asp, void *p, uintptr_t sz) {
if (!p)
return spl_alloc_ex(asp, sz);
if (sz == 0) {
asp->free(asp->ctx, (uintptr_t)p);
return NULL;
}
return (void *)asp->realloc(asp->ctx, (uintptr_t)p, sz);
}
SPL_CORE_FN void *vm_memcpy(void *dst, const void *src, uintptr_t n) {
if (n)
memcpy(dst, src, (size_t)n);
return dst;
}
SPL_CORE_FN void *vm_memmove(void *dst, const void *src, uintptr_t n) {
if (n)
memmove(dst, src, (size_t)n);
return dst;
}
SPL_CORE_FN intptr_t vm_memcmp(const void *a, const void *b, uintptr_t n) {
if (n == 0)
return 0;
return (intptr_t)memcmp(a, b, (size_t)n);
}
SPL_CORE_FN int spl_bytes_eq_ex(spl_asp_t *asp, const void *a, uintptr_t alen, const void *b,
uintptr_t blen) {
(void)asp;
if (alen != blen)
return 0;
if (alen == 0)
return 1;
if (!a || !b)
return 0;
return memcmp(a, b, (size_t)alen) == 0;
}
/* ---------------- Vec ---------------- */
typedef struct {
uintptr_t raw;
uintptr_t data;
uintptr_t len;
uintptr_t cap;
uintptr_t elem_size;
uintptr_t elem_align;
} spl_vec_t;
SPL_CORE_FN int spl_align_ok(uintptr_t a) { return a != 0 && (a & (a - 1)) == 0; }
SPL_CORE_FN int spl_vec_is_valid(const spl_vec_t *v) {
uintptr_t a;
if (v->elem_size == 0)
return 0;
a = v->elem_align;
if (a == 0 || (a & (a - 1)) != 0)
return 0;
if (v->len > v->cap)
return 0;
if (v->cap == 0)
return v->raw == 0 && v->data == 0 && v->len == 0;
if (v->raw == 0 || v->data == 0)
return 0;
return (v->data & (a - 1)) == 0;
}
SPL_CORE_FN void spl_grow_capacity(uintptr_t current, uintptr_t min_cap, uintptr_t need,
uintptr_t *out) {
uintptr_t cap = current < min_cap ? min_cap : current;
uintptr_t limit = ~(uintptr_t)0;
while (cap < need) {
if (cap > limit / 2) {
cap = need;
break;
}
cap = cap * 2;
}
*out = cap;
}
SPL_CORE_FN int spl_vec_allocate_ex(spl_asp_t *asp, spl_vec_t *out, uintptr_t capacity,
uintptr_t elem_size, uintptr_t elem_align) {
uintptr_t bytes, mask, addr;
uintptr_t raw;
if (elem_size == 0)
return 1;
if (!spl_align_ok(elem_align))
return 4;
out->raw = 0;
out->data = 0;
out->len = 0;
out->cap = 0;
out->elem_size = elem_size;
out->elem_align = elem_align;
if (capacity == 0)
return 0;
if (capacity > ~(uintptr_t)0 / elem_size)
return 3;
bytes = capacity * elem_size;
mask = elem_align - 1;
if (bytes > ~(uintptr_t)0 - mask)
return 3;
raw = asp->alloc(asp->ctx, bytes + mask);
if (!raw)
return 3;
addr = raw;
if (addr > ~(uintptr_t)0 - mask) {
asp->free(asp->ctx, raw);
return 3;
}
out->raw = raw;
out->data = (addr + mask) & ~mask;
out->cap = capacity;
return 0;
}
SPL_CORE_FN int spl_vec_reserve_raw_ex(spl_asp_t *asp, spl_vec_t *v, uintptr_t need) {
uintptr_t new_cap = 0, used = 0, old;
spl_vec_t fresh;
int rc;
if (need <= v->cap)
return 0;
spl_grow_capacity(v->cap, 8, need, &new_cap);
rc = spl_vec_allocate_ex(asp, &fresh, new_cap, v->elem_size, v->elem_align);
if (rc)
return rc;
if (v->len != 0) {
if (v->len > ~(uintptr_t)0 / v->elem_size) {
asp->free(asp->ctx, fresh.raw);
return 3;
}
used = v->len * v->elem_size;
}
if (used)
memcpy(spl_x(asp, fresh.data), spl_x(asp, v->data), used);
fresh.len = v->len;
old = v->raw;
*v = fresh;
if (old)
asp->free(asp->ctx, old);
return 0;
}
SPL_CORE_FN void spl_vec_release_ex(spl_asp_t *asp, spl_vec_t *v) {
uintptr_t es = v->elem_size, ea = v->elem_align;
if (v->raw)
asp->free(asp->ctx, v->raw);
v->raw = 0;
v->data = 0;
v->len = 0;
v->cap = 0;
v->elem_size = es;
v->elem_align = ea;
}
SPL_CORE_FN int spl_vec_valid_ex(const void *vv) {
return vv ? spl_vec_is_valid((const spl_vec_t *)vv) : 0;
}
SPL_CORE_FN int spl_vec_push_raw_ex(spl_asp_t *asp, void *vv, const void *elem) {
spl_vec_t *v = (spl_vec_t *)vv;
if (!v || !elem)
return 0;
if (v->len < v->cap) {
memcpy(spl_x(asp, v->data + v->len * v->elem_size), elem, v->elem_size);
v->len++;
return 1;
}
return 0;
}
SPL_CORE_FN int spl_vec_push_ex(spl_asp_t *asp, void *vv, const void *elem) {
spl_vec_t *v = (spl_vec_t *)vv;
int rc;
if (!v || !elem || !spl_vec_is_valid(v))
return 1;
if (v->len < v->cap) {
memcpy(spl_x(asp, v->data + v->len * v->elem_size), elem, v->elem_size);
v->len++;
return 0;
}
rc = spl_vec_reserve_raw_ex(asp, v, v->len + 1);
if (rc)
return rc;
memmove(spl_x(asp, v->data + v->len * v->elem_size), elem, v->elem_size);
v->len++;
return 0;
}
SPL_CORE_FN int spl_vec_get_ex(spl_asp_t *asp, const void *vv, uintptr_t index, void **out) {
const spl_vec_t *v = (const spl_vec_t *)vv;
unsigned char *p;
if (!v || !out)
return 1;
if (!spl_vec_is_valid(v))
return 1;
if (index >= v->len)
return 2;
p = spl_x(asp, v->data) + index * v->elem_size;
*out = (void *)(uintptr_t)asp->to_off(asp->ctx, p, v->elem_size);
return 0;
}
SPL_CORE_FN int spl_vec_allocate_out_ex(spl_asp_t *asp, void *out, uintptr_t capacity,
uintptr_t elem_size, uintptr_t elem_align) {
if (!out)
return 1;
return spl_vec_allocate_ex(asp, (spl_vec_t *)out, capacity, elem_size, elem_align);
}
SPL_CORE_FN int spl_vec_reserve_raw_void_ex(spl_asp_t *asp, void *vv, uintptr_t need) {
if (!vv)
return 1;
return spl_vec_reserve_raw_ex(asp, (spl_vec_t *)vv, need);
}
SPL_CORE_FN int spl_vec_append_ex(spl_asp_t *asp, void *vv, const void *elems, uintptr_t count) {
spl_vec_t *v = (spl_vec_t *)vv;
uintptr_t copy_bytes, new_len, used, source_offset = 0;
int source_inside = 0, rc;
if (!v || !spl_vec_is_valid(v))
return 1;
if (count == 0)
return 0;
if (!elems)
return 1;
if (count > ~(uintptr_t)0 / v->elem_size)
return 3;
copy_bytes = count * v->elem_size;
if (v->len > ~(uintptr_t)0 - count)
return 3;
new_len = v->len + count;
used = v->len * v->elem_size;
if (used) {
unsigned char *base = spl_x(asp, v->data);
uintptr_t source = (uintptr_t)elems;
if (source >= (uintptr_t)base && source - (uintptr_t)base < used) {
source_offset = source - (uintptr_t)base;
if (copy_bytes > used - source_offset)
return 2;
source_inside = 1;
}
}
rc = spl_vec_reserve_raw_ex(asp, v, new_len);
if (rc)
return rc;
if (source_inside)
elems = (const void *)(spl_x(asp, v->data) + source_offset);
memmove(spl_x(asp, v->data + used), elems, copy_bytes);
v->len = new_len;
return 0;
}
SPL_CORE_FN int spl_vec_set_ex(spl_asp_t *asp, void *vv, uintptr_t index, const void *elem) {
spl_vec_t *v = (spl_vec_t *)vv;
if (!v || !elem || !spl_vec_is_valid(v))
return 1;
if (index >= v->len)
return 2;
memmove(spl_x(asp, v->data + index * v->elem_size), elem, v->elem_size);
return 0;
}
SPL_CORE_FN int spl_vec_pop_ex(spl_asp_t *asp, void *vv, void *out) {
spl_vec_t *v = (spl_vec_t *)vv;
if (!v || !out || !spl_vec_is_valid(v))
return 1;
if (v->len == 0)
return 2;
memmove(out, spl_x(asp, v->data + (v->len - 1) * v->elem_size), v->elem_size);
v->len--;
return 0;
}
SPL_CORE_FN int spl_vec_clone_ex(spl_asp_t *asp, void *outp, const void *srcp) {
spl_vec_t *out = (spl_vec_t *)outp;
const spl_vec_t *src = (const spl_vec_t *)srcp;
spl_vec_t fresh;
uintptr_t bytes;
int rc;
if (!out || !src || !spl_vec_is_valid(out) || !spl_vec_is_valid(src))
return 1;
if (out->elem_size != src->elem_size || out->elem_align != src->elem_align)
return 1;
fresh.raw = 0;
fresh.data = 0;
fresh.len = 0;
fresh.cap = 0;
fresh.elem_size = src->elem_size;
fresh.elem_align = src->elem_align;
rc = spl_vec_reserve_raw_ex(asp, &fresh, src->len);
if (rc)
return rc;
bytes = src->len * src->elem_size;
if (bytes)
memcpy(spl_x(asp, fresh.data), spl_x(asp, src->data), bytes);
fresh.len = src->len;
if (out->raw)
asp->free(asp->ctx, out->raw);
*out = fresh;
return 0;
}
SPL_CORE_FN int spl_vec_drop_ex(spl_asp_t *asp, void *vv) {
if (vv)
spl_vec_release_ex(asp, (spl_vec_t *)vv);
return 0;
}
/* ---------------- Map ---------------- */
typedef struct {
spl_vec_t keys;
spl_vec_t vals;
spl_vec_t states;
uintptr_t size;
uintptr_t key_size;
uintptr_t val_size;
uintptr_t hash_kind;
} spl_map_t;
SPL_CORE_FN uintptr_t spl_hash_usize_value(uintptr_t value) {
uintptr_t prime = 1099511628211ull;
uintptr_t h = value;
h = (h ^ (h >> 30)) * prime;
h = h ^ (h >> 27);
h = h * prime;
h = h ^ (h >> 31);
if (h == 0)
return prime;
return h;
}
SPL_CORE_FN uintptr_t spl_hash_bytes(const void *data, uintptr_t len) {
const unsigned char *p = (const unsigned char *)data;
uintptr_t h = 14695981039346656037ull;
uintptr_t prime = 1099511628211ull;
uintptr_t i;
for (i = 0; i < len; i++)
h = (h ^ (uintptr_t)p[i]) * prime;
return h;
}
SPL_CORE_FN uintptr_t spl_hash_bytes_ex(spl_asp_t *asp, const void *data, uintptr_t len) {
return spl_hash_bytes(spl_x(asp, (uintptr_t)data), len);
}
SPL_CORE_FN uintptr_t spl_map_hash_ex(spl_asp_t *asp, const spl_map_t *m, const void *key) {
const uintptr_t *v;
if (m->hash_kind == 0)
return spl_hash_usize_value(*(const uintptr_t *)key);
if (m->hash_kind == 2)
return 3;
v = (const uintptr_t *)key;
return spl_hash_bytes_ex(asp, (const void *)v[0], v[1]);
}
SPL_CORE_FN int spl_map_eq_ex(spl_asp_t *asp, const spl_map_t *m, const void *a, const void *b) {
const uintptr_t *x;
const uintptr_t *y;
if (m->hash_kind == 0 || m->hash_kind == 2)
return *(const uintptr_t *)a == *(const uintptr_t *)b;
x = (const uintptr_t *)a;
y = (const uintptr_t *)b;
if (x[1] != y[1])
return 0;
if (x[1] == 0)
return 1;
return memcmp(spl_x(asp, x[0]), spl_x(asp, y[0]), (size_t)x[1]) == 0;
}
SPL_CORE_FN int spl_map_valid(const spl_map_t *m) {
if (m->key_size == 0 || m->val_size == 0)
return 0;
if (m->keys.len != m->states.len || m->vals.len != m->states.len)
return 0;
if (m->size > m->states.len)
return 0;
return 1;
}
SPL_CORE_FN uintptr_t spl_map_find_slot_ex(spl_asp_t *asp, const spl_map_t *m, const void *key) {
uintptr_t mask, i, probes;
unsigned char *st;
if (m->states.cap == 0)
return ~(uintptr_t)0;
mask = m->states.cap - 1;
i = spl_map_hash_ex(asp, m, key) & mask;
st = spl_x(asp, m->states.data);
probes = 0;
while (probes < m->states.cap && st[i] != 0) {
if (st[i] == 1 &&
spl_map_eq_ex(asp, m, spl_x(asp, m->keys.data + i * m->key_size), key))
return i;
i = (i + 1) & mask;
probes++;
}
return ~(uintptr_t)0;
}
SPL_CORE_FN void spl_map_place_ex(spl_asp_t *asp, spl_map_t *m, const void *key, const void *val) {
uintptr_t mask = m->states.cap - 1;
uintptr_t i = spl_map_hash_ex(asp, m, key) & mask;
unsigned char *st = spl_x(asp, m->states.data);
uintptr_t first_deleted = ~(uintptr_t)0;
uintptr_t probes = 0;
while (probes < m->states.cap && st[i] != 0) {
if (st[i] == 2 && first_deleted == ~(uintptr_t)0)
first_deleted = i;
i = (i + 1) & mask;
probes++;
}
if (first_deleted != ~(uintptr_t)0)
i = first_deleted;
memcpy(spl_x(asp, m->keys.data + i * m->key_size), key, m->key_size);
memcpy(spl_x(asp, m->vals.data + i * m->val_size), val, m->val_size);
st[i] = 1;
m->size++;
}
SPL_CORE_FN int spl_map_reserve_ex(spl_asp_t *asp, spl_map_t *m, uintptr_t need) {
uintptr_t new_cap = 0, i;
spl_map_t fresh;
int rc;
if (!spl_map_valid(m))
return 1;
if (need <= m->states.cap)
return 0;
spl_grow_capacity(m->states.cap, 8, need, &new_cap);
fresh = *m;
fresh.keys.raw = 0;
fresh.keys.data = 0;
fresh.keys.len = 0;
fresh.keys.cap = 0;
fresh.vals.raw = 0;
fresh.vals.data = 0;
fresh.vals.len = 0;
fresh.vals.cap = 0;
fresh.states.raw = 0;
fresh.states.data = 0;
fresh.states.len = 0;
fresh.states.cap = 0;
fresh.size = 0;
rc = spl_vec_reserve_raw_ex(asp, &fresh.keys, new_cap);
if (rc)
goto fail;
rc = spl_vec_reserve_raw_ex(asp, &fresh.vals, new_cap);
if (rc)
goto fail;
rc = spl_vec_reserve_raw_ex(asp, &fresh.states, new_cap);
if (rc)
goto fail;
fresh.keys.len = new_cap;
fresh.vals.len = new_cap;
fresh.states.len = new_cap;
memset(spl_x(asp, fresh.states.data), 0, new_cap);
for (i = 0; i < m->states.len; i++) {
if (spl_x(asp, m->states.data)[i] == 1)
spl_map_place_ex(asp, &fresh, spl_x(asp, m->keys.data + i * m->key_size),
spl_x(asp, m->vals.data + i * m->val_size));
}
spl_vec_release_ex(asp, &m->keys);
spl_vec_release_ex(asp, &m->vals);
spl_vec_release_ex(asp, &m->states);
*m = fresh;
return 0;
fail:
spl_vec_release_ex(asp, &fresh.keys);
spl_vec_release_ex(asp, &fresh.vals);
spl_vec_release_ex(asp, &fresh.states);
return rc;
}
SPL_CORE_FN int spl_map_valid_ex(const void *mm) {
return mm ? spl_map_valid((const spl_map_t *)mm) : 0;
}
SPL_CORE_FN int spl_map_reserve_void_ex(spl_asp_t *asp, void *mm, uintptr_t need) {
if (!mm)
return 1;
return spl_map_reserve_ex(asp, (spl_map_t *)mm, need);
}
SPL_CORE_FN int spl_map_put_ex(spl_asp_t *asp, void *mm, const void *key, const void *val) {
spl_map_t *m = (spl_map_t *)mm;
uintptr_t existing;
if (!m || !key || !val || !spl_map_valid(m))
return 1;
existing = spl_map_find_slot_ex(asp, m, key);
if (existing != ~(uintptr_t)0) {
memcpy(spl_x(asp, m->vals.data + existing * m->val_size), val, m->val_size);
return 0;
}
if (m->states.cap == 0) {
int rc = spl_map_reserve_ex(asp, m, 8);
if (rc)
return rc;
} else {
uintptr_t next_size, scaled;
if (m->size > ~(uintptr_t)0 - 1)
return 3;
next_size = m->size + 1;
if (next_size > ~(uintptr_t)0 / 128)
return 3;
scaled = next_size * 128;
if (scaled / m->states.cap >= 70) {
int rc;
if (m->states.cap > ~(uintptr_t)0 / 2)
return 3;
rc = spl_map_reserve_ex(asp, m, m->states.cap * 2);
if (rc)
return rc;
}
}
spl_map_place_ex(asp, m, key, val);
return 0;
}
SPL_CORE_FN int spl_map_get_ex(spl_asp_t *asp, void *mm, const void *key, void *out) {
spl_map_t *m = (spl_map_t *)mm;
uintptr_t i;
if (!m || !key || !out || !spl_map_valid(m))
return 1;
i = spl_map_find_slot_ex(asp, m, key);
if (i == ~(uintptr_t)0)
return 1;
memcpy(out, spl_x(asp, m->vals.data + i * m->val_size), m->val_size);
return 0;
}
SPL_CORE_FN int spl_map_del_ex(spl_asp_t *asp, void *mm, const void *key) {
spl_map_t *m = (spl_map_t *)mm;
uintptr_t i;
if (!m || !key || !spl_map_valid(m))
return 1;
i = spl_map_find_slot_ex(asp, m, key);
if (i == ~(uintptr_t)0)
return 1;
spl_x(asp, m->states.data)[i] = 2;
m->size--;
return 0;
}
SPL_CORE_FN int spl_map_clear_ex(spl_asp_t *asp, void *mm) {
spl_map_t *m = (spl_map_t *)mm;
if (!m)
return 0;
if (m->states.data && m->states.len)
memset(spl_x(asp, m->states.data), 0, m->states.len);
m->size = 0;
return 0;
}
SPL_CORE_FN int spl_map_drop_ex(spl_asp_t *asp, void *mm) {
spl_map_t *m = (spl_map_t *)mm;
if (!m)
return 0;
spl_vec_release_ex(asp, &m->keys);
spl_vec_release_ex(asp, &m->vals);
spl_vec_release_ex(asp, &m->states);
m->size = 0;
return 0;
}
/* ================================================================
* 公开 C 符号(identity):供 native 编译器直接调用
* ================================================================ */
SPL_CORE_FN void *vm_alloc(uintptr_t sz) { return spl_alloc_ex(SPL_ASP_ID, sz); }
SPL_CORE_FN void vm_free(void *p) { spl_free_ex(SPL_ASP_ID, p); }
SPL_CORE_FN void *vm_realloc(void *p, uintptr_t sz) { return spl_realloc_ex(SPL_ASP_ID, p, sz); }
SPL_CORE_FN int vm_bytes_eq(const void *a, uintptr_t alen, const void *b, uintptr_t blen) {
return spl_bytes_eq_ex(SPL_ASP_ID, a, alen, b, blen);
}
SPL_CORE_FN int vm_vec_valid(const void *v) { return spl_vec_valid_ex(v); }
SPL_CORE_FN int vm_vec_push_raw(void *v, const void *e) {
return spl_vec_push_raw_ex(SPL_ASP_ID, v, e);
}
SPL_CORE_FN int vm_vec_push(void *v, const void *e) { return spl_vec_push_ex(SPL_ASP_ID, v, e); }
SPL_CORE_FN int vm_vec_get(const void *v, uintptr_t i, void **o) {
return spl_vec_get_ex(SPL_ASP_ID, v, i, o);
}
SPL_CORE_FN int vm_vec_allocate(void *o, uintptr_t c, uintptr_t es, uintptr_t ea) {
return spl_vec_allocate_out_ex(SPL_ASP_ID, o, c, es, ea);
}
SPL_CORE_FN int vm_vec_reserve_raw(void *v, uintptr_t n) {
return spl_vec_reserve_raw_void_ex(SPL_ASP_ID, v, n);
}
SPL_CORE_FN int vm_vec_append(void *v, const void *e, uintptr_t n) {
return spl_vec_append_ex(SPL_ASP_ID, v, e, n);
}
SPL_CORE_FN int vm_vec_set(void *v, uintptr_t i, const void *e) {
return spl_vec_set_ex(SPL_ASP_ID, v, i, e);
}
SPL_CORE_FN int vm_vec_pop(void *v, void *o) { return spl_vec_pop_ex(SPL_ASP_ID, v, o); }
SPL_CORE_FN int vm_vec_clone(void *o, const void *s) { return spl_vec_clone_ex(SPL_ASP_ID, o, s); }
SPL_CORE_FN int vm_vec_drop(void *v) { return spl_vec_drop_ex(SPL_ASP_ID, v); }
SPL_CORE_FN int vm_map_valid(const void *m) { return spl_map_valid_ex(m); }
SPL_CORE_FN int vm_map_reserve(void *m, uintptr_t n) {
return spl_map_reserve_void_ex(SPL_ASP_ID, m, n);
}
SPL_CORE_FN int vm_map_put(void *m, const void *k, const void *v) {
return spl_map_put_ex(SPL_ASP_ID, m, k, v);
}
SPL_CORE_FN int vm_map_get(void *m, const void *k, void *o) {
return spl_map_get_ex(SPL_ASP_ID, m, k, o);
}
SPL_CORE_FN int vm_map_del(void *m, const void *k) { return spl_map_del_ex(SPL_ASP_ID, m, k); }
SPL_CORE_FN int vm_map_clear(void *m) { return spl_map_clear_ex(SPL_ASP_ID, m); }
SPL_CORE_FN int vm_map_drop(void *m) { return spl_map_drop_ex(SPL_ASP_ID, m); }
#endif /* __SPL_RT_CORE_H__ */
+730 -178
View File
File diff suppressed because it is too large. Load diff
+878 -213
View File
File diff suppressed because it is too large. Load diff
+94 -5
View File
@@ -3,12 +3,27 @@
#ifndef __SPL_VM_H__
#define __SPL_VM_H__
#include "include/core_map.h"
#include "include/core_vec.h"
#include "spl_asp.h"
#include "spl_mcode.h"
#include <stdint.h>
#define SPL_STACK_CANARY ((spl_vm_val_t)0xDEADBEEFCAFEBABEull)
/* 统一偏移地址空间:SPL 侧指针一律是句柄/偏移,0 保留为 null。
* offset = (handle<<32) | 区内字节偏移;handle 1 = 栈,handle>=2 = 堆分配。 */
#define SPL_OFF_NULL ((spl_vm_val_t)0)
#define SPL_ASP_HANDLE_STACK ((spl_vm_val_t)1)
#define SPL_ASP_OFF(handle, byte) ((((spl_vm_val_t)(handle)) << 32) | (spl_vm_val_t)(byte))
typedef struct {
unsigned char *base;
usize size;
int used;
} spl_vm_region_t;
typedef VEC(spl_vm_region_t) spl_vm_region_vec_t;
typedef struct {
uintptr_t saved_sp;
uintptr_t saved_fp;
@@ -19,22 +34,55 @@ typedef struct {
typedef VEC(spl_vm_val_t) spl_stack_vec_t;
typedef VEC(spl_callframe_t) spl_frame_vec_t;
/* 一次调用描述(VM <-> 外部两侧统一形状)。 */
typedef struct {
const char *name;
const spl_vm_val_t *args;
usize nargs;
spl_vm_val_t ret;
} spl_vm_call_t;
/* 线 1 动态形态:VM 调“外部(非 VM)函数”的按名处理器。
* 当 NCALL 的 native 无 impl_fn 时调用;返回被调函数的返回值。 */
typedef spl_vm_val_t (*spl_vm_foreign_fn)(spl_vm_t *vm, const spl_vm_call_t *call, void *ctx);
typedef struct {
MAP(const char *, usize) stacks;
VEC(usize) self_samples;
VEC(uint64_t) self_ns; /* 每函数 time-slice 累加(纳秒) */
uint64_t last_ns;
uint64_t total_ns;
usize samples;
} spl_prof_t;
typedef struct spl_vm {
spl_stack_vec_t stacks;
spl_frame_vec_t frames;
uintptr_t gp; // global pointer
uintptr_t sp; // stack pointer
uintptr_t fp; // frame pointer
uintptr_t cp; // call pointer
uintptr_t ip; // instr pointer
uintptr_t gp; // global pointer
uintptr_t sp; // stack pointer
uintptr_t fp; // frame pointer
uintptr_t cp; // call pointer
uintptr_t ip; // instr pointer
uint64_t steps; // 已执行指令数(供性能分析参考)
uint64_t ncalls; // 已执行 CALL/CALLI 次数(供性能分析参考)
int exit_code;
int trace; /* non-zero to print each instruction */
int debug; /* non-zero to enable canary checks */
int debug_addr; /* non-zero to check for low-address memory access */
int skip_bp; /* non-zero: run_once 忽略下一次断点(continue 越过当前断点指令) */
spl_prof_t *profile; /* native 采样 profiler 状态(spl_prof_t*),NULL = 关闭 */
usize profile_interval; /* 采样间隔(指令数) */
VEC(usize) breakpoints; /* ip 断点(执行到该指令前暂停,run_once 返回 2) */
VEC(char *) fn_breakpoints; /* 函数名断点(CALL/CALLI 目标函数入口暂停) */
spl_prog_t *prog;
/* 统一偏移地址空间:regions[1]=栈,regions[i>=2]=堆分配。 */
spl_vm_region_vec_t regions;
VEC(usize) gdata_off; /* prog->gdata[i] 在本 VM 空间内的偏移 */
spl_asp_t asp; /* 本 VM 的地址空间描述符(按值持有,避免共享静态) */
spl_vm_val_t stdio_off[3]; /* stdin/stdout/stderr 的 VM 句柄(懒建) */
spl_vm_val_t ret_val; /* 最近一次完成的调用返回值(64 位) */
spl_vm_foreign_fn foreign; /* 线 1 动态处理器(可为 NULL) */
void *foreign_ctx;
char error_msg[1024];
struct {
uintptr_t max_stack_depth;
@@ -52,6 +100,40 @@ void spl_vm_init_ex(spl_vm_t *vm, int stack_size, int call_depth);
/* Free dynamically allocated memory in VM */
void spl_vm_drop(spl_vm_t *vm);
int spl_vm_addr_valid(uintptr_t p, usize size);
/* 偏移地址空间:偏移 -> 宿主地址;越界/ null 返回 NULL。 */
void *spl_vm_xlate(spl_vm_t *vm, spl_vm_val_t off, usize size);
/* 宿主地址 -> 偏移;不属于本 VM 地址空间返回 0。 */
spl_vm_val_t spl_vm_to_off(spl_vm_t *vm, const void *ptr);
/* 本 VM 的地址空间描述符(供 runtime 解引用)。 */
spl_asp_t *spl_vm_asp(spl_vm_t *vm);
/* ---- 线 1:VM -> 外部函数 ---- */
/* 安装“外部函数”按名处理器(NCALL 的 native 无 impl_fn 时使用)。 */
void spl_vm_set_foreign(spl_vm_t *vm, spl_vm_foreign_fn fn, void *ctx);
/* ---- 线 2:外部 -> VM 函数 ---- */
/* 按 call->name 调用 VM 内函数(call->args/nargs 为实参),结果写入 call->ret。
* 可在 VM 运行中重入调用。返回 0 成功,-1 失败。 */
int spl_vm_invoke(spl_vm_t *vm, spl_vm_call_t *call);
/* 入口调用:调用方先按 ABI 压 2 个 pad 再压实参。 */
int spl_vm_call_entry(spl_vm_t *vm, const char *name, int nargs);
/* ---- 栈 / 结果 / 内存 访问(不暴露宿主指针) ---- */
spl_vm_val_t spl_vm_ret_val(spl_vm_t *vm);
usize spl_vm_sp(spl_vm_t *vm);
usize spl_vm_fp(spl_vm_t *vm);
spl_vm_val_t spl_vm_stack_get(spl_vm_t *vm, usize index);
int spl_vm_stack_set(spl_vm_t *vm, usize index, spl_vm_val_t val);
/* 本次调用实参在 VM 栈中的起始下标(外部处理器可按需用 stack_get 逐个读取,
* 无需在 C 侧了解参数类型/元数上限)。失败返回 (usize)-1。 */
usize spl_vm_call_arg_base(spl_vm_t *vm, const spl_vm_call_t *call);
spl_vm_val_t spl_vm_mem_alloc(spl_vm_t *vm, usize size);
int spl_vm_mem_read(spl_vm_t *vm, spl_vm_val_t off, void *dst, usize n);
int spl_vm_mem_write(spl_vm_t *vm, spl_vm_val_t off, const void *src, usize n);
int spl_vm_load_prog(spl_vm_t *vm, spl_prog_t *prog);
int spl_vm_prepare(spl_vm_t *vm, const char *entry, int argc, const char **argv, const char **envp);
@@ -71,6 +153,13 @@ void spl_vm_clear_breakpoints(spl_vm_t *vm);
/* 让 run_once 执行当前指令(忽略一次断点命中);continue 越过当前断点用 */
void spl_vm_skip_breakpoint(spl_vm_t *vm);
/* ---- native 采样 profiler ----
* 每隔 interval 条指令记录一次当前调用栈(折叠格式),用于生成火焰图。
* 必须在 prog 加载后调用 enable;dump 输出折叠栈/函数表,再 drop 时释放。 */
void spl_vm_profile_enable(spl_vm_t *vm, usize interval);
void spl_vm_profile_dump_folded(spl_vm_t *vm, const char *path);
void spl_vm_profile_dump_table(spl_vm_t *vm, const char *path);
void spl_vm_dump_instr(spl_vm_t *vm, spl_vm_val_t ip);
void spl_vm_stackdump(spl_vm_t *vm, spl_vm_val_t sp);
int spl_vm_backtrace(spl_vm_t *vm, spl_vm_val_t fp);
+116
View File
@@ -0,0 +1,116 @@
/* spl_vm_embed.h - 裸机内嵌 VM(装载/运行,不含任何内置 native/syscall)。
*
* 单 TU 合并式:定义 SPL_VM_EMBED_IMPLIMENTATION 后 include 本头,会把 stage0 的
* spl_mcode.c / spl_vm.c 一并纳入当前翻译单元(不单独编译/链接)。
*/
#ifndef __SPL_VM_EMBED_H__
#define __SPL_VM_EMBED_H__
void *vm_new(void);
void vm_drop(void *vm);
int vm_load_from_buffer(void *vm, const void *buf, unsigned long long len);
void vm_push(void *vm, unsigned long long val);
int vm_call(void *vm, const char *name, unsigned long long nargs);
int vm_run(void *vm);
unsigned long long vm_ret_val(void *vm);
unsigned long long vm_mem_alloc(void *vm, unsigned long long size);
int vm_mem_read(void *vm, unsigned long long off, void *dst, unsigned long long n);
int vm_mem_write(void *vm, unsigned long long off, const void *src, unsigned long long n);
#ifdef SPL_VM_EMBED_IMPLIMENTATION
/* 裸机内嵌 VM:只提供装载/运行 VM 的能力,不内置任何 native/syscall。
* 外部函数解析与注册属于配置端(调用方自行处理)。 */
#include "spl_mcode.c"
#include "spl_vm.c"
void *vm_new(void) {
spl_vm_t *vm = (spl_vm_t *)malloc(sizeof(spl_vm_t));
if (!vm)
return 0;
spl_vm_init(vm);
return vm;
}
void vm_drop(void *p) {
spl_vm_t *vm = (spl_vm_t *)p;
if (!vm)
return;
if (vm->prog) {
spl_prog_drop(vm->prog);
free(vm->prog);
vm->prog = 0;
}
spl_vm_drop(vm);
free(vm);
}
int vm_load_from_buffer(void *p, const void *buf, unsigned long long len) {
spl_vm_t *vm = (spl_vm_t *)p;
spl_prog_t *prog;
if (!vm || !buf)
return -1;
prog = (spl_prog_t *)malloc(sizeof(spl_prog_t));
if (!prog)
return -1;
if (spl_prog_load_from_buffer((const char *)buf, (usize)len, prog) != 0) {
free(prog);
return -1;
}
if (spl_vm_load_prog(vm, prog) != 0) {
spl_prog_drop(prog);
free(prog);
return -1;
}
return 0;
}
void vm_push(void *p, unsigned long long val) {
spl_vm_t *vm = (spl_vm_t *)p;
if (!vm)
return;
if (vm->sp >= vm->config.max_stack_depth)
return;
vm->stacks.data[vm->sp++] = (spl_vm_val_t)val;
}
int vm_call(void *p, const char *name, unsigned long long nargs) {
spl_vm_t *vm = (spl_vm_t *)p;
if (!vm || !name)
return -1;
/* 入口调用:调用方需先按 ABI 压 2 个 pad 再压实参。 */
return spl_vm_call_entry(vm, name, (int)nargs);
}
int vm_run(void *p) {
spl_vm_t *vm = (spl_vm_t *)p;
if (!vm)
return -1;
spl_vm_run_until(vm, 0);
return (int)vm->exit_code;
}
unsigned long long vm_ret_val(void *p) {
spl_vm_t *vm = (spl_vm_t *)p;
return vm ? (unsigned long long)vm->ret_val : 0;
}
unsigned long long vm_mem_alloc(void *p, unsigned long long size) {
spl_vm_t *vm = (spl_vm_t *)p;
return (unsigned long long)spl_vm_mem_alloc(vm, (usize)size);
}
int vm_mem_read(void *p, unsigned long long off, void *dst, unsigned long long n) {
spl_vm_t *vm = (spl_vm_t *)p;
return spl_vm_mem_read(vm, (spl_vm_val_t)off, dst, (usize)n);
}
int vm_mem_write(void *p, unsigned long long off, const void *src, unsigned long long n) {
spl_vm_t *vm = (spl_vm_t *)p;
return spl_vm_mem_write(vm, (spl_vm_val_t)off, src, (usize)n);
}
#endif /* SPL_VM_EMBED_IMPLIMENTATION */
#endif /* __SPL_VM_EMBED_H__ */
+140 -10
View File
@@ -6,6 +6,7 @@
#include "include/acutest.h"
#include "spl_mcode.h"
#include "spl_syscall.h"
#include "spl_vm.h"
#include <stdio.h>
@@ -159,7 +160,8 @@ static int run_with_natives(const spl_vm_ins_t *instns, int ninsns, spl_vm_nativ
* Native function for NCALL tests
* ================================================================ */
static spl_vm_val_t native_add_impl(int nargs, spl_vm_val_t *args) {
static spl_vm_val_t native_add_impl(spl_vm_t *vm, int nargs, spl_vm_val_t *args) {
(void)vm;
return (nargs >= 2) ? args[0] + args[1] : 0;
}
@@ -479,7 +481,8 @@ void test_call_add(void) {
spl_vm_ins_t add_insns[] = {ins(SPL_LADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_LADDR, SPL_VOID, 8), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_ADD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
spl_vm_ins_t main_insns[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
spl_vm_ins_t main_insns[] = {ins(SPL_PUSH, SPL_VOID, 0), ins(SPL_PUSH, SPL_VOID, 0),
ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
ins(SPL_PUSH, SPL_VOID, 0), ins(SPL_CALL, SPL_VOID, 2),
ins(SPL_RET, SPL_I64, 0)};
@@ -586,11 +589,12 @@ void test_calli(void) {
spl_vm_ins_t add_insns[] = {ins(SPL_LADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_LADDR, SPL_VOID, 8), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_ADD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
spl_vm_ins_t main_insns[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
spl_vm_ins_t main_insns[] = {ins(SPL_PUSH, SPL_VOID, 0), ins(SPL_PUSH, SPL_VOID, 0),
ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
ins(SPL_PUSH, SPL_VOID, 2), ins(SPL_PUSH, SPL_VOID, 0),
ins(SPL_CALLI, SPL_VOID, 0), ins(SPL_RET, SPL_I64, 0)};
int ninsns_t = 6 + 6;
int ninsns_t = 6 + 8;
size_t nlen0 = strlen("add") + 1;
size_t npad0 = ((nlen0 + 7) / 8) * 8 - nlen0;
size_t nlen1 = strlen("main") + 1;
@@ -627,7 +631,7 @@ void test_calli(void) {
p += npad1;
LE64(p, 0);
LE64(p, 0);
LE64(p, 6);
LE64(p, 8);
LE64(p, 6);
for (int i = 0; i < 6; i++) {
@@ -637,7 +641,7 @@ void test_calli(void) {
*p++ = (unsigned char)(add_insns[i].type >> 8);
LE64(p, add_insns[i].imm);
}
for (int i = 0; i < 6; i++) {
for (int i = 0; i < 8; i++) {
*p++ = (unsigned char)(main_insns[i].opcode);
*p++ = (unsigned char)(main_insns[i].opcode >> 8);
*p++ = (unsigned char)(main_insns[i].type);
@@ -669,8 +673,9 @@ void test_calli(void) {
* ================================================================ */
void test_gaddr_ld32(void) {
/* Load program, add global data, GADDR + LD32 to read it back */
spl_vm_ins_t p[] = {ins(SPL_GADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_U32, 0),
/* Load program, add global data, GADDR + LD32 to read it back.
* gdata 索引 0 为哨兵;首次 add_data 得到索引 1。 */
spl_vm_ins_t p[] = {ins(SPL_GADDR, SPL_VOID, 1), ins(SPL_LOAD, SPL_U32, 0),
ins(SPL_RET, SPL_U32, 0)};
size_t len;
unsigned char *bin = build_binary("main", 0, p, 3, &len);
@@ -697,8 +702,8 @@ void test_gaddr_ld32(void) {
}
void test_gaddr_multi(void) {
/* Two global data entries: read second one via GADDR 1 */
spl_vm_ins_t p[] = {ins(SPL_GADDR, SPL_VOID, 1), ins(SPL_LOAD, SPL_U32, 0),
/* Two global data entries: read second one via GADDR 2 (0 为哨兵) */
spl_vm_ins_t p[] = {ins(SPL_GADDR, SPL_VOID, 2), ins(SPL_LOAD, SPL_U32, 0),
ins(SPL_RET, SPL_U32, 0)};
size_t len;
unsigned char *bin = build_binary("main", 0, p, 3, &len);
@@ -818,6 +823,127 @@ void test_empty(void) {
TEST_CHECK(spl_prog_load_from_file("nonexistent_file_xyz.bin", NULL) != 0);
}
/* ================================================================
* 动态回调 / 重入 / 栈访问 API
* ================================================================ */
static spl_vm_val_t foreign_add_cb(spl_vm_t *vm, const spl_vm_call_t *call, void *ctx) {
(void)vm;
(void)ctx;
if (!call || call->nargs < 2)
return 0;
return call->args[0] + call->args[1];
}
/* 线 1 动态:NCALL 的 native 无 impl_fn 时走 vm->foreign。 */
void test_foreign_fallback(void) {
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 30), ins(SPL_PUSH, SPL_I32, 12),
ins(SPL_PUSH, SPL_VOID, 0), ins(SPL_NCALL, SPL_I32, 2),
ins(SPL_RET, SPL_I32, 0)};
size_t len;
unsigned char *bin = build_binary("main", 0, p, 5, &len);
spl_prog_t prog;
spl_vm_t vm;
spl_vm_native_t nat[] = {{"host_add", 0, NULL}};
int rc = -1;
spl_vm_init(&vm);
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
for (int i = 0; i < 1; i++)
spl_prog_add_native(&prog, &nat[i]);
if (spl_vm_load_prog(&vm, &prog) == 0) {
spl_vm_set_foreign(&vm, foreign_add_cb, NULL);
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
int ret = spl_vm_run_until(&vm, 0);
if (ret == 1)
rc = (int)vm.ret_val;
}
}
}
spl_vm_drop(&vm);
remove(TMPFILE);
free(bin);
TEST_CHECK(rc == 42);
}
/* 线 2:外部 -> VM 调用。 */
void test_invoke(void) {
spl_vm_ins_t add_insns[] = {ins(SPL_LADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_LADDR, SPL_VOID, 8), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_ADD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
size_t len;
unsigned char *bin = build_binary("add", 2, add_insns, 6, &len);
spl_prog_t prog;
spl_vm_t vm;
spl_vm_val_t args[2];
spl_vm_call_t call;
int ok = 0;
spl_vm_init(&vm);
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
if (spl_vm_load_prog(&vm, &prog) == 0) {
args[0] = 40;
args[1] = 2;
call.name = "add";
call.args = args;
call.nargs = 2;
call.ret = 0;
ok = (spl_vm_invoke(&vm, &call) == 0) && (call.ret == 42);
}
}
spl_vm_drop(&vm);
remove(TMPFILE);
free(bin);
TEST_CHECK(ok);
}
/* 栈 / 结果访问 API 不依赖宿主指针。 */
void test_stack_api(void) {
spl_vm_t vm;
spl_vm_init(&vm);
TEST_CHECK(spl_vm_stack_set(&vm, 0, 123) == 0);
TEST_CHECK(spl_vm_stack_get(&vm, 0) == 123);
TEST_CHECK(spl_vm_sp(&vm) == 0);
TEST_CHECK(spl_vm_fp(&vm) == 0);
TEST_CHECK(spl_vm_stack_get(&vm, (usize)-1) == 0);
spl_vm_drop(&vm);
}
/* syscall 注册 + 内存 API:NCALL vm_alloc 得到 VM 偏移,并经 mem_read/write 往返。 */
void test_syscall_registration(void) {
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I64, 16), ins(SPL_PUSH, SPL_VOID, 0),
ins(SPL_NCALL, SPL_I64, 1), ins(SPL_RET, SPL_I64, 0)};
size_t len;
unsigned char *bin = build_binary("main", 0, p, 4, &len);
spl_prog_t prog;
spl_vm_t vm;
spl_vm_native_t nat[] = {{"vm_alloc", 0, NULL}};
spl_vm_val_t off = 0;
int ok = 0;
char w[4] = {1, 2, 3, 0};
char r[4] = {0};
spl_vm_init(&vm);
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
for (int i = 0; i < 1; i++)
spl_prog_add_native(&prog, &nat[i]);
spl_syscall_register(&prog);
if (spl_vm_load_prog(&vm, &prog) == 0 && spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
if (spl_vm_run_until(&vm, 0) == 1) {
off = vm.ret_val;
if (off != 0 && spl_vm_mem_write(&vm, off, w, 4) == 0 &&
spl_vm_mem_read(&vm, off, r, 4) == 0 && memcmp(w, r, 4) == 0)
ok = 1;
}
}
}
spl_vm_drop(&vm);
remove(TMPFILE);
free(bin);
TEST_CHECK(off != 0);
TEST_CHECK(ok);
}
/* ================================================================
* Test list
* ================================================================ */
@@ -872,6 +998,10 @@ TEST_LIST = {
{"ld_st64", test_ld_st64},
{"many_ops", test_many_ops},
{"halt", test_halt},
{"foreign_fallback", test_foreign_fallback},
{"invoke", test_invoke},
{"stack_api", test_stack_api},
{"syscall_registration", test_syscall_registration},
{"bad_magic", test_bad_magic},
{"empty", test_empty},
{NULL, NULL},
+612
View File
@@ -0,0 +1,612 @@
/* ir_opt.c - IR 层优化 pass
*
* 通用函数内联(任意大小/形状的 callee,不再限叶子):
* - callee 体内允许 control.call(嵌套调用)、分支/循环、多个返回、unit 返回;
* - 参数:克隆体内 Param 的“使用处”替换为调用实参 ref;
* - 控制流:克隆体追加到调用者末尾,内部 br/jmp/label 整体 +shift;
* - 返回:
* * 有返回值:在调用者追加 ret 槽(MemAlloca)与常量 1;每个 Ret 改为
* jmp 到“MemStore(槽, val) + jmp(join)”,join 处 MemLoad(槽) 供使用者;
* * 无返回值(unit):每个 Ret 直接 jmp 到调用点之后。
* - 调用点改为 jmp 到克隆体入口;其使用者改指到 join 的 MemLoad(或 0)。
* - 防环:若 callee(传递)可达 caller,则拒绝内联,保证调用图保持无环、
* 内联过程终止(直接/间接递归都不会被内联)。
* - 迭代到不动点(无环调用图上轮数有上界)。
* 默认关闭(splc0 --opt 驱动),且只内联显式 `#inline` 标记的 callee。 */
#include "ir_opt.h"
#include <stdlib.h>
#include <string.h>
typedef struct {
spl_ir_t *ir;
spl_ir_func_ref_t caller;
spl_ir_func_ref_t callee;
spl_ir_node_ref_t shift; /* new = old + shift */
usize nparams;
spl_ir_node_ref_t *param_ref;
spl_ir_node_ref_t *param_arg;
} inline_ctx_t;
static spl_ir_node_t *nd(spl_ir_t *ir, spl_ir_func_ref_t fid, spl_ir_node_ref_t ref) {
return spl_ir_node(ir, fid, ref);
}
/* ---- 引用重写(克隆体旧 ref -> 新 ref) ---- */
static spl_ir_node_ref_t rw(inline_ctx_t *c, spl_ir_node_ref_t r) {
if (r == 0)
return 0;
for (usize i = 0; i < c->nparams; i++)
if (c->param_ref[i] == r)
return c->param_arg[i];
return r + c->shift;
}
static void rw_vec(inline_ctx_t *c, spl_ir_node_ref_vec_t *v) {
for (usize i = 0; i < vec_size(*v); i++)
vec_at(*v, i) = rw(c, vec_at(*v, i));
}
static void rw_node(inline_ctx_t *c, spl_ir_node_t *n) {
switch (n->kind) {
case SPL_IR_ARITH_ADD: case SPL_IR_ARITH_SUB: case SPL_IR_ARITH_MUL:
case SPL_IR_ARITH_DIV: case SPL_IR_ARITH_REM: case SPL_IR_ARITH_NEG:
case SPL_IR_ARITH_ABS: case SPL_IR_ARITH_AND: case SPL_IR_ARITH_OR:
case SPL_IR_ARITH_XOR: case SPL_IR_ARITH_SHL: case SPL_IR_ARITH_SHR:
case SPL_IR_ARITH_NOT:
n->arith.left = rw(c, n->arith.left);
n->arith.right = rw(c, n->arith.right);
break;
case SPL_IR_CMP_EQ: case SPL_IR_CMP_NE: case SPL_IR_CMP_LT:
case SPL_IR_CMP_LE: case SPL_IR_CMP_GT: case SPL_IR_CMP_GE:
n->cmp.a = rw(c, n->cmp.a);
n->cmp.b = rw(c, n->cmp.b);
break;
case SPL_IR_CAST_TRUNC: case SPL_IR_CAST_ZEXT: case SPL_IR_CAST_SEXT:
case SPL_IR_CAST_FEXT: case SPL_IR_CAST_FTRUNC: case SPL_IR_CAST_BITCAST:
case SPL_IR_CAST_PTR2INT: case SPL_IR_CAST_INT2PTR: case SPL_IR_CAST_BOOL2INT:
case SPL_IR_CASE_INT2FLOAT: case SPL_IR_CASE_FLOAT2INT:
n->cast.val = rw(c, n->cast.val);
break;
case SPL_IR_MEM_ALLOCA:
n->mem_alloc.count = rw(c, n->mem_alloc.count);
break;
case SPL_IR_MEM_GLOBAL_ALLOC:
n->mem_global_alloc.const_node = rw(c, n->mem_global_alloc.const_node);
break;
case SPL_IR_MEM_LOAD:
n->mem_load.ptr = rw(c, n->mem_load.ptr);
break;
case SPL_IR_MEM_STORE:
n->mem_store.ptr = rw(c, n->mem_store.ptr);
n->mem_store.val = rw(c, n->mem_store.val);
break;
case SPL_IR_MEM_OFFSET:
n->mem_offset.ptr = rw(c, n->mem_offset.ptr);
n->mem_offset.offset = rw(c, n->mem_offset.offset);
break;
case SPL_IR_MEM_FIELD_PTR:
n->mem_field_ptr.agg = rw(c, n->mem_field_ptr.agg);
break;
case SPL_IR_MEM_COPY:
n->mem_copy.dst = rw(c, n->mem_copy.dst);
n->mem_copy.src = rw(c, n->mem_copy.src);
n->mem_copy.size = rw(c, n->mem_copy.size);
break;
case SPL_IR_MEM_SET:
n->mem_set.dst = rw(c, n->mem_set.dst);
n->mem_set.val = rw(c, n->mem_set.val);
n->mem_set.size = rw(c, n->mem_set.size);
break;
case SPL_IR_TYPE_OFFSETOF:
n->ir_offsetof.field_idx = rw(c, n->ir_offsetof.field_idx);
break;
case SPL_IR_AGG_CONSTRUCT:
rw_vec(c, &n->agg_construct.fields);
break;
case SPL_IR_AGG_EXTRACT:
n->agg_extract.val = rw(c, n->agg_extract.val);
break;
case SPL_IR_AGG_INSERT:
n->agg_insert.agg = rw(c, n->agg_insert.agg);
n->agg_insert.field = rw(c, n->agg_insert.field);
break;
case SPL_IR_CONTROL_SELECT:
n->control_select.cond = rw(c, n->control_select.cond);
n->control_select.true_val = rw(c, n->control_select.true_val);
n->control_select.false_val = rw(c, n->control_select.false_val);
break;
case SPL_IR_CONTROL_BR:
n->control_br.cond = rw(c, n->control_br.cond);
n->control_br.true_label = rw(c, n->control_br.true_label);
n->control_br.false_label = rw(c, n->control_br.false_label);
break;
case SPL_IR_CONTROL_JMP:
n->control_jmp.label = rw(c, n->control_jmp.label);
break;
case SPL_IR_CONTROL_PARAM:
n->control_param.idx = rw(c, n->control_param.idx);
break;
case SPL_IR_CONTROL_RET:
n->control_ret.val = rw(c, n->control_ret.val);
break;
case SPL_IR_CONTROL_CALL:
n->control_call.func = rw(c, n->control_call.func);
rw_vec(c, &n->control_call.params);
break;
default:
break;
}
}
/* ---- 克隆 ---- */
static void clone_ref_vec(spl_ir_node_ref_vec_t *dst, const spl_ir_node_ref_vec_t *src) {
dst->size = 0;
dst->cap = 0;
dst->data = NULL;
usize n = vec_size(*src);
if (n == 0)
return;
spl_ir_node_ref_t *p = (spl_ir_node_ref_t *)malloc(sizeof(spl_ir_node_ref_t) * n);
if (!p)
return;
memcpy(p, src->data, sizeof(spl_ir_node_ref_t) * n);
dst->data = p;
dst->size = n;
dst->cap = n;
}
static void clone_node(spl_ir_t *ir, spl_ir_func_ref_t caller, spl_ir_node_ref_t dst_ref,
const spl_ir_node_t *src) {
spl_ir_node_t *dst = nd(ir, caller, dst_ref);
memcpy(dst, src, sizeof(*dst));
dst->attrs.data = NULL;
dst->attrs.size = 0;
dst->attrs.cap = 0;
if (vec_size(src->attrs) != 0)
clone_ref_vec((spl_ir_node_ref_vec_t *)&dst->attrs,
(const spl_ir_node_ref_vec_t *)&src->attrs);
if (src->kind == SPL_IR_AGG_CONSTRUCT)
clone_ref_vec(&dst->agg_construct.fields, &src->agg_construct.fields);
else if (src->kind == SPL_IR_CONTROL_CALL)
clone_ref_vec(&dst->control_call.params, &src->control_call.params);
}
/* ---- 调用图可达性(防环) ---- */
static spl_ir_func_ref_t call_callee(spl_ir_t *ir, spl_ir_func_ref_t fid, spl_ir_node_t *call) {
spl_ir_node_t *fn = nd(ir, fid, call->control_call.func);
if (!fn || fn->kind != SPL_IR_TYPE_CONST)
return 0;
return fn->type_const.fn;
}
static bool reaches(spl_ir_t *ir, spl_ir_func_ref_t from, spl_ir_func_ref_t target,
unsigned char *mark) {
if (from == 0)
return false;
if (from == target)
return true;
if (mark[from])
return false;
mark[from] = 1;
spl_ir_func_t *f = spl_ir_func(ir, from);
if (!f)
return false;
usize n = vec_size(f->nodes);
for (usize i = 1; i < n; i++) {
spl_ir_node_t *x = nd(ir, from, i);
if (x && x->kind == SPL_IR_CONTROL_CALL) {
spl_ir_func_ref_t c = call_callee(ir, from, x);
if (c != 0 && reaches(ir, c, target, mark))
return true;
}
}
return false;
}
/* 从 from 的直接被调者出发是否能到达 target(用于判断 from 自身是否递归/自可达)。 */
static bool reaches_succ(spl_ir_t *ir, spl_ir_func_ref_t from, spl_ir_func_ref_t target,
unsigned char *mark) {
spl_ir_func_t *f = spl_ir_func(ir, from);
if (!f)
return false;
usize n = vec_size(f->nodes);
for (usize i = 1; i < n; i++) {
spl_ir_node_t *x = nd(ir, from, i);
if (x && x->kind == SPL_IR_CONTROL_CALL) {
spl_ir_func_ref_t c = call_callee(ir, from, x);
if (c != 0 && reaches(ir, c, target, mark))
return true;
}
}
return false;
}
/* ---- 内联一个调用点 ---- */
typedef struct {
spl_ir_node_ref_t clone_ref;
spl_ir_node_ref_t val;
} ret_rec_t;
static bool param_index(spl_ir_t *ir, spl_ir_func_ref_t fid, spl_ir_node_t *p, usize *out) {
spl_ir_node_t *idx = nd(ir, fid, p->control_param.idx);
if (!idx || idx->kind != SPL_IR_TYPE_CONST)
return false;
*out = idx->type_const.int_lit;
return true;
}
static spl_ir_node_ref_t find_const_src(spl_ir_t *ir, spl_ir_func_ref_t fid) {
spl_ir_func_t *f = spl_ir_func(ir, fid);
if (!f)
return 0;
for (usize i = 1; i < vec_size(f->nodes); i++) {
spl_ir_node_t *x = nd(ir, fid, i);
if (x && x->kind == SPL_IR_TYPE_CONST)
return i;
}
return 0;
}
static bool try_inline(spl_ir_t *ir, spl_ir_func_ref_t caller, spl_ir_node_ref_t ci,
spl_ir_func_ref_t callee) {
spl_ir_func_t *cf = spl_ir_func(ir, caller);
spl_ir_func_t *calf = spl_ir_func(ir, callee);
if (!cf || !calf || callee == caller)
return false;
spl_ir_node_t *call = nd(ir, caller, ci);
if (!call || call->kind != SPL_IR_CONTROL_CALL)
return false;
usize orig_len = vec_size(cf->nodes);
if (ci + 1 >= orig_len)
return false;
usize nargs = vec_size(call->control_call.params);
/* 统计参数与返回,分配动态映射。 */
usize cnodes = vec_size(calf->nodes);
usize in_params = 0;
usize in_rets = 0;
for (usize i = 1; i < cnodes; i++) {
spl_ir_node_t *x = nd(ir, callee, i);
if (!x)
return false;
if (x->kind == SPL_IR_CONTROL_PARAM)
in_params++;
else if (x->kind == SPL_IR_CONTROL_RET)
in_rets++;
}
if (in_rets == 0 || in_params != nargs)
return false;
inline_ctx_t c;
memset(&c, 0, sizeof(c));
c.ir = ir;
c.caller = caller;
c.callee = callee;
c.nparams = in_params;
c.param_ref = (spl_ir_node_ref_t *)malloc(sizeof(spl_ir_node_ref_t) * (in_params ? in_params : 1));
c.param_arg = (spl_ir_node_ref_t *)malloc(sizeof(spl_ir_node_ref_t) * (in_params ? in_params : 1));
ret_rec_t *recs = (ret_rec_t *)malloc(sizeof(ret_rec_t) * (in_rets ? in_rets : 1));
if (!c.param_ref || !c.param_arg || !recs) {
free(c.param_ref);
free(c.param_arg);
free(recs);
return false;
}
spl_type_id_t ret_tid = call->control_call.tid;
/* 参数映射(旧 ref -> 实参 ref)。 */
usize pi = 0;
for (usize i = 1; i < cnodes; i++) {
spl_ir_node_t *x = nd(ir, callee, i);
if (x && x->kind == SPL_IR_CONTROL_PARAM) {
usize idx = 0;
if (!param_index(ir, callee, x, &idx) || idx >= nargs) {
free(c.param_ref);
free(c.param_arg);
free(recs);
return false;
}
c.param_ref[pi] = i;
c.param_arg[pi] = vec_at(call->control_call.params, idx);
pi++;
}
}
bool has_val = false;
for (usize i = 1; i < cnodes; i++) {
spl_ir_node_t *x = nd(ir, callee, i);
if (x && x->kind == SPL_IR_CONTROL_RET && x->control_ret.val != 0)
has_val = true;
}
spl_ir_node_ref_t const_src = find_const_src(ir, callee);
if (has_val && const_src == 0) {
free(c.param_ref);
free(c.param_arg);
free(recs);
return false;
}
/* 释放调用点原 params(随后 ci 改为 jmp) */
vec_free(call->control_call.params);
/* 克隆体入口前置:常量 1 + ret 槽 */
spl_ir_node_ref_t body_entry = 0;
spl_ir_node_ref_t slot_ref = 0;
if (has_val) {
spl_ir_node_ref_t c_ref = spl_ir_alloc_node(ir, caller);
clone_node(ir, caller, c_ref, nd(ir, callee, const_src));
spl_ir_node_t *cn = nd(ir, caller, c_ref);
cn->kind = SPL_IR_TYPE_CONST;
cn->type_const.int_lit = 1;
slot_ref = spl_ir_alloc_node(ir, caller);
spl_ir_node_t *sn = nd(ir, caller, slot_ref);
memset(sn, 0, sizeof(*sn));
sn->kind = SPL_IR_MEM_ALLOCA;
sn->mem_alloc.tid = ret_tid;
sn->mem_alloc.count = c_ref;
body_entry = c_ref;
}
c.shift = vec_size(cf->nodes) - 1;
if (!has_val)
body_entry = vec_size(cf->nodes);
/* 克隆体 */
usize nrec = 0;
for (usize i = 1; i < cnodes; i++) {
spl_ir_node_ref_t dst_ref = spl_ir_alloc_node(ir, caller);
if (dst_ref == 0) {
free(c.param_ref);
free(c.param_arg);
free(recs);
return false;
}
spl_ir_node_t *src = nd(ir, callee, i);
clone_node(ir, caller, dst_ref, src);
rw_node(&c, nd(ir, caller, dst_ref));
spl_ir_node_t *x = nd(ir, caller, dst_ref);
/* 克隆进来的 Param 使用处已替换为实参,节点本身不能留在调用者里
* 改变其参数个数;退化为无害常量。 */
if (src->kind == SPL_IR_CONTROL_PARAM) {
x->kind = SPL_IR_TYPE_CONST;
x->type_const.tid = 0;
x->type_const.int_lit = 0;
continue;
}
if (x->kind == SPL_IR_CONTROL_RET) {
recs[nrec].clone_ref = dst_ref;
recs[nrec].val = x->control_ret.val;
nrec++;
x->kind = SPL_IR_CONTROL_JMP;
x->control_jmp.label = 0; /* 稍后回填 */
}
}
spl_ir_node_ref_t ret_new = 0;
if (has_val) {
spl_ir_node_ref_t join = spl_ir_alloc_node(ir, caller);
spl_ir_node_t *jn = nd(ir, caller, join);
memset(jn, 0, sizeof(*jn));
jn->kind = SPL_IR_MEM_LOAD;
jn->mem_load.tid = ret_tid;
jn->mem_load.ptr = slot_ref;
/* join 之后跳回调用点的后继(原代码此处靠 fallthrough)。 */
spl_ir_node_ref_t jc = spl_ir_alloc_node(ir, caller);
spl_ir_node_t *jcn = nd(ir, caller, jc);
memset(jcn, 0, sizeof(*jcn));
jcn->kind = SPL_IR_CONTROL_JMP;
jcn->control_jmp.label = ci + 1;
for (usize k = 0; k < nrec; k++) {
spl_ir_node_ref_t st = spl_ir_alloc_node(ir, caller);
spl_ir_node_t *stn = nd(ir, caller, st);
memset(stn, 0, sizeof(*stn));
stn->kind = SPL_IR_MEM_STORE;
stn->mem_store.tid = ret_tid;
stn->mem_store.ptr = slot_ref;
stn->mem_store.val = recs[k].val;
spl_ir_node_ref_t jr = spl_ir_alloc_node(ir, caller);
spl_ir_node_t *jrn = nd(ir, caller, jr);
memset(jrn, 0, sizeof(*jrn));
jrn->kind = SPL_IR_CONTROL_JMP;
jrn->control_jmp.label = join;
spl_ir_node_t *rn = nd(ir, caller, recs[k].clone_ref);
rn->control_jmp.label = st;
}
ret_new = join;
} else {
for (usize k = 0; k < nrec; k++) {
spl_ir_node_t *rn = nd(ir, caller, recs[k].clone_ref);
rn->control_jmp.label = ci + 1;
}
}
/* 调用点 -> jmp 到克隆体入口 */
call = nd(ir, caller, ci);
call->kind = SPL_IR_CONTROL_JMP;
call->control_jmp.label = body_entry;
/* 使用者替换:只把等于 ci 的引用改为 ret_new(不动其它 ref) */
for (usize j = 1; j < orig_len; j++) {
spl_ir_node_t *x = nd(ir, caller, j);
if (!x)
continue;
switch (x->kind) {
case SPL_IR_ARITH_ADD: case SPL_IR_ARITH_SUB: case SPL_IR_ARITH_MUL:
case SPL_IR_ARITH_DIV: case SPL_IR_ARITH_REM: case SPL_IR_ARITH_NEG:
case SPL_IR_ARITH_ABS: case SPL_IR_ARITH_AND: case SPL_IR_ARITH_OR:
case SPL_IR_ARITH_XOR: case SPL_IR_ARITH_SHL: case SPL_IR_ARITH_SHR:
case SPL_IR_ARITH_NOT:
if (x->arith.left == ci) x->arith.left = ret_new;
if (x->arith.right == ci) x->arith.right = ret_new;
break;
case SPL_IR_CMP_EQ: case SPL_IR_CMP_NE: case SPL_IR_CMP_LT:
case SPL_IR_CMP_LE: case SPL_IR_CMP_GT: case SPL_IR_CMP_GE:
if (x->cmp.a == ci) x->cmp.a = ret_new;
if (x->cmp.b == ci) x->cmp.b = ret_new;
break;
case SPL_IR_CAST_TRUNC: case SPL_IR_CAST_ZEXT: case SPL_IR_CAST_SEXT:
case SPL_IR_CAST_FEXT: case SPL_IR_CAST_FTRUNC: case SPL_IR_CAST_BITCAST:
case SPL_IR_CAST_PTR2INT: case SPL_IR_CAST_INT2PTR: case SPL_IR_CAST_BOOL2INT:
case SPL_IR_CASE_INT2FLOAT: case SPL_IR_CASE_FLOAT2INT:
if (x->cast.val == ci) x->cast.val = ret_new;
break;
case SPL_IR_MEM_ALLOCA:
if (x->mem_alloc.count == ci) x->mem_alloc.count = ret_new;
break;
case SPL_IR_MEM_GLOBAL_ALLOC:
if (x->mem_global_alloc.const_node == ci) x->mem_global_alloc.const_node = ret_new;
break;
case SPL_IR_MEM_LOAD:
if (x->mem_load.ptr == ci) x->mem_load.ptr = ret_new;
break;
case SPL_IR_MEM_STORE:
if (x->mem_store.ptr == ci) x->mem_store.ptr = ret_new;
if (x->mem_store.val == ci) x->mem_store.val = ret_new;
break;
case SPL_IR_MEM_OFFSET:
if (x->mem_offset.ptr == ci) x->mem_offset.ptr = ret_new;
if (x->mem_offset.offset == ci) x->mem_offset.offset = ret_new;
break;
case SPL_IR_MEM_FIELD_PTR:
if (x->mem_field_ptr.agg == ci) x->mem_field_ptr.agg = ret_new;
break;
case SPL_IR_MEM_COPY:
if (x->mem_copy.dst == ci) x->mem_copy.dst = ret_new;
if (x->mem_copy.src == ci) x->mem_copy.src = ret_new;
if (x->mem_copy.size == ci) x->mem_copy.size = ret_new;
break;
case SPL_IR_MEM_SET:
if (x->mem_set.dst == ci) x->mem_set.dst = ret_new;
if (x->mem_set.val == ci) x->mem_set.val = ret_new;
if (x->mem_set.size == ci) x->mem_set.size = ret_new;
break;
case SPL_IR_TYPE_OFFSETOF:
if (x->ir_offsetof.field_idx == ci) x->ir_offsetof.field_idx = ret_new;
break;
case SPL_IR_AGG_CONSTRUCT:
for (usize k = 0; k < vec_size(x->agg_construct.fields); k++)
if (vec_at(x->agg_construct.fields, k) == ci)
vec_at(x->agg_construct.fields, k) = ret_new;
break;
case SPL_IR_AGG_EXTRACT:
if (x->agg_extract.val == ci) x->agg_extract.val = ret_new;
break;
case SPL_IR_AGG_INSERT:
if (x->agg_insert.agg == ci) x->agg_insert.agg = ret_new;
if (x->agg_insert.field == ci) x->agg_insert.field = ret_new;
break;
case SPL_IR_CONTROL_SELECT:
if (x->control_select.cond == ci) x->control_select.cond = ret_new;
if (x->control_select.true_val == ci) x->control_select.true_val = ret_new;
if (x->control_select.false_val == ci) x->control_select.false_val = ret_new;
break;
case SPL_IR_CONTROL_BR:
if (x->control_br.cond == ci) x->control_br.cond = ret_new;
break;
case SPL_IR_CONTROL_CALL:
if (x->control_call.func == ci) x->control_call.func = ret_new;
for (usize k = 0; k < vec_size(x->control_call.params); k++)
if (vec_at(x->control_call.params, k) == ci)
vec_at(x->control_call.params, k) = ret_new;
break;
case SPL_IR_CONTROL_PARAM:
if (x->control_param.idx == ci) x->control_param.idx = ret_new;
break;
case SPL_IR_CONTROL_RET:
if (x->control_ret.val == ci) x->control_ret.val = ret_new;
break;
default:
break;
}
}
free(c.param_ref);
free(c.param_arg);
free(recs);
return true;
}
static bool func_has_attr(const spl_attr_t *attr, const spl_ir_func_t *f, const char *name);
static int opt_function(spl_ir_t *ir, spl_ir_func_ref_t fid, const spl_attr_t *attr,
unsigned char *mark, usize nfuncs) {
spl_ir_func_t *f = spl_ir_func(ir, fid);
if (!f)
return 0;
int done = 0;
usize orig = vec_size(f->nodes);
for (usize ci = 1; ci < orig; ci++) {
spl_ir_node_t *x = nd(ir, fid, ci);
if (!x || x->kind != SPL_IR_CONTROL_CALL)
continue;
spl_ir_func_ref_t callee = call_callee(ir, fid, x);
if (callee == 0 || callee == fid || callee >= nfuncs)
continue;
spl_ir_func_t *calf = spl_ir_func(ir, callee);
if (!func_has_attr(attr, calf, "inline") || func_has_attr(attr, calf, "noinline"))
continue;
/* extern 是占位体,绝不能内联。 */
if (func_has_attr(attr, calf, "extern"))
continue;
/* 防环:callee 传递可达 caller 时拒绝(覆盖直接/间接互递归)。 */
memset(mark, 0, nfuncs);
if (reaches(ir, callee, fid, mark))
continue;
/* 防爆炸:callee 自身递归时拒绝(否则不动点会反复内联其内部调用)。 */
memset(mark, 0, nfuncs);
if (reaches_succ(ir, callee, callee, mark))
continue;
if (try_inline(ir, fid, ci, callee))
done++;
}
return done;
}
/* 是否带有指定名字的属性/tag。 */
static bool func_has_attr(const spl_attr_t *attr, const spl_ir_func_t *f, const char *name) {
if (!attr || !f)
return false;
for (usize i = 0; i < vec_size(f->attrs); ++i) {
spl_attr_node_t *a = spl_attr_node((spl_attr_t *)attr, vec_at(f->attrs, i));
if (!a || !a->info)
continue;
if (strcmp(a->info->name, name) == 0)
return true;
}
return false;
}
void spl_ir_opt(spl_ir_t *ir, const spl_attr_t *attr) {
if (!ir)
return;
usize nfuncs = vec_size(ir->funcs);
unsigned char *mark = (unsigned char *)malloc(nfuncs ? nfuncs : 1);
if (!mark)
return;
/* 无环调用图上,最多 nfuncs 轮即可到不动点。 */
for (usize pass = 0; pass <= nfuncs; pass++) {
int total = 0;
usize n = vec_size(ir->funcs);
for (spl_ir_func_ref_t fid = 1; fid < n && fid < nfuncs; fid++)
total += opt_function(ir, fid, attr, mark, nfuncs);
if (total == 0)
break;
}
free(mark);
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef __IR_OPT_H__
#define __IR_OPT_H__
/* IR 层优化 pass:输入输出都是 spl_ir_t(节点 arena),纯函数式、可开关。
* 由 splc0 --opt 在 ast2ir 之后、ir2vm 之前调用。 */
#include "spl_ir.h"
void spl_ir_opt(spl_ir_t *ir, const spl_attr_t *attr);
#endif /* __IR_OPT_H__ */
+21 -2202
View File
File diff suppressed because it is too large. Load diff
+64 -87
View File
@@ -2,11 +2,9 @@
#define __SPL_AST_H__
#include "../stage0/include/utils.h"
#include "spl_dbg.h"
#include "spl_lexer.h"
#include "spl_tok.h"
#include "spl_attr.h"
#include "spl_dbg.h"
#include "spl_tok.h"
/* clang-format off */
#define SPL_AST_KIND_TABLE \
@@ -73,7 +71,10 @@
X(SPL_AST_PATTERN_UNDERSCORE, V0, pattern_underscore) \
X(SPL_AST_PATTERN_DOT, V0, pattern_dot) \
X(SPL_AST_PATTERN_TUPLE, V0, pattern_tuple) \
X(SPL_AST_PATTERN_LIST, V0, pattern_list)
X(SPL_AST_PATTERN_LIST, V0, pattern_list) \
X(SPL_AST_PATTERN_AGGREGATE, V0, pattern_aggregate) \
X(SPL_AST_PATTERN_FIELD, V0, pattern_field) \
X(SPL_AST_PATTERN_REF, V0, pattern_ref)
/* clang-format on */
typedef enum {
@@ -139,11 +140,9 @@ typedef enum {
SPL_UNARY_OP_NOT,
SPL_UNARY_OP_BIT_NOT,
SPL_UNARY_OP_ADDRESS,
SPL_UNARY_OP_DEREF,
} spl_unary_op_t;
const char *spl_binary_op_name(spl_binary_op_t op);
const char *spl_unary_op_name(spl_unary_op_t op);
struct spl_ast_node;
typedef struct spl_ast_node spl_ast_node_t;
typedef usize spl_ast_node_ref_t;
@@ -159,144 +158,115 @@ struct spl_ast_node {
spl_attr_id_vec_t attrs;
union {
/* SPL_AST_ROOT */
struct {
spl_ast_node_ref_t members; /* container_members 节点 */
spl_ast_node_ref_t members;
} root;
/* SPL_AST_CONTAINER_MEMBERS */
struct {
spl_ast_node_ref_vec_t external_tags; /* external_tag_item 节点列表 */
spl_ast_node_ref_vec_t external_tags;
spl_ast_node_ref_vec_t declarations;
} container_members;
/* SPL_AST_FN_DECL */
struct {
spl_ast_node_ref_vec_t tag_list; /* tag_item 节点 */
spl_ast_node_ref_vec_t tag_list;
bool is_comptime;
const char *name;
spl_ast_node_ref_vec_t params; /* param_decl 节点 */
spl_ast_node_ref_t ret_type; /* 类型节点,可空 */
spl_ast_node_ref_t body; /* 表达式节点,可空表示仅声明 */
spl_ast_node_ref_vec_t params;
spl_ast_node_ref_t ret_type;
spl_ast_node_ref_t body;
} fn_decl;
/* SPL_AST_VAR_DECL / SPL_AST_CONST_DECL */
struct {
spl_ast_node_ref_vec_t attr_list;
spl_ast_node_ref_t pattern; /* 模式节点 */
spl_ast_node_ref_t type; /* 类型节点,可空 */
spl_ast_node_ref_t init_expr; /* 表达式节点,可空 */
spl_ast_node_ref_t pattern;
spl_ast_node_ref_t type;
spl_ast_node_ref_t init_expr;
} var_const_decl;
/* SPL_AST_TYPE_DECL */
struct {
spl_ast_node_ref_vec_t attr_list;
const char *name;
spl_ast_node_ref_t type_expr; /* 类型节点 */
spl_ast_node_ref_t pattern;
spl_ast_node_ref_t type_expr;
} type_decl;
/* SPL_AST_MEMBER_DECL */
struct {
spl_ast_node_ref_vec_t attr_list;
const char *name;
spl_ast_node_ref_t type_expr; /* 可空 */
spl_ast_node_ref_t init_expr; /* 可空 */
spl_ast_node_ref_t type_expr;
spl_ast_node_ref_t init_expr;
} member_decl;
/* SPL_AST_PARAM_DECL */
struct {
spl_ast_node_ref_vec_t attr_list;
const char *name;
spl_ast_node_ref_t type_expr;
} param_decl;
/* SPL_AST_TAG_ITEM / SPL_AST_EXTERNAL_TAG_ITEM */
struct {
bool is_at; /* true: @, false: # */
bool is_external; /* true: @!或#! */
bool is_at;
bool is_external;
const char *name;
spl_ast_node_ref_vec_t args;
} tag_item;
/* SPL_AST_COMPTIME_DECL */
struct {
spl_ast_node_ref_t body; /* block 或 expr 节点 */
spl_ast_node_ref_t body;
} comptime_decl;
/* SPL_AST_TOP_LEVEL_BLOCK */
struct {
spl_ast_node_ref_vec_t tag_list; /* tag_item 节点列表 */
spl_ast_node_ref_t block; /* 表达式节点block */
spl_ast_node_ref_vec_t tag_list;
spl_ast_node_ref_t block;
} top_level_block;
/* SPL_AST_STATEMENT_RET */
struct {
spl_ast_node_ref_t expr; /* 可空 */
spl_ast_node_ref_t expr;
} statement_ret;
/* SPL_AST_STATEMENT_BREAK */
struct {
spl_ast_node_ref_t label; /* 可空 */
spl_ast_node_ref_t expr; /* 可空 */
spl_ast_node_ref_t label;
spl_ast_node_ref_t expr;
} statement_break;
/* SPL_AST_STATEMENT_CONTINUE */
struct {
spl_ast_node_ref_t label; /* 可空 */
spl_ast_node_ref_t label;
} statement_continue;
/* SPL_AST_STATEMENT_DEFER */
struct {
spl_ast_node_ref_t body; /* block 或 statement 节点 */
spl_ast_node_ref_t body;
} statement_defer;
/* SPL_AST_STATEMENT_COMPTIME */
struct {
spl_ast_node_ref_t body; /* block 或 expr 节点 */
spl_ast_node_ref_t body;
} statement_comptime;
/* SPL_AST_STATEMENT_DECL */
struct {
spl_ast_node_ref_t decl; /* var/const/type 声明节点 */
spl_ast_node_ref_t decl;
} statement_decl;
/* SPL_AST_STATEMENT_EXPR */
struct {
spl_ast_node_ref_t expr;
} statement_expr;
/* SPL_AST_AGGREGATE_MEMBER */
struct {
spl_ast_node_ref_vec_t attr_list;
const char *name;
spl_ast_node_ref_t type_expr; /* 可空 */
spl_ast_node_ref_t init_expr; /* 可空 */
spl_ast_node_ref_t type_expr;
spl_ast_node_ref_t init_expr;
} aggregate_member;
/* SPL_AST_STRUCT_INIT_ITEM */
struct {
const char *name;
spl_ast_node_ref_t expr; /* 可空 */
spl_ast_node_ref_t expr;
} struct_init_item;
/* SPL_AST_MATCH_ARM */
struct {
spl_ast_node_ref_t pattern; /* 模式节点 */
spl_ast_node_ref_t body; /* block 或 expr 节点 */
spl_ast_node_ref_t pattern;
spl_ast_node_ref_t body;
} match_arm;
/* SPL_AST_LABEL */
struct {
const char *name;
} label;
/* ---- 表达式子类型 ---- */
struct {
isize val;
} int_lit;
struct {
double val;
} float_lit;
spl_mpa_int_t int_lit;
spl_mpa_float_t float_lit;
struct {
char val;
} char_lit;
@@ -307,7 +277,6 @@ struct spl_ast_node {
struct {
bool val;
} bool_lit;
/* null_lit, undefined_lit 无数据 */
struct {
const char *name;
@@ -358,13 +327,13 @@ struct spl_ast_node {
bool has_len;
usize len;
bool is_underscore_len;
spl_ast_node_ref_t elem_type; /* 可空 匿名时为 NULL */
spl_ast_node_ref_t elem_type;
spl_ast_node_ref_vec_t elems;
} array_literal;
struct {
const char *name; /* 可空 匿名时为 NULL */
spl_ast_node_ref_vec_t fields; /* struct_init_item 节点列表 */
const char *name;
spl_ast_node_ref_vec_t fields;
} aggregate_literal;
struct {
@@ -404,14 +373,14 @@ struct spl_ast_node {
struct {
spl_ast_node_ref_t label;
spl_ast_node_ref_vec_t iter_exprs;
spl_ast_node_ref_vec_t idents;
spl_ast_node_ref_t pattern;
spl_ast_node_ref_t body;
spl_ast_node_ref_t else_branch;
} for_expr;
struct {
spl_ast_node_ref_t matched_expr;
spl_ast_node_ref_vec_t arms; /* match_arm 节点 */
spl_ast_node_ref_vec_t arms;
} match_expr;
struct {
@@ -420,7 +389,6 @@ struct spl_ast_node {
spl_ast_node_ref_t ret_type;
} fn_type;
/* ---- 类型子类型 ---- */
struct {
const char *name;
spl_ast_node_ref_vec_t generic_args;
@@ -453,7 +421,7 @@ struct spl_ast_node {
struct {
const char *kind_name;
spl_ast_node_ref_vec_t members; /* aggregate_member 节点 */
spl_ast_node_ref_vec_t members;
} type_aggregate;
struct {
@@ -461,13 +429,10 @@ struct spl_ast_node {
spl_ast_node_ref_vec_t args;
} type_comptime_call;
/* ---- 模式子类型 ---- */
struct {
const char *name;
} pattern_ident;
/* pattern_underscore 无数据 */
struct {
const char *name;
spl_ast_node_ref_t sub_pattern;
@@ -476,31 +441,43 @@ struct spl_ast_node {
struct {
spl_ast_node_ref_vec_t patterns;
} pattern_tuple;
struct {
spl_ast_node_ref_vec_t patterns;
} pattern_list;
struct {
spl_ast_node_ref_vec_t fields;
} pattern_aggregate;
struct {
const char *name;
spl_ast_node_ref_t pattern;
} pattern_field;
struct {
spl_ast_node_ref_t pat;
} pattern_ref;
};
};
typedef VEC(spl_ast_node_t) spl_ast_node_vec_t;
typedef VEC(char *) spl_ast_cstr_ref_vec_t;
typedef struct {
int parsed;
typedef struct spl_ast {
spl_tok_vec_t input;
spl_ast_node_vec_t node_buckets;
spl_ast_cstr_ref_vec_t str_buckets;
spl_ast_node_ref_t root;
} spl_ast_t;
typedef VEC(spl_ast_t *) spl_ast_ptr_vec_t;
void spl_ast_init(spl_ast_t *ast, const spl_tok_vec_t *tok_vec /*move*/);
void spl_ast_drop(spl_ast_t *ast);
spl_ast_node_t *spl_ast_node(spl_ast_t *ast, spl_ast_node_ref_t node);
const char *spl_ast_node_name(spl_ast_node_t *node);
void spl_ast_parse(spl_ast_t *ast);
void spl_ast_valid(spl_ast_t *ast);
void spl_ast_dump(spl_ast_t *ast, spl_ast_node_ref_t node);
const char *spl_ast_kind_name(spl_ast_node_kind_t kind);
const char *spl_binary_op_name(spl_binary_op_t op);
const char *spl_unary_op_name(spl_unary_op_t op);
#endif /* __SPL_AST_H__ */
+1492 -357
View File
File diff suppressed because it is too large. Load diff
+13 -4
View File
@@ -1,6 +1,7 @@
#ifndef __SPL_AST2IR_H__
#define __SPL_AST2IR_H__
#include "spl_diag.h"
#include "spl_ir.h"
#include "spl_sema.h"
@@ -8,8 +9,11 @@ typedef MAP(spl_def_id_t, spl_ir_node_ref_t) spl_def2ir_map_t;
typedef struct {
usize cont_blk;
usize brk_blk;
usize defer_base;
const char *label;
spl_ir_node_ref_vec_t breaks;
spl_ir_node_ref_t result_slot;
spl_type_id_t result_tid;
} spl_loop_ctx_t;
typedef VEC(spl_loop_ctx_t) spl_loop_ctx_vec_t;
@@ -18,16 +22,21 @@ typedef VEC(spl_ast_node_ref_t) spl_defer_stack_t;
typedef struct {
const spl_sema_t *sema;
spl_ir_builder_t *ir;
spl_ast_t *ast;
spl_layout_t *layout; /* 借用 splc0 的 layout */
spl_def2ir_map_t def2ir_map;
spl_loop_ctx_vec_t loop_stack;
spl_defer_stack_t defer_stack;
spl_lang_type_id_t cur_ret_lang;
int err_count;
usize cur_defer_base;
spl_diag_t *diag;
} spl_ast2ir_t;
void spl_ast2ir_init(spl_ast2ir_t *ast2ir, spl_ir_builder_t *ir, const spl_sema_t *sema);
void spl_ast2ir_init(spl_ast2ir_t *ast2ir, spl_ir_builder_t *ir, spl_layout_t *layout,
const spl_sema_t *sema);
void spl_ast2ir_drop(spl_ast2ir_t *ast2ir);
void spl_ast2ir_run(spl_ast2ir_t *ast2ir);
void spl_ast2ir_run(spl_ast2ir_t *ast2ir, spl_ast_ptr_vec_t ast_vec);
#endif /* __SPL_AST2IR_H__ */
+1
View File
@@ -2,6 +2,7 @@
#define __SPL_ATTR_H__
#include "../stage0/include/utils.h"
#include "spl_mpa.h"
typedef enum {
SPL_ATTR_STAGE_SEMA,
+368
View File
@@ -0,0 +1,368 @@
#include "spl_diag.h"
#include "../stage0/include/color.h"
#include <stdarg.h>
static const char *const g_kind_names[] = {
[SPL_DIAG_KIND_NONE] = "none",
[SPL_DIAG_KIND_LEX_UNEXPECTED] = "lex_unexpected",
[SPL_DIAG_KIND_PARSE_EXPECTED] = "parse_expected",
[SPL_DIAG_KIND_PARSE_UNRECOGNIZED] = "parse_unrecognized",
[SPL_DIAG_KIND_UNDEFINED_SYMBOL] = "undefined_symbol",
[SPL_DIAG_KIND_DUPLICATE_SYMBOL] = "duplicate_symbol",
[SPL_DIAG_KIND_TYPE_MISMATCH] = "type_mismatch",
[SPL_DIAG_KIND_ARG_COUNT_MISMATCH] = "arg_count_mismatch",
[SPL_DIAG_KIND_NOT_A_FUNCTION] = "not_a_function",
[SPL_DIAG_KIND_NO_FIELD] = "no_field",
[SPL_DIAG_KIND_NO_METHOD] = "no_method",
[SPL_DIAG_KIND_INDEX_BASE_INVALID] = "index_base_invalid",
[SPL_DIAG_KIND_TUPLE_INDEX_OOB] = "tuple_index_oob",
[SPL_DIAG_KIND_FITS_OVERFLOW] = "fits_overflow",
[SPL_DIAG_KIND_CIRCULAR_DEP] = "circular_dep",
[SPL_DIAG_KIND_BREAK_OUTSIDE_LOOP] = "break_outside_loop",
[SPL_DIAG_KIND_CONTINUE_OUTSIDE_LOOP] = "continue_outside_loop",
[SPL_DIAG_KIND_UNDEFINED_LABEL] = "undefined_label",
[SPL_DIAG_KIND_CAST_INVALID] = "cast_invalid",
[SPL_DIAG_KIND_UNKNOWN_ATTR] = "unknown_attr",
[SPL_DIAG_KIND_UNKNOWN_INTRIN] = "unknown_intrin",
[SPL_DIAG_KIND_INTRIN_ARG_COUNT] = "intrin_arg_count",
[SPL_DIAG_KIND_VAR_UNINITIALIZED] = "var_uninitialized",
[SPL_DIAG_KIND_CANNOT_INFER] = "cannot_infer",
[SPL_DIAG_KIND_MUST_RETURN_VALUE] = "must_return_value",
[SPL_DIAG_KIND_UNHANDLED_NODE] = "unhandled_node",
[SPL_DIAG_KIND_IMPLICIT_CAST_FAIL] = "implicit_cast_fail",
[SPL_DIAG_KIND_NO_LVALUE] = "no_lvalue",
[SPL_DIAG_KIND_BAD_FIELD_INDEX] = "bad_field_index",
[SPL_DIAG_KIND_BAD_BLOCK] = "bad_block",
[SPL_DIAG_KIND_IR_UNKNOWN_KIND] = "ir_unknown_kind",
[SPL_DIAG_KIND_FRAME_OVERFLOW] = "frame_overflow",
[SPL_DIAG_KIND_INTERNAL] = "internal",
};
const char *spl_diag_kind_name(spl_diag_kind_t k) {
if ((usize)k < sizeof(g_kind_names) / sizeof(g_kind_names[0]) && g_kind_names[k])
return g_kind_names[k];
return "unknown";
}
static const char *const g_stage_names[] = {
[SPL_STAGE_NONE] = "none", [SPL_STAGE_LEX] = "lex", [SPL_STAGE_PARSE] = "parse",
[SPL_STAGE_SEMA] = "sema", [SPL_STAGE_AST2IR] = "ast2ir", [SPL_STAGE_IR2VM] = "ir2vm",
[SPL_STAGE_DRIVER] = "driver",
};
const char *spl_diag_stage_name(spl_diag_stage_t s) {
if ((usize)s < sizeof(g_stage_names) / sizeof(g_stage_names[0]) && g_stage_names[s])
return g_stage_names[s];
return "?";
}
const char *spl_diag_level_name(spl_diag_level_t l) {
switch (l) {
case SPL_DIAG_LEVEL_NOTE:
return "note";
case SPL_DIAG_LEVEL_WARN:
return "warn";
case SPL_DIAG_LEVEL_ERROR:
return "error";
case SPL_DIAG_LEVEL_FATAL:
return "fatal";
}
return "?";
}
static const char *level_color(spl_diag_level_t l) {
switch (l) {
case SPL_DIAG_LEVEL_NOTE:
return ANSI_FG_CYAN;
case SPL_DIAG_LEVEL_WARN:
return ANSI_FG_YELLOW;
case SPL_DIAG_LEVEL_ERROR:
return ANSI_FG_RED;
case SPL_DIAG_LEVEL_FATAL:
return ANSI_FG_RED ANSI_UNDERLINED;
}
return ANSI_NONE;
}
static char *diag_strdup(const char *s) {
if (!s)
return NULL;
usize n = strlen(s);
char *r = malloc(n + 1);
if (!r)
return NULL;
memcpy(r, s, n);
r[n] = '\0';
return r;
}
void spl_diag_init(spl_diag_t *d, void *fout) {
memset(d, 0, sizeof *d);
vec_init(d->ctx);
vec_init(d->records);
d->collect = true;
d->fout = fout;
d->color = true;
d->stage = SPL_STAGE_NONE;
}
static void frame_free(spl_diag_frame_t *f) {
free(f->fn_name);
free(f->label);
f->fn_name = NULL;
f->label = NULL;
}
static void record_free(spl_diag_record_t *r) {
free(r->message);
r->message = NULL;
for (usize i = 0; i < vec_size(r->ctx); ++i)
frame_free(&vec_at(r->ctx, i));
vec_free(r->ctx);
for (usize i = 0; i < vec_size(r->notes); ++i)
free(vec_at(r->notes, i).message);
vec_free(r->notes);
}
void spl_diag_reset(spl_diag_t *d) {
for (usize i = 0; i < vec_size(d->ctx); ++i)
frame_free(&vec_at(d->ctx, i));
d->ctx.size = 0;
for (usize i = 0; i < vec_size(d->records); ++i)
record_free(&vec_at(d->records, i));
d->records.size = 0;
d->num_printed = 0;
d->n_errors = 0;
d->n_warns = 0;
d->n_fatals = 0;
}
void spl_diag_drop(spl_diag_t *d) {
spl_diag_reset(d);
vec_free(d->ctx);
vec_free(d->records);
}
void spl_diag_set_stage(spl_diag_t *d, spl_diag_stage_t stage) { d->stage = stage; }
void spl_diag_set_output(spl_diag_t *d, FILE *out) { d->fout = out; }
void spl_diag_set_color(spl_diag_t *d, bool enabled) { d->color = enabled; }
void spl_diag_set_collect(spl_diag_t *d, bool enabled) { d->collect = enabled; }
void spl_diag_push(spl_diag_t *d, const spl_dbg_node_t *loc, const char *fn_name,
const char *label) {
spl_diag_frame_t f = {0};
if (loc)
f.loc = *loc;
f.fn_name = fn_name ? diag_strdup(fn_name) : NULL;
f.label = label ? diag_strdup(label) : NULL;
vec_push(d->ctx, f);
}
void spl_diag_pop(spl_diag_t *d) {
if (vec_size(d->ctx) == 0)
return;
frame_free(&vec_at(d->ctx, vec_size(d->ctx) - 1));
d->ctx.size--;
}
usize spl_diag_depth(const spl_diag_t *d) { return vec_size(d->ctx); }
static spl_diag_frame_vec_t ctx_snapshot(const spl_diag_t *d) {
spl_diag_frame_vec_t snap;
vec_init(snap);
for (usize i = 0; i < vec_size(d->ctx); ++i) {
spl_diag_frame_t *src = &vec_at(d->ctx, i);
spl_diag_frame_t f = {0};
f.loc = src->loc;
f.fn_name = src->fn_name ? diag_strdup(src->fn_name) : NULL;
f.label = src->label ? diag_strdup(src->label) : NULL;
vec_push(snap, f);
}
return snap;
}
static void frame_desc(const spl_diag_frame_t *f, char *buf, usize cap) {
if (f->fn_name && f->label)
snprintf(buf, cap, "%s %s", f->label, f->fn_name);
else if (f->fn_name)
snprintf(buf, cap, "%s", f->fn_name);
else if (f->label)
snprintf(buf, cap, "%s", f->label);
else
snprintf(buf, cap, "?");
}
static void print_loc(FILE *out, const spl_dbg_node_t *loc) {
if (loc && loc->fname)
fprintf(out, "%s:%d:%d", loc->fname, loc->line, loc->col);
else
fprintf(out, "<no-loc>");
}
static void print_record(const spl_diag_t *d, const spl_diag_record_t *r) {
FILE *out = (FILE *)d->fout;
if (!out)
return;
const char *col = d->color ? level_color(r->level) : "";
const char *rst = d->color ? ANSI_NONE : "";
fprintf(out, "%s%s[%s:%s]%s: ", col, spl_diag_level_name(r->level),
spl_diag_stage_name(r->stage), spl_diag_kind_name(r->kind), rst);
print_loc(out, &r->loc);
fputc('\n', out);
fprintf(out, " %s\n", r->message ? r->message : "");
for (usize i = vec_size(r->ctx); i > 0; --i) {
char buf[256];
frame_desc(&vec_at(r->ctx, i - 1), buf, sizeof buf);
fprintf(out, " in %s at ", buf);
print_loc(out, &vec_at(r->ctx, i - 1).loc);
fputc('\n', out);
}
for (usize i = 0; i < vec_size(r->notes); ++i) {
spl_diag_note_t *n = &vec_at(r->notes, i);
const char *ncol = d->color ? ANSI_FG_CYAN : "";
fprintf(out, "%snote%s: ", ncol, rst);
print_loc(out, &n->loc);
fputc('\n', out);
fprintf(out, " %s\n", n->message ? n->message : "");
}
}
static void emit(spl_diag_t *d, spl_diag_level_t level, spl_diag_kind_t kind,
const spl_dbg_node_t *loc, const char *fmt, va_list ap) {
va_list ap2;
va_copy(ap2, ap);
int need = vsnprintf(NULL, 0, fmt, ap);
if (need < 0)
need = 0;
char *msg = malloc((usize)need + 1);
if (msg)
vsnprintf(msg, (usize)need + 1, fmt, ap2);
va_end(ap2);
spl_diag_record_t rec = {0};
rec.level = level;
rec.stage = d->stage;
rec.kind = kind;
if (loc)
rec.loc = *loc;
rec.message = msg;
rec.ctx = ctx_snapshot(d);
vec_init(rec.notes);
if (level == SPL_DIAG_LEVEL_ERROR)
d->n_errors++;
else if (level == SPL_DIAG_LEVEL_FATAL) {
d->n_errors++;
d->n_fatals++;
} else if (level == SPL_DIAG_LEVEL_WARN)
d->n_warns++;
if (d->fout) {
print_record(d, &rec);
d->num_printed++;
}
if (d->collect)
vec_push(d->records, rec);
else
record_free(&rec);
}
void spl_diag_note(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
...) {
va_list ap;
va_start(ap, fmt);
emit(d, SPL_DIAG_LEVEL_NOTE, kind, loc, fmt, ap);
va_end(ap);
}
void spl_diag_warn(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
...) {
va_list ap;
va_start(ap, fmt);
emit(d, SPL_DIAG_LEVEL_WARN, kind, loc, fmt, ap);
va_end(ap);
}
void spl_diag_error(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
...) {
va_list ap;
va_start(ap, fmt);
emit(d, SPL_DIAG_LEVEL_ERROR, kind, loc, fmt, ap);
va_end(ap);
}
void spl_diag_fatal(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
...) {
va_list ap;
va_start(ap, fmt);
emit(d, SPL_DIAG_LEVEL_FATAL, kind, loc, fmt, ap);
va_end(ap);
}
void spl_diag_attach_note(spl_diag_t *d, const spl_dbg_node_t *loc, const char *fmt, ...) {
if (vec_size(d->records) == 0)
return;
spl_diag_record_t *last = &vec_at(d->records, vec_size(d->records) - 1);
va_list ap;
va_start(ap, fmt);
int need = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
if (need < 0)
need = 0;
char *msg = malloc((usize)need + 1);
if (msg) {
va_start(ap, fmt);
vsnprintf(msg, (usize)need + 1, fmt, ap);
va_end(ap);
}
spl_diag_note_t n = {0};
if (loc)
n.loc = *loc;
n.message = msg;
vec_push(last->notes, n);
}
bool spl_diag_has_error(const spl_diag_t *d) { return d->n_errors > 0; }
bool spl_diag_has_fatal(const spl_diag_t *d) { return d->n_fatals > 0; }
usize spl_diag_error_count(const spl_diag_t *d) { return d->n_errors; }
usize spl_diag_warn_count(const spl_diag_t *d) { return d->n_warns; }
void spl_diag_summary(const spl_diag_t *d) {
FILE *out = (FILE *)d->fout;
if (!out)
return;
/* 把还没实时打印过的记录补打一遍。
collect == false 时 records 是空的,num_printed 可能非零,
用 records.size 兜底避免越界。 */
usize printed_limit = d->num_printed;
if (printed_limit > vec_size(d->records))
printed_limit = vec_size(d->records);
for (usize i = printed_limit; i < vec_size(d->records); ++i)
print_record(d, &vec_at(d->records, i));
/* 计数 */
const char *ecol = d->color && d->n_errors > 0 ? ANSI_FG_RED : "";
const char *rst = d->color ? ANSI_NONE : "";
fprintf(out, "%s%zu error%s, %zu warning%s%s\n", ecol, d->n_errors, d->n_errors == 1 ? "" : "s",
d->n_warns, d->n_warns == 1 ? "" : "s", rst);
}
+165
View File
@@ -0,0 +1,165 @@
#ifndef __SPL_DIAG_H__
#define __SPL_DIAG_H__
#include "../stage0/include/utils.h"
#include "spl_dbg.h"
typedef enum {
SPL_DIAG_LEVEL_NOTE = 0,
SPL_DIAG_LEVEL_WARN,
SPL_DIAG_LEVEL_ERROR,
SPL_DIAG_LEVEL_FATAL,
} spl_diag_level_t;
typedef enum {
SPL_STAGE_NONE = 0,
SPL_STAGE_LEX,
SPL_STAGE_PARSE,
SPL_STAGE_SEMA,
SPL_STAGE_AST2IR,
SPL_STAGE_IR2VM,
SPL_STAGE_DRIVER,
} spl_diag_stage_t;
/* kind 枚举:边写边补。新增时放对应分组末尾即可。 */
typedef enum {
SPL_DIAG_KIND_NONE = 0,
/* 词法 / 语法 */
SPL_DIAG_KIND_LEX_UNEXPECTED,
SPL_DIAG_KIND_PARSE_EXPECTED,
SPL_DIAG_KIND_PARSE_UNRECOGNIZED,
/* sema */
SPL_DIAG_KIND_UNDEFINED_SYMBOL,
SPL_DIAG_KIND_DUPLICATE_SYMBOL,
SPL_DIAG_KIND_TYPE_MISMATCH,
SPL_DIAG_KIND_ARG_COUNT_MISMATCH,
SPL_DIAG_KIND_NOT_A_FUNCTION,
SPL_DIAG_KIND_NO_FIELD,
SPL_DIAG_KIND_NO_METHOD,
SPL_DIAG_KIND_INDEX_BASE_INVALID,
SPL_DIAG_KIND_TUPLE_INDEX_OOB,
SPL_DIAG_KIND_FITS_OVERFLOW,
SPL_DIAG_KIND_CIRCULAR_DEP,
SPL_DIAG_KIND_BREAK_OUTSIDE_LOOP,
SPL_DIAG_KIND_CONTINUE_OUTSIDE_LOOP,
SPL_DIAG_KIND_UNDEFINED_LABEL,
SPL_DIAG_KIND_CAST_INVALID,
SPL_DIAG_KIND_UNKNOWN_ATTR,
SPL_DIAG_KIND_UNKNOWN_INTRIN,
SPL_DIAG_KIND_INTRIN_ARG_COUNT,
SPL_DIAG_KIND_VAR_UNINITIALIZED,
SPL_DIAG_KIND_CANNOT_INFER,
SPL_DIAG_KIND_MUST_RETURN_VALUE,
/* ast2ir */
SPL_DIAG_KIND_UNHANDLED_NODE,
SPL_DIAG_KIND_IMPLICIT_CAST_FAIL,
SPL_DIAG_KIND_NO_LVALUE,
SPL_DIAG_KIND_BAD_FIELD_INDEX,
SPL_DIAG_KIND_BAD_BLOCK,
/* ir2vm */
SPL_DIAG_KIND_IR_UNKNOWN_KIND,
SPL_DIAG_KIND_FRAME_OVERFLOW,
/* 通用 */
SPL_DIAG_KIND_INTERNAL,
} spl_diag_kind_t;
typedef struct {
spl_dbg_node_t loc;
char *fn_name;
char *label;
} spl_diag_frame_t;
typedef VEC(spl_diag_frame_t) spl_diag_frame_vec_t;
typedef struct {
spl_dbg_node_t loc;
char *message;
} spl_diag_note_t;
typedef VEC(spl_diag_note_t) spl_diag_note_vec_t;
typedef struct {
spl_diag_level_t level;
spl_diag_stage_t stage;
spl_diag_kind_t kind;
spl_dbg_node_t loc;
char *message;
spl_diag_frame_vec_t ctx;
spl_diag_note_vec_t notes;
} spl_diag_record_t;
typedef VEC(spl_diag_record_t) spl_diag_record_vec_t;
typedef struct spl_diag {
spl_diag_frame_vec_t ctx;
spl_diag_record_vec_t records;
usize num_printed;
bool collect;
usize n_errors;
usize n_warns;
usize n_fatals;
spl_diag_stage_t stage;
void *fout;
bool color;
} spl_diag_t;
void spl_diag_init(spl_diag_t *d, void *fout);
void spl_diag_drop(spl_diag_t *d);
void spl_diag_reset(spl_diag_t *d);
void spl_diag_set_stage(spl_diag_t *d, spl_diag_stage_t stage);
void spl_diag_set_output(spl_diag_t *d, FILE *out);
void spl_diag_set_color(spl_diag_t *d, bool enabled);
void spl_diag_set_collect(spl_diag_t *d, bool enabled);
void spl_diag_push(spl_diag_t *d, const spl_dbg_node_t *loc, const char *fn_name,
const char *label);
void spl_diag_pop(spl_diag_t *d);
usize spl_diag_depth(const spl_diag_t *d);
/* ============================================================
* 发射
*
* 每个级别都生成独立的 spl_diag_record_t:
* - 计入 records(collect=true 时)
* - error/fatal 计入 n_errors,warn 计入 n_warns
* - 立刻输出到 out(如果非 NULL)
*
* 四种级别的语义区别:
* note —— 一般信息;不阻塞;用在前序诊断之外的独立说明
* warn —— 警告;不阻塞;driver 可忽略
* error —— 用户代码错误;调用者应 return 0
* fatal —— 用户代码错误到数据结构已损坏;调用者必须 return 0
* ============================================================ */
void spl_diag_note(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
...);
void spl_diag_warn(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
...);
void spl_diag_error(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
...);
void spl_diag_fatal(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
...);
/* 挂到最近一条记录(records 末尾)的 notes 列表下。
* 不生成独立记录、不计入计数,输出时显示为:
* note: <loc>
* <msg>
* 使用场景:辅助说明前一条诊断,例如
* spl_diag_error(d, DUPLICATE_SYMBOL, &n->dbg, "duplicate symbol `foo`");
* spl_diag_attach_note(d, &prev->dbg, "previously defined here");
* 约束:只在 records 非空时生效;否则静默丢弃,不报错。 */
void spl_diag_attach_note(spl_diag_t *d, const spl_dbg_node_t *loc, const char *fmt, ...);
bool spl_diag_has_error(const spl_diag_t *d);
bool spl_diag_has_fatal(const spl_diag_t *d);
usize spl_diag_error_count(const spl_diag_t *d);
usize spl_diag_warn_count(const spl_diag_t *d);
void spl_diag_summary(const spl_diag_t *d);
const char *spl_diag_kind_name(spl_diag_kind_t k);
const char *spl_diag_stage_name(spl_diag_stage_t s);
const char *spl_diag_level_name(spl_diag_level_t l);
#endif /* __SPL_DIAG_H__ */
+34
View File
@@ -0,0 +1,34 @@
#include "spl_fold.h"
static usize spl_fold_bucket_hash(usize k) { return MAP_HASH_INT(k); }
static int spl_fold_bucket_cmp(usize a, usize b) { return MAP_CMP_INT(a, b); }
void spl_fold_init(spl_fold_t *fold, void *ctx, spl_fold_eq_fn eq, spl_fold_next_fn next,
spl_fold_set_next_fn set_next) {
map_init(fold->buckets, spl_fold_bucket_hash, spl_fold_bucket_cmp);
fold->ctx = ctx;
fold->eq = eq;
fold->next = next;
fold->set_next = set_next;
}
void spl_fold_drop(spl_fold_t *fold) { map_free(fold->buckets); }
spl_fold_id_t spl_fold_find(const spl_fold_t *fold, usize hash, const void *probe) {
spl_fold_id_t cur = 0;
if (!map_get(fold->buckets, hash, &cur))
return 0;
while (cur) {
if (fold->eq(fold->ctx, cur, probe))
return cur;
cur = fold->next(fold->ctx, cur);
}
return 0;
}
void spl_fold_insert(spl_fold_t *fold, usize hash, spl_fold_id_t id) {
spl_fold_id_t head = 0;
map_get(fold->buckets, hash, &head);
fold->set_next(fold->ctx, id, head);
map_put(fold->buckets, hash, id);
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef __SPL_FOLD_H__
#define __SPL_FOLD_H__
#include "../stage0/include/utils.h"
/* 通用 intrusive 哈希集合,对应 LLVM FoldingSet 的桶链部分。
- 节点表由调用方持有,节点自带 next 字段;
- hash 只用于选择桶(本实现以完整 hash 值作桶 map 的 key),
相等始终由 eq 回调做结构比较;
- 只增不删,适合类型 intern。 */
typedef usize spl_fold_id_t;
/* 相等返回非零;probe 指向调用方的临时(尚未入表)节点。 */
typedef int (*spl_fold_eq_fn)(void *ctx, spl_fold_id_t node, const void *probe);
typedef spl_fold_id_t (*spl_fold_next_fn)(void *ctx, spl_fold_id_t node);
typedef void (*spl_fold_set_next_fn)(void *ctx, spl_fold_id_t node, spl_fold_id_t next);
typedef struct {
MAP(usize, spl_fold_id_t) buckets;
void *ctx;
spl_fold_eq_fn eq;
spl_fold_next_fn next;
spl_fold_set_next_fn set_next;
} spl_fold_t;
void spl_fold_init(spl_fold_t *fold, void *ctx, spl_fold_eq_fn eq, spl_fold_next_fn next,
spl_fold_set_next_fn set_next);
void spl_fold_drop(spl_fold_t *fold);
/* 命中返回已存在的 id,否则返回 0。 */
spl_fold_id_t spl_fold_find(const spl_fold_t *fold, usize hash, const void *probe);
/* 把已分配 id 的节点挂到对应桶头。 */
void spl_fold_insert(spl_fold_t *fold, usize hash, spl_fold_id_t id);
#endif /* __SPL_FOLD_H__ */
+68
View File
@@ -2,6 +2,8 @@
static const spl_intrin_info_t g_intrin_table[] = {
[SPL_INTRIN_NONE] = {0},
[SPL_INTRIN_BITSIZEOF] = {"@bitsizeof", SPL_INTRIN_BITSIZEOF, SPL_INTRIN_FORM_CALL, 1, 1,
SPL_INTRIN_RESULT_USIZE},
[SPL_INTRIN_SIZEOF] = {"@sizeof", SPL_INTRIN_SIZEOF, SPL_INTRIN_FORM_CALL, 1, 1,
SPL_INTRIN_RESULT_USIZE},
[SPL_INTRIN_ALIGNOF] = {"@alignof", SPL_INTRIN_ALIGNOF, SPL_INTRIN_FORM_CALL, 1, 1,
@@ -12,6 +14,40 @@ static const spl_intrin_info_t g_intrin_table[] = {
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_LEN] = {"len", SPL_INTRIN_LEN, SPL_INTRIN_FORM_FIELD, 0, 0,
SPL_INTRIN_RESULT_USIZE},
[SPL_INTRIN_INT_CAST] = {"@int_cast", SPL_INTRIN_INT_CAST, SPL_INTRIN_FORM_CALL, 2, 2,
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_TRUNCATE] = {"@truncate", SPL_INTRIN_TRUNCATE, SPL_INTRIN_FORM_CALL, 2, 2,
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_BIT_CAST] = {"@bit_cast", SPL_INTRIN_BIT_CAST, SPL_INTRIN_FORM_CALL, 2, 2,
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_FLOAT_CAST] = {"@float_cast", SPL_INTRIN_FLOAT_CAST, SPL_INTRIN_FORM_CALL, 2, 2,
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_FLOAT2INT] = {"@float2int", SPL_INTRIN_FLOAT2INT, SPL_INTRIN_FORM_CALL, 2, 2,
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_INT2FLOAT] = {"@int2float", SPL_INTRIN_INT2FLOAT, SPL_INTRIN_FORM_CALL, 2, 2,
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_BOOL2INT] = {"@bool2int", SPL_INTRIN_BOOL2INT, SPL_INTRIN_FORM_CALL, 1, 2,
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_PTR2INT] = {"@ptr2int", SPL_INTRIN_PTR2INT, SPL_INTRIN_FORM_CALL, 1, 2,
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_INT2PTR] = {"@int2ptr", SPL_INTRIN_INT2PTR, SPL_INTRIN_FORM_CALL, 2, 2,
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_PTR_CAST] = {"@ptr_cast", SPL_INTRIN_PTR_CAST, SPL_INTRIN_FORM_CALL, 2, 2,
SPL_INTRIN_RESULT_UNIT},
[SPL_INTRIN_ENUM2INT] = {"@enum2int", SPL_INTRIN_ENUM2INT, SPL_INTRIN_FORM_CALL, 1, 1,
SPL_INTRIN_RESULT_USIZE},
[SPL_INTRIN_OFFSETOF] = {"@offsetof", SPL_INTRIN_OFFSETOF, SPL_INTRIN_FORM_CALL, 2, 2,
SPL_INTRIN_RESULT_USIZE},
[SPL_INTRIN_FIELD_COUNT] = {"@field_count", SPL_INTRIN_FIELD_COUNT, SPL_INTRIN_FORM_CALL, 1, 1,
SPL_INTRIN_RESULT_USIZE},
[SPL_INTRIN_FILE_NAME] = {"@file_name", SPL_INTRIN_FILE_NAME, SPL_INTRIN_FORM_CALL, 0, 0,
SPL_INTRIN_RESULT_USIZE},
[SPL_INTRIN_LINE] = {"@line", SPL_INTRIN_LINE, SPL_INTRIN_FORM_CALL, 0, 0,
SPL_INTRIN_RESULT_USIZE},
[SPL_INTRIN_COL] = {"@col", SPL_INTRIN_COL, SPL_INTRIN_FORM_CALL, 0, 0,
SPL_INTRIN_RESULT_USIZE},
[SPL_INTRIN_FN_NAME] = {"@fn_name", SPL_INTRIN_FN_NAME, SPL_INTRIN_FORM_CALL, 0, 0,
SPL_INTRIN_RESULT_USIZE},
};
static const usize g_intrin_count = sizeof(g_intrin_table) / sizeof(g_intrin_table[0]);
@@ -46,3 +82,35 @@ spl_intrin_t spl_intrin_lookup_field(spl_lang_type_kind_t recv_kind, const char
}
return SPL_INTRIN_NONE;
}
spl_intrin_t spl_intrin_lookup_cast(spl_type_t *type, spl_type_id_t ft, spl_type_id_t tt) {
/* 这是 `as` 的展开规则表。要调整 as 的语义,改这里。 */
spl_type_node_t *f = spl_type_node(type, ft);
spl_type_node_t *t = spl_type_node(type, tt);
if (!f || !t)
return SPL_INTRIN_NONE;
if (f->kind == SPL_TYPE_INT && t->kind == SPL_TYPE_INT) {
usize fb = f->int_type.bits;
usize tb = t->int_type.bits;
if (fb == tb)
return SPL_INTRIN_BIT_CAST;
if (fb > tb)
return SPL_INTRIN_TRUNCATE;
return SPL_INTRIN_INT_CAST;
}
if (f->kind == SPL_TYPE_FLOAT && t->kind == SPL_TYPE_FLOAT)
return SPL_INTRIN_FLOAT_CAST;
if (f->kind == SPL_TYPE_BOOL && t->kind == SPL_TYPE_INT)
return SPL_INTRIN_BOOL2INT;
if (f->kind == SPL_TYPE_PTR && t->kind == SPL_TYPE_INT)
return SPL_INTRIN_PTR2INT;
if (f->kind == SPL_TYPE_INT && t->kind == SPL_TYPE_PTR)
return SPL_INTRIN_INT2PTR;
if (f->kind == SPL_TYPE_PTR && t->kind == SPL_TYPE_PTR)
return SPL_INTRIN_PTR_CAST;
/* int float 刻意不在这里:只能通过显式 builtin 调用。
enum<->int 也刻意禁止(用 @enum2int 取 tag)。 */
return SPL_INTRIN_NONE;
}
+21 -5
View File
@@ -2,15 +2,34 @@
#define __SPL_INTRIN_H__
#include "../stage0/include/utils.h"
#include "spl_type.h"
#include "spl_lang_type.h"
typedef enum {
SPL_INTRIN_NONE = 0,
SPL_INTRIN_BITSIZEOF,
SPL_INTRIN_SIZEOF,
SPL_INTRIN_ALIGNOF,
SPL_INTRIN_ASSERT,
SPL_INTRIN_BREAKPOINT,
SPL_INTRIN_LEN,
SPL_INTRIN_INT_CAST,
SPL_INTRIN_TRUNCATE,
SPL_INTRIN_BIT_CAST,
SPL_INTRIN_FLOAT_CAST,
SPL_INTRIN_FLOAT2INT,
SPL_INTRIN_INT2FLOAT,
SPL_INTRIN_BOOL2INT,
SPL_INTRIN_PTR2INT,
SPL_INTRIN_INT2PTR,
SPL_INTRIN_PTR_CAST,
SPL_INTRIN_ENUM2INT,
SPL_INTRIN_OFFSETOF,
SPL_INTRIN_FIELD_COUNT,
/* 调用点位置信息(供日志/性能分析库使用) */
SPL_INTRIN_FILE_NAME,
SPL_INTRIN_LINE,
SPL_INTRIN_COL,
SPL_INTRIN_FN_NAME,
} spl_intrin_t;
typedef enum {
@@ -33,13 +52,10 @@ typedef struct {
spl_intrin_result_t result;
} spl_intrin_info_t;
/* 统一入口:名字 → 枚举。name 允许带或不带 '@'。 */
spl_intrin_t spl_intrin_lookup_call(const char *name);
/* 统一入口:字段形态,按接收者类型分派。 */
spl_intrin_t spl_intrin_lookup_field(spl_lang_type_kind_t recv_kind, const char *field);
spl_intrin_t spl_intrin_lookup_cast(spl_type_t *type, spl_type_id_t ft, spl_type_id_t tt);
/* 从枚举取得元数据,SEMA/AST2IR 都从这里读。 */
const spl_intrin_info_t *spl_intrin_info(spl_intrin_t kind);
/* ---- 与 IR 侧共享的类型布局约定 ---- */
+24 -2
View File
@@ -203,8 +203,9 @@ static void ir_dump_node(const spl_type_t *ty, spl_ir_node_t *n) {
printf(" float=%f", n->type_const.float_lit);
} else if (t && t->kind == SPL_TYPE_FN) {
printf(" fn#%zu", n->type_const.fn);
// } else if (t && t->kind == SPL_TYPE_PTR) {
// printf(" str=\"%s\"", n->type_const.cstr_lit ? n->type_const.cstr_lit : "?");
} else if (t && t->kind == SPL_TYPE_ARRAY) {
/* 字节常量:不打印宿主指针(不确定),只标记 bytes。 */
printf(" bytes");
} else if (t && t->kind == SPL_TYPE_INT && t->int_type.bits == 8) {
printf(" char='%c'", n->type_const.ch_lit);
} else {
@@ -387,6 +388,27 @@ spl_ir_func_ref_t spl_ir_builder_fn_begin(spl_ir_builder_t *b, const char *name,
return fid;
}
spl_ir_func_ref_t spl_ir_builder_fn_reserve(spl_ir_builder_t *b, const char *name,
spl_type_id_t fn_tid) {
spl_ir_func_ref_t fid = spl_ir_alloc_fn(&b->ir);
spl_ir_func_t *f = spl_ir_func(&b->ir, fid);
f->name = name;
vec_init(f->attrs);
f->fn_tid = fn_tid;
return fid;
}
void spl_ir_builder_fn_begin_at(spl_ir_builder_t *b, spl_ir_func_ref_t fid) {
if (b->current_fn && b->current_fn != fid) {
spl_ir_builder_fn_end(b);
}
b->current_fn = fid;
b->current_block = 0;
b->blocks.size = 0;
b->fixups.size = 0;
b->terminated = false;
}
void spl_ir_builder_fn_end(spl_ir_builder_t *b) {
spl_ir_func_t *f = spl_ir_func(&b->ir, b->current_fn);
if (!f) {
+6 -2
View File
@@ -2,11 +2,10 @@
#define __SPL_IR_H__
#include "../stage0/include/utils.h"
#include "spl_attr.h"
#include "spl_dbg.h"
#include "spl_type.h"
#include "spl_attr.h"
/* clang-format off */
#define SPL_IR_FN_TABLE \
X(arith.add, V0, SPL_IR_ARITH_ADD) \
@@ -82,6 +81,8 @@ typedef VEC(spl_ir_node_ref_t) spl_ir_node_ref_vec_t;
typedef usize spl_ir_func_ref_t; /* 0 is error */
#define SPL_ENUM_TAG_FIELD_IDX 0
typedef struct {
spl_ir_kind_t kind;
spl_dbg_node_t dbg;
@@ -309,6 +310,9 @@ void spl_ir_builder_drop(spl_ir_builder_t *b);
spl_ir_func_ref_t spl_ir_builder_fn_begin(spl_ir_builder_t *b, const char *name,
spl_type_id_t fn_tid);
spl_ir_func_ref_t spl_ir_builder_fn_reserve(spl_ir_builder_t *b, const char *name,
spl_type_id_t fn_tid);
void spl_ir_builder_fn_begin_at(spl_ir_builder_t *b, spl_ir_func_ref_t fid);
void spl_ir_builder_fn_end(spl_ir_builder_t *b);
usize spl_ir_builder_block_begin(spl_ir_builder_t *b);
+460 -88
View File
@@ -1,35 +1,26 @@
#include "spl_ir2vm.h"
#include "spl_layout.h"
static usize align_up(usize x, usize a);
/* size/align/offset 统一取自 layout(spl_layout 是唯一准确来源)。 */
static const spl_layout_info_t *ir_layout(spl_ir2vm_t *ctx, spl_type_id_t tid) {
return spl_layout_info_of(ctx->layout, tid);
}
static usize ir_type_size(spl_ir2vm_t *ctx, spl_type_id_t tid) {
spl_type_node_t *tn = spl_type_node(ctx->type, tid);
if (!tn)
return 8;
switch (tn->kind) {
case SPL_TYPE_UNIT:
const spl_layout_info_t *info = ir_layout(ctx, tid);
if (!info)
return 0;
case SPL_TYPE_BOOL:
return (info->size_bits + 7) / 8;
}
static usize ir_type_align(spl_ir2vm_t *ctx, spl_type_id_t tid) {
const spl_layout_info_t *info = ir_layout(ctx, tid);
if (!info)
return 1;
case SPL_TYPE_INT:
return (tn->int_type.bits + 7) / 8;
case SPL_TYPE_FLOAT:
return (tn->float_type.bits + 7) / 8;
case SPL_TYPE_PTR:
return sizeof(void *);
case SPL_TYPE_FN:
return sizeof(void *);
case SPL_TYPE_ARRAY:
return ir_type_size(ctx, tn->array_type.element) * tn->array_type.len;
case SPL_TYPE_STRUCT:
case SPL_TYPE_UNION: {
usize s = 0;
for (usize i = 0; i < vec_size(tn->agg_type.variants); ++i)
s += ir_type_size(ctx, vec_at(tn->agg_type.variants, i).type_id);
return s;
}
default:
return 8;
}
usize a = info->align_bits / 8;
return a ? a : 1;
}
static bool ir_is_aggregate(spl_ir2vm_t *ctx, spl_type_id_t tid) {
@@ -40,10 +31,35 @@ static bool ir_is_aggregate(spl_ir2vm_t *ctx, spl_type_id_t tid) {
tn->kind == SPL_TYPE_UNION || tn->kind == SPL_TYPE_ENUM || tn->kind == SPL_TYPE_SHAPE;
}
/* 从 fn_tid 取返回类型;非 fn 类型返回 0。
集中处理 fn 类型查表,避免调用点各自 spl_type_node + kind 检查。 */
static spl_type_id_t fn_ret_type(spl_ir2vm_t *ctx, spl_type_id_t fn_tid) {
spl_type_node_t *ftn = spl_type_node(ctx->type, fn_tid);
if (!ftn || ftn->kind != SPL_TYPE_FN)
return 0;
return ftn->fn_type.ret;
}
/* 从 fn_tid 取参数个数;非 fn 类型返回 0 */
static usize fn_nargs(spl_ir2vm_t *ctx, spl_type_id_t fn_tid) {
spl_type_node_t *ftn = spl_type_node(ctx->type, fn_tid);
if (!ftn || ftn->kind != SPL_TYPE_FN)
return 0;
return vec_size(ftn->fn_type.params);
}
static spl_type_id_t ir_agg_field_tid(spl_ir2vm_t *ctx, spl_type_id_t tid, usize idx) {
spl_type_node_t *tn = spl_type_node(ctx->type, tid);
if (!tn)
return 0;
if (tn->kind == SPL_TYPE_ENUM) {
if (idx == SPL_ENUM_TAG_FIELD_IDX)
return tn->agg_type.tag_type;
usize vidx = idx - 1;
if (vidx < vec_size(tn->agg_type.variants))
return vec_at(tn->agg_type.variants, vidx).type_id;
return 0;
}
if (tn->kind == SPL_TYPE_STRUCT || tn->kind == SPL_TYPE_UNION) {
if (idx < vec_size(tn->agg_type.variants))
return vec_at(tn->agg_type.variants, idx).type_id;
@@ -57,15 +73,13 @@ static usize ir_agg_field_off(spl_ir2vm_t *ctx, spl_type_id_t tid, usize idx) {
spl_type_node_t *tn = spl_type_node(ctx->type, tid);
if (!tn)
return 0;
if (tn->kind == SPL_TYPE_STRUCT || tn->kind == SPL_TYPE_UNION) {
usize off = 0;
for (usize i = 0; i < idx && i < vec_size(tn->agg_type.variants); ++i)
off += ir_type_size(ctx, vec_at(tn->agg_type.variants, i).type_id);
return off;
}
if (tn->kind == SPL_TYPE_ARRAY)
return idx * ir_type_size(ctx, tn->array_type.element);
return 0;
const spl_layout_info_t *info = ir_layout(ctx, tid);
if (!info || idx >= vec_size(info->field_bit_offsets))
return 0;
return vec_at(info->field_bit_offsets, idx) / 8;
}
static int get_or_add_native(spl_ir2vm_t *ctx, const char *name) {
@@ -132,7 +146,12 @@ static uint8_t vm_kind_of(spl_ir2vm_t *ctx, spl_type_id_t tid) {
}
}
static spl_type_id_t ir_result_tid(const spl_ir_node_t *n) {
/* 计算 IR 节点的"值类型"。
* 语义区分:
* mem.offset(elem_t, ptr, off) → 结果类型 *elem_t
* mem.field(agg_t, base, idx) → 结果类型 *field_t
* 这两个 builder 的 .tid 存的是 pointee / aggregate,不能直接当结果类型用。 */
static spl_type_id_t ir_result_tid(spl_ir2vm_t *ctx, const spl_ir_node_t *n) {
switch (n->kind) {
case SPL_IR_ARITH_ADD:
case SPL_IR_ARITH_SUB:
@@ -168,15 +187,18 @@ static spl_type_id_t ir_result_tid(const spl_ir_node_t *n) {
case SPL_IR_CASE_FLOAT2INT:
return n->cast.to_tid;
case SPL_IR_MEM_ALLOCA:
return n->mem_alloc.tid;
return spl_type_builder_ptr((spl_type_t *)ctx->type, n->mem_alloc.tid);
case SPL_IR_MEM_GLOBAL_ALLOC:
return n->mem_global_alloc.tid;
case SPL_IR_MEM_LOAD:
return n->mem_load.tid;
case SPL_IR_MEM_OFFSET:
return n->mem_offset.tid;
case SPL_IR_MEM_FIELD_PTR:
return n->mem_field_ptr.tid;
return spl_type_builder_ptr((spl_type_t *)ctx->type, n->mem_offset.tid);
case SPL_IR_MEM_FIELD_PTR: {
spl_type_id_t field =
ir_agg_field_tid(ctx, n->mem_field_ptr.tid, n->mem_field_ptr.field_idx);
return spl_type_builder_ptr((spl_type_t *)ctx->type, field);
}
case SPL_IR_TYPE_CONST:
return n->type_const.tid;
case SPL_IR_TYPE_BITSIZEOF:
@@ -276,15 +298,16 @@ static void frame_build(frame_layout_t *fl, spl_ir2vm_t *ctx, spl_ir_func_ref_t
for (usize i = 0; i < n; ++i)
vec_push(fl->slots, (slot_t){0});
spl_type_node_t *ftn = spl_type_node(ctx->type, f->fn_tid);
usize nargs = (ftn && ftn->kind == SPL_TYPE_FN) ? vec_size(ftn->fn_type.params) : 0;
fl->args_bytes = nargs * sizeof(spl_vm_val_t);
/* 聚合返回时 fp+0 是 caller 传入的 sret 指针,占用一个隐藏参数槽 */
usize nargs = fn_nargs(ctx, f->fn_tid);
usize hidden_sret = ir_is_aggregate(ctx, fn_ret_type(ctx, f->fn_tid)) ? 1 : 0;
fl->args_bytes = (nargs + hidden_sret) * sizeof(spl_vm_val_t);
usize off = fl->args_bytes;
for (usize i = 1; i < n; ++i) {
const spl_ir_node_t *node = &vec_at(f->nodes, i);
if (ir_result_tid(node)) {
if (ir_result_tid(ctx, node)) {
vec_at(fl->slots, i).result_off = off;
off += sizeof(spl_vm_val_t);
}
@@ -321,6 +344,13 @@ typedef struct {
frame_layout_t frame;
VEC(usize) node_ip;
fix_vec_t *fixups;
/* 块内栈化(opt) */
int opt;
uint8_t *stk_loc; /* per node: 1 = 值当前在操作数栈上 */
usize *stk_pos; /* 在虚拟栈中的位置 */
usize *use_left; /* 剩余使用次数 */
usize *at_pos; /* 每个栈位置对应的 node ref */
usize tracked; /* 当前栈上被跟踪的值个数 */
} cgen_t;
static void emit(cgen_t *cg, uint8_t op, uint8_t ty, spl_vm_val_t imm) {
@@ -355,6 +385,240 @@ static void cg_store_at(cgen_t *cg, usize addr, uint8_t k) {
CG_STORE(cg, k);
}
/* ---------- 块内栈化(opt)辅助 ---------- */
static uint8_t node_result_kind(cgen_t *cg, usize ref) {
const spl_ir_node_t *n = &vec_at(cg->irf->nodes, ref);
return vm_kind_of(cg->ctx, ir_result_tid(cg->ctx, n));
}
static bool node_stackable(cgen_t *cg, const spl_ir_node_t *n) {
switch (n->kind) {
case SPL_IR_ARITH_ADD:
case SPL_IR_ARITH_SUB:
case SPL_IR_ARITH_MUL:
case SPL_IR_ARITH_DIV:
case SPL_IR_ARITH_REM:
case SPL_IR_ARITH_NEG:
case SPL_IR_ARITH_AND:
case SPL_IR_ARITH_OR:
case SPL_IR_ARITH_XOR:
case SPL_IR_ARITH_SHL:
case SPL_IR_ARITH_SHR:
case SPL_IR_ARITH_NOT:
case SPL_IR_CMP_EQ:
case SPL_IR_CMP_NE:
case SPL_IR_CMP_LT:
case SPL_IR_CMP_LE:
case SPL_IR_CMP_GT:
case SPL_IR_CMP_GE:
case SPL_IR_CAST_TRUNC:
case SPL_IR_CAST_ZEXT:
case SPL_IR_CAST_SEXT:
case SPL_IR_CAST_FEXT:
case SPL_IR_CAST_FTRUNC:
case SPL_IR_CAST_BITCAST:
case SPL_IR_CAST_PTR2INT:
case SPL_IR_CAST_INT2PTR:
case SPL_IR_CAST_BOOL2INT:
case SPL_IR_CASE_INT2FLOAT:
case SPL_IR_CASE_FLOAT2INT:
case SPL_IR_TYPE_CONST: {
spl_type_node_t *tn = spl_type_node(cg->ctx->type, n->type_const.tid);
return tn && tn->kind != SPL_TYPE_UNIT;
}
case SPL_IR_TYPE_BITSIZEOF:
case SPL_IR_TYPE_SIZEOF:
case SPL_IR_TYPE_ALIGNOF:
case SPL_IR_TYPE_OFFSETOF:
case SPL_IR_TYPE_FIELD_COUNT:
case SPL_IR_MEM_OFFSET:
case SPL_IR_MEM_FIELD_PTR:
return true;
case SPL_IR_MEM_LOAD:
return !ir_is_aggregate(cg->ctx, n->mem_load.tid);
default:
return false;
}
}
/* 该节点必须使用槽(直接按槽偏移读写 / 控制转移 / 聚合数据),生成前需 flush。 */
static bool node_needs_slots(cgen_t *cg, const spl_ir_node_t *n) {
switch (n->kind) {
case SPL_IR_MEM_ALLOCA:
case SPL_IR_MEM_GLOBAL_ALLOC:
case SPL_IR_MEM_COPY:
case SPL_IR_MEM_SET:
case SPL_IR_AGG_CONSTRUCT:
case SPL_IR_AGG_EXTRACT:
case SPL_IR_AGG_INSERT:
case SPL_IR_CONTROL_SELECT:
case SPL_IR_CONTROL_BR:
case SPL_IR_CONTROL_JMP:
case SPL_IR_CONTROL_RET:
case SPL_IR_CONTROL_UNREACHABLE:
case SPL_IR_CONTROL_TRAP:
case SPL_IR_DBG_PANIC:
case SPL_IR_DBG_BREAKPOINT:
return true;
case SPL_IR_MEM_LOAD:
return ir_is_aggregate(cg->ctx, n->mem_load.tid);
case SPL_IR_MEM_STORE:
return ir_is_aggregate(cg->ctx, n->mem_store.tid);
default:
return false;
}
}
/* 取操作数到 TOS:若在栈上就 PICK,否则从槽 load。j = 本节点已压入的操作数个数。 */
static void cg_operand(cgen_t *cg, usize ref, uint8_t k, usize j) {
if (cg->opt) {
if (cg->use_left[ref] > 0)
cg->use_left[ref]--;
if (cg->stk_loc[ref]) {
/* 始终复制一份到 TOS,绝不消费栈上的“被跟踪原值”,否则位置模型失效。 */
usize dist = cg->tracked + j - 1 - cg->stk_pos[ref];
emit(cg, SPL_PICK, SPL_VOID, dist);
return;
}
}
cg_load_slot(cg, ref, k);
}
/* 节点结果:若仍会被使用则留在栈上,否则 DROP。 */
static void cg_result(cgen_t *cg, usize ref, uint8_t k) {
if (cg->opt) {
if (cg->use_left[ref] > 0) {
cg->stk_loc[ref] = 1;
cg->stk_pos[ref] = cg->tracked;
cg->at_pos[cg->tracked] = ref;
cg->tracked++;
} else {
emit(cg, SPL_DROP, SPL_VOID, 0);
}
return;
}
cg_store_slot(cg, ref, k);
}
/* 把栈上仍存活的值溢出回各自槽(TOS 起),死值 DROP;清空跟踪。 */
static void cg_flush(cgen_t *cg) {
if (!cg->opt)
return;
while (cg->tracked > 0) {
cg->tracked--;
usize r = cg->at_pos[cg->tracked];
cg->stk_loc[r] = 0;
if (cg->use_left[r] > 0) {
uint8_t k = node_result_kind(cg, r);
cg_store_slot(cg, r, k);
} else {
emit(cg, SPL_DROP, SPL_VOID, 0);
}
}
}
/* 统计被引用次数(所有节点载荷中的 node ref)。 */
static void count_ref(cgen_t *cg, usize r) {
if (r != 0 && r < vec_size(cg->irf->nodes))
cg->use_left[r]++;
}
static void count_refs_vec(cgen_t *cg, const spl_ir_node_ref_vec_t *v) {
for (usize i = 0; i < vec_size(*v); i++)
count_ref(cg, vec_at(*v, i));
}
static void count_refs(cgen_t *cg, const spl_ir_node_t *n) {
switch (n->kind) {
case SPL_IR_ARITH_ADD: case SPL_IR_ARITH_SUB: case SPL_IR_ARITH_MUL:
case SPL_IR_ARITH_DIV: case SPL_IR_ARITH_REM: case SPL_IR_ARITH_NEG:
case SPL_IR_ARITH_ABS: case SPL_IR_ARITH_AND: case SPL_IR_ARITH_OR:
case SPL_IR_ARITH_XOR: case SPL_IR_ARITH_SHL: case SPL_IR_ARITH_SHR:
case SPL_IR_ARITH_NOT:
count_ref(cg, n->arith.left);
count_ref(cg, n->arith.right);
break;
case SPL_IR_CMP_EQ: case SPL_IR_CMP_NE: case SPL_IR_CMP_LT:
case SPL_IR_CMP_LE: case SPL_IR_CMP_GT: case SPL_IR_CMP_GE:
count_ref(cg, n->cmp.a);
count_ref(cg, n->cmp.b);
break;
case SPL_IR_CAST_TRUNC: case SPL_IR_CAST_ZEXT: case SPL_IR_CAST_SEXT:
case SPL_IR_CAST_FEXT: case SPL_IR_CAST_FTRUNC: case SPL_IR_CAST_BITCAST:
case SPL_IR_CAST_PTR2INT: case SPL_IR_CAST_INT2PTR: case SPL_IR_CAST_BOOL2INT:
case SPL_IR_CASE_INT2FLOAT: case SPL_IR_CASE_FLOAT2INT:
count_ref(cg, n->cast.val);
break;
case SPL_IR_MEM_ALLOCA:
count_ref(cg, n->mem_alloc.count);
break;
case SPL_IR_MEM_GLOBAL_ALLOC:
count_ref(cg, n->mem_global_alloc.const_node);
break;
case SPL_IR_MEM_LOAD:
count_ref(cg, n->mem_load.ptr);
break;
case SPL_IR_MEM_STORE:
count_ref(cg, n->mem_store.ptr);
count_ref(cg, n->mem_store.val);
break;
case SPL_IR_MEM_OFFSET:
count_ref(cg, n->mem_offset.ptr);
count_ref(cg, n->mem_offset.offset);
break;
case SPL_IR_MEM_FIELD_PTR:
count_ref(cg, n->mem_field_ptr.agg);
break;
case SPL_IR_MEM_COPY:
count_ref(cg, n->mem_copy.dst);
count_ref(cg, n->mem_copy.src);
count_ref(cg, n->mem_copy.size);
break;
case SPL_IR_MEM_SET:
count_ref(cg, n->mem_set.dst);
count_ref(cg, n->mem_set.val);
count_ref(cg, n->mem_set.size);
break;
case SPL_IR_TYPE_OFFSETOF:
count_ref(cg, n->ir_offsetof.field_idx);
break;
case SPL_IR_AGG_CONSTRUCT:
count_refs_vec(cg, &n->agg_construct.fields);
break;
case SPL_IR_AGG_EXTRACT:
count_ref(cg, n->agg_extract.val);
break;
case SPL_IR_AGG_INSERT:
count_ref(cg, n->agg_insert.agg);
count_ref(cg, n->agg_insert.field);
break;
case SPL_IR_CONTROL_SELECT:
count_ref(cg, n->control_select.cond);
count_ref(cg, n->control_select.true_val);
count_ref(cg, n->control_select.false_val);
break;
case SPL_IR_CONTROL_BR:
count_ref(cg, n->control_br.cond);
count_ref(cg, n->control_br.true_label);
count_ref(cg, n->control_br.false_label);
break;
case SPL_IR_CONTROL_JMP:
count_ref(cg, n->control_jmp.label);
break;
case SPL_IR_CONTROL_CALL:
count_ref(cg, n->control_call.func);
count_refs_vec(cg, &n->control_call.params);
break;
case SPL_IR_CONTROL_PARAM:
count_ref(cg, n->control_param.idx);
break;
case SPL_IR_CONTROL_RET:
count_ref(cg, n->control_ret.val);
break;
default:
break;
}
}
typedef struct {
bool indirect;
usize off;
@@ -416,25 +680,25 @@ static void cg_add_fix(cgen_t *cg, usize ins_idx, fix_kind_t kind, spl_ir_node_r
static void cg_binop(cgen_t *cg, usize ref, const spl_ir_node_t *n, uint8_t op) {
uint8_t k = vm_kind_of(cg->ctx, n->arith.tid);
cg_load_slot(cg, n->arith.left, k);
cg_load_slot(cg, n->arith.right, k);
cg_operand(cg, n->arith.left, k, 0);
cg_operand(cg, n->arith.right, k, 1);
CG_OP(cg, op, k);
cg_store_slot(cg, ref, k);
cg_result(cg, ref, k);
}
static void cg_unop(cgen_t *cg, usize ref, const spl_ir_node_t *n, uint8_t op) {
uint8_t k = vm_kind_of(cg->ctx, n->arith.tid);
cg_load_slot(cg, n->arith.left, k);
cg_operand(cg, n->arith.left, k, 0);
CG_OP(cg, op, k);
cg_store_slot(cg, ref, k);
cg_result(cg, ref, k);
}
static void cg_cmp(cgen_t *cg, usize ref, const spl_ir_node_t *n, uint8_t op) {
uint8_t k = vm_kind_of(cg->ctx, n->cmp.tid);
cg_load_slot(cg, n->cmp.a, k);
cg_load_slot(cg, n->cmp.b, k);
cg_operand(cg, n->cmp.a, k, 0);
cg_operand(cg, n->cmp.b, k, 1);
CG_OP(cg, op, k);
cg_store_slot(cg, ref, SPL_BOOL);
cg_result(cg, ref, SPL_BOOL);
}
static void cg_const(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
@@ -456,7 +720,7 @@ static void cg_const(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
CG_PUSH(cg, SPL_PTR, 0);
cg_add_fix(cg, idx, FIX_FNADDR, 0, n->type_const.fn);
}
cg_store_slot(cg, ref, SPL_PTR);
cg_result(cg, ref, SPL_PTR);
return;
}
@@ -484,7 +748,7 @@ static void cg_const(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
break;
}
CG_PUSH(cg, k, imm);
cg_store_slot(cg, ref, k);
cg_result(cg, ref, k);
}
static void cg_cast(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
@@ -497,7 +761,7 @@ static void cg_cast(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
uint8_t fk = vm_kind_of(ctx, n->cast.from_tid);
uint8_t tk = vm_kind_of(ctx, n->cast.to_tid);
cg_load_slot(cg, n->cast.val, fk);
cg_operand(cg, n->cast.val, fk, 0);
if (ft->kind == SPL_TYPE_INT && tt->kind == SPL_TYPE_INT) {
usize fb = ft->int_type.bits;
@@ -510,7 +774,7 @@ static void cg_cast(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
emit(cg, SPL_ZEXT, SPL_VOID, 1);
}
cg_store_slot(cg, ref, tk);
cg_result(cg, ref, tk);
}
static void cg_mem(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
@@ -548,9 +812,9 @@ static void cg_mem(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
cg_store_slot(cg, ref, SPL_PTR);
} else {
uint8_t k = vm_kind_of(ctx, tid);
cg_load_slot(cg, n->mem_load.ptr, SPL_PTR);
cg_operand(cg, n->mem_load.ptr, SPL_PTR, 0);
CG_LOAD(cg, k);
cg_store_slot(cg, ref, k);
cg_result(cg, ref, k);
}
break;
}
@@ -562,8 +826,8 @@ static void cg_mem(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
ptr_slot(vec_at(cg->frame.slots, n->mem_store.ptr).result_off), tid);
} else {
uint8_t k = vm_kind_of(ctx, tid);
cg_load_slot(cg, n->mem_store.ptr, SPL_PTR);
cg_load_slot(cg, n->mem_store.val, k);
cg_operand(cg, n->mem_store.ptr, SPL_PTR, 0);
cg_operand(cg, n->mem_store.val, k, 1);
CG_STORE(cg, k);
}
break;
@@ -573,24 +837,24 @@ static void cg_mem(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
usize elem_size = ir_type_size(ctx, n->mem_offset.tid);
if (elem_size == 0)
elem_size = 1;
cg_load_slot(cg, n->mem_offset.ptr, SPL_PTR);
cg_load_slot(cg, n->mem_offset.offset, SPL_U64);
cg_operand(cg, n->mem_offset.ptr, SPL_PTR, 0);
cg_operand(cg, n->mem_offset.offset, SPL_U64, 1);
CG_PUSH(cg, SPL_U64, elem_size);
CG_OP(cg, SPL_MUL, SPL_U64);
CG_OP(cg, SPL_ADD, SPL_PTR);
cg_store_slot(cg, ref, SPL_PTR);
cg_result(cg, ref, SPL_PTR);
break;
}
case SPL_IR_MEM_FIELD_PTR: {
spl_type_id_t agg_tid = n->mem_field_ptr.tid;
usize off = ir_agg_field_off(ctx, agg_tid, n->mem_field_ptr.field_idx);
cg_load_slot(cg, n->mem_field_ptr.agg, SPL_PTR);
cg_operand(cg, n->mem_field_ptr.agg, SPL_PTR, 0);
if (off) {
CG_PUSH(cg, SPL_U64, off);
CG_OP(cg, SPL_ADD, SPL_PTR);
}
cg_store_slot(cg, ref, SPL_PTR);
cg_result(cg, ref, SPL_PTR);
break;
}
@@ -690,17 +954,36 @@ static void cg_typeop(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
case SPL_IR_TYPE_SIZEOF:
v = ir_type_size(ctx, n->ir_sizeof.tid);
break;
case SPL_IR_TYPE_BITSIZEOF:
v = ir_type_size(ctx, n->bitsizeof.tid) * 8;
case SPL_IR_TYPE_BITSIZEOF: {
const spl_layout_info_t *info = ir_layout(ctx, n->bitsizeof.tid);
v = info ? info->size_bits : 0;
break;
}
case SPL_IR_TYPE_ALIGNOF:
v = ir_type_size(ctx, n->ir_alignof.tid);
v = ir_type_align(ctx, n->ir_alignof.tid);
break;
case SPL_IR_TYPE_OFFSETOF: {
usize idx = 0;
if (n->ir_offsetof.field_idx < vec_size(cg->irf->nodes)) {
const spl_ir_node_t *idx_node = &vec_at(cg->irf->nodes, n->ir_offsetof.field_idx);
if (idx_node->kind == SPL_IR_TYPE_CONST)
idx = (usize)idx_node->type_const.int_lit;
}
v = ir_agg_field_off(ctx, n->ir_offsetof.tid, idx);
break;
}
case SPL_IR_TYPE_FIELD_COUNT: {
spl_type_node_t *tn = spl_type_node(ctx->type, n->field_count.tid);
if (tn && (tn->kind == SPL_TYPE_STRUCT || tn->kind == SPL_TYPE_UNION ||
tn->kind == SPL_TYPE_ENUM || tn->kind == SPL_TYPE_SHAPE))
v = vec_size(tn->agg_type.variants);
break;
}
default:
return;
}
CG_PUSH(cg, SPL_U64, v);
cg_store_slot(cg, ref, SPL_U64);
cg_result(cg, ref, SPL_U64);
}
static void cg_control(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
@@ -715,14 +998,30 @@ static void cg_control(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
if (idx_node->kind == SPL_IR_TYPE_CONST)
idx = (usize)idx_node->type_const.int_lit;
}
CG_LADDR(cg, idx * sizeof(spl_vm_val_t));
/* 聚合返回时 fp+0 是 sret 指针,实参从 fp+8 起 */
usize shift = ir_is_aggregate(ctx, fn_ret_type(ctx, cg->irf->fn_tid)) ? 1 : 0;
CG_LADDR(cg, (idx + shift) * sizeof(spl_vm_val_t));
CG_LOAD(cg, k);
cg_store_slot(cg, ref, k);
break;
}
case SPL_IR_CONTROL_RET: {
uint8_t k = vm_kind_of(ctx, n->control_ret.tid);
spl_type_id_t ret_tid = n->control_ret.tid;
if (ir_is_aggregate(ctx, ret_tid)) {
/* sret:把返回值写入 *(fp+0),然后以 void 返回 */
if (n->control_ret.val) {
usize sz = ir_type_size(ctx, ret_tid);
CG_LADDR(cg, 0);
CG_LOAD(cg, SPL_PTR);
cg_load_slot(cg, n->control_ret.val, SPL_PTR);
CG_PUSH(cg, SPL_U64, sz);
emit(cg, SPL_MEMCPY, SPL_VOID, 0);
}
emit(cg, SPL_RET, SPL_VOID, 0);
break;
}
uint8_t k = vm_kind_of(ctx, ret_tid);
if (k != SPL_VOID && n->control_ret.val)
cg_load_slot(cg, n->control_ret.val, k);
emit(cg, SPL_RET, k, 0);
@@ -766,32 +1065,56 @@ static void cg_control(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
for (usize i = 0; i < nargs; ++i) {
spl_ir_node_ref_t pref = vec_at(n->control_call.params, i);
const spl_ir_node_t *pn = &vec_at(cg->irf->nodes, pref);
spl_type_id_t ptid = ir_result_tid(pn);
spl_type_id_t ptid = ir_result_tid(ctx, pn);
uint8_t pk = vm_kind_of(ctx, ptid);
cg_load_slot(cg, pref, pk);
cg_operand(cg, pref, pk, i);
}
CG_PUSH(cg, SPL_U64, (spl_vm_val_t)nat_idx);
emit(cg, SPL_NCALL, SPL_VOID, (spl_vm_val_t)nargs);
uint8_t rk = vm_kind_of(ctx, n->control_call.tid);
if (rk == SPL_VOID)
emit(cg, SPL_DROP, SPL_VOID, 0); /* NCALL 总是 push 一个结果 */
emit(cg, SPL_DROP, SPL_VOID, 0); /* NCALL always push result */
else
cg_store_slot(cg, ref, rk);
cg_result(cg, ref, rk);
} else {
spl_type_id_t ret_t = n->control_call.tid;
bool ret_is_sret = ir_is_aggregate(ctx, ret_t);
usize sret_off = 0;
/* j = 本节点已压入 tracked 块之上的值的个数(用于栈化 PICK 距离)。
先压 2 个 ABI 空闲 slot,故从 2 起算。 */
usize j = 2;
/* ABI:实参下方预留 2 个空闲 slot(PAD0/PAD1)。 */
CG_PUSH(cg, SPL_U64, 0);
CG_PUSH(cg, SPL_U64, 0);
if (ret_is_sret) {
sret_off = vec_at(cg->frame.slots, ref).data_off;
CG_LADDR(cg, sret_off);
j = 3;
}
for (usize i = 0; i < nargs; ++i) {
spl_ir_node_ref_t pref = vec_at(n->control_call.params, i);
const spl_ir_node_t *pn = &vec_at(cg->irf->nodes, pref);
spl_type_id_t ptid = ir_result_tid(pn);
spl_type_id_t ptid = ir_result_tid(ctx, pn);
uint8_t pk = vm_kind_of(ctx, ptid);
cg_load_slot(cg, pref, pk);
cg_operand(cg, pref, pk, j);
j++;
}
cg_load_slot(cg, fref, SPL_PTR);
emit(cg, SPL_CALL, SPL_VOID, (spl_vm_val_t)nargs);
cg_operand(cg, fref, SPL_PTR, j);
spl_vm_val_t actual_nargs = nargs + (ret_is_sret ? 1 : 0);
emit(cg, SPL_CALL, SPL_VOID, actual_nargs);
uint8_t rk = vm_kind_of(ctx, n->control_call.tid);
if (rk != SPL_VOID)
cg_store_slot(cg, ref, rk);
if (ret_is_sret) {
/* result slot saves sret pointer */
CG_LADDR(cg, sret_off);
cg_result(cg, ref, SPL_PTR);
} else {
uint8_t rk = vm_kind_of(ctx, ret_t);
if (rk != SPL_VOID)
cg_result(cg, ref, rk);
}
}
break;
}
@@ -857,10 +1180,10 @@ static void cg_node(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
case SPL_IR_ARITH_NOT: {
spl_type_node_t *tn = spl_type_node(cg->ctx->type, n->arith.tid);
if (tn && tn->kind == SPL_TYPE_BOOL) {
cg_load_slot(cg, n->arith.left, SPL_BOOL);
CG_PUSH(cg, SPL_U8, 0);
CG_OP(cg, SPL_EQ, SPL_U8);
cg_store_slot(cg, ref, SPL_BOOL);
cg_operand(cg, n->arith.left, SPL_BOOL, 0);
CG_PUSH(cg, SPL_BOOL, 0);
CG_OP(cg, SPL_EQ, SPL_BOOL);
cg_result(cg, ref, SPL_BOOL);
} else {
cg_unop(cg, ref, n, SPL_NOT);
}
@@ -932,6 +1255,8 @@ static void cg_node(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
case SPL_IR_TYPE_SIZEOF:
case SPL_IR_TYPE_BITSIZEOF:
case SPL_IR_TYPE_ALIGNOF:
case SPL_IR_TYPE_OFFSETOF:
case SPL_IR_TYPE_FIELD_COUNT:
cg_typeop(cg, ref, n);
break;
@@ -1007,8 +1332,7 @@ static usize translate_function(spl_ir2vm_t *ctx, spl_ir_func_ref_t fid, fix_vec
spl_vm_func_t entry = {0};
entry.name = strdup(irf->name ? irf->name : "");
entry.idx_of_strtab = 0;
spl_type_node_t *ftn = spl_type_node(ctx->type, irf->fn_tid);
entry.nargs = (ftn && ftn->kind == SPL_TYPE_FN) ? vec_size(ftn->fn_type.params) : 0;
entry.nargs = fn_nargs(ctx, irf->fn_tid);
entry.ninsns = 0;
entry.address = func_ip;
vec_push(ctx->prog.funcs, entry);
@@ -1021,9 +1345,49 @@ static usize translate_function(spl_ir2vm_t *ctx, spl_ir_func_ref_t fid, fix_vec
for (usize i = 0; i < nnodes; ++i)
vec_push(cg.node_ip, 0);
unsigned char *leader = NULL;
cg.opt = ctx->opt;
if (ctx->opt) {
cg.stk_loc = (uint8_t *)calloc(nnodes, 1);
cg.stk_pos = (usize *)calloc(nnodes, sizeof(usize));
cg.use_left = (usize *)calloc(nnodes, sizeof(usize));
cg.at_pos = (usize *)calloc(nnodes, sizeof(usize));
leader = (unsigned char *)calloc(nnodes, 1);
cg.tracked = 0;
for (usize i = 1; i < nnodes; ++i)
count_refs(&cg, &vec_at(irf->nodes, i));
if (nnodes > 1)
leader[1] = 1;
for (usize i = 1; i < nnodes; ++i) {
const spl_ir_node_t *n = &vec_at(irf->nodes, i);
if (n->kind == SPL_IR_CONTROL_BR) {
if (n->control_br.true_label < nnodes)
leader[n->control_br.true_label] = 1;
if (n->control_br.false_label < nnodes)
leader[n->control_br.false_label] = 1;
} else if (n->kind == SPL_IR_CONTROL_JMP) {
if (n->control_jmp.label < nnodes)
leader[n->control_jmp.label] = 1;
}
}
}
for (usize i = 1; i < nnodes; ++i) {
const spl_ir_node_t *n = &vec_at(irf->nodes, i);
if (cg.opt) {
if (leader[i] || node_needs_slots(&cg, n))
cg_flush(&cg);
}
vec_at(cg.node_ip, i) = ip_of(&cg);
cg_node(&cg, i, &vec_at(irf->nodes, i));
cg_node(&cg, i, n);
}
if (cg.opt) {
cg_flush(&cg);
free(cg.stk_loc);
free(cg.stk_pos);
free(cg.use_left);
free(cg.at_pos);
free(leader);
}
for (usize i = 0; i < vec_size(local_fixups); ++i) {
@@ -1047,13 +1411,17 @@ static usize spl_map_hash(usize k) { return MAP_HASH_INT(k); }
static int spl_map_cmp(usize a, usize b) { return MAP_CMP_INT(a, b); }
void spl_ir2vm_init(spl_ir2vm_t *ctx, const spl_ir_t *ir, const spl_type_t *type,
const spl_attr_t *attr) {
spl_layout_t *layout, const spl_attr_t *attr, int opt) {
Assert(ir != NULL && type != NULL && attr != NULL);
ctx->ir = ir;
ctx->type = type;
ctx->attr = attr;
ctx->layout = layout;
ctx->opt = opt;
map_init(ctx->extern_native, spl_map_hash, spl_map_cmp);
spl_prog_init(&ctx->prog);
ctx->diag = NULL;
}
void spl_ir2vm_drop(spl_ir2vm_t *ctx) {
@@ -1085,6 +1453,10 @@ int spl_ir2vm_run(spl_ir2vm_t *ctx) {
}
}
/* 保留 insn 0 作为哨兵:确保真实函数入口地址非零,
从而 0 可唯一表示 null 函数指针(函数指针与 null 比较)。 */
spl_prog_add_instr(&ctx->prog, SPL_HALT, SPL_VOID, 0);
/* Pass 2:翻译非 extern 的函数 */
usize *fid_ip = calloc(n, sizeof(usize));
fix_vec_t all_fixups;
+7 -3
View File
@@ -1,7 +1,9 @@
#ifndef __SPL_IR2VM_H__
#define __SPL_IR2VM_H__
#include "spl_diag.h"
#include "spl_ir.h"
#include "spl_layout.h"
#include "spl_type.h"
#include "../stage0/spl_mcode.h"
@@ -13,19 +15,21 @@ typedef struct {
typedef VEC(spl_ir2vm_fdbg_t) spl_ir2vm_fdbg_vec_t;
typedef MAP(spl_ir_func_ref_t, isize) spl_extern_idx_map_t;
// MAP()
typedef struct {
const spl_ir_t *ir;
const spl_type_t *type;
const spl_attr_t *attr;
spl_extern_idx_map_t extern_native;
spl_prog_t prog;
spl_layout_t *layout; /* 借用 splc0 的 layout(size/align/offset 唯一来源) */
// spl_ir2vm_fdbg_vec_t fdbg; /* ir2vm_run 后填充;splc0 -g 读取生成 debug 段 */
spl_diag_t *diag;
int opt; /* 非 0:块内操作数栈化(表达式临时值留栈,块边界溢出回槽) */
} spl_ir2vm_t;
void spl_ir2vm_init(spl_ir2vm_t *ctx, const spl_ir_t *ir, const spl_type_t *type,
const spl_attr_t *attr);
spl_layout_t *layout, const spl_attr_t *attr, int opt);
void spl_ir2vm_drop(spl_ir2vm_t *ctx);
int spl_ir2vm_run_func(spl_ir2vm_t *ctx, spl_ir_func_ref_t ref);
+836
View File
@@ -0,0 +1,836 @@
#include "spl_lang_type.h"
static usize spl_lang_type_hash(spl_lang_type_node_t n) {
usize hash = spl_hash_seed();
hash = spl_hash_mix(hash, (usize)n.kind);
hash = spl_hash_mix(hash, n.ir_type_id);
switch (n.kind) {
case SPL_LANG_TYPE_PTR:
case SPL_LANG_TYPE_SLICE:
case SPL_LANG_TYPE_RANGE:
hash = spl_hash_mix(hash, n.extra.elem);
break;
case SPL_LANG_TYPE_TUPLE:
hash = spl_hash_mix(hash, vec_size(n.extra.elems));
for (usize i = 0; i < vec_size(n.extra.elems); i++) {
hash = spl_hash_mix(hash, vec_at(n.extra.elems, i));
}
break;
case SPL_LANG_TYPE_FN:
hash = spl_hash_mix(hash, n.extra.fn.ret);
hash = spl_hash_mix(hash, vec_size(n.extra.fn.params));
for (usize i = 0; i < vec_size(n.extra.fn.params); i++) {
hash = spl_hash_mix(hash, vec_at(n.extra.fn.params, i));
}
break;
case SPL_LANG_TYPE_STRUCT:
case SPL_LANG_TYPE_UNION:
case SPL_LANG_TYPE_ENUM:
case SPL_LANG_TYPE_SHAPE:
hash = spl_hash_mix(hash, vec_size(n.methods));
for (usize i = 0; i < vec_size(n.methods); i++) {
spl_lang_method_t *m = &vec_at(n.methods, i);
hash = spl_hash_mix(hash, m->def_id);
hash = spl_hash_mix_str(hash, m->name);
}
break;
default:
break;
}
return hash;
}
static int spl_lang_type_eq(spl_lang_type_node_t n1, spl_lang_type_node_t n2) {
if (n1.kind != n2.kind || n1.ir_type_id != n2.ir_type_id)
return 1;
switch (n1.kind) {
case SPL_LANG_TYPE_PTR:
case SPL_LANG_TYPE_SLICE:
case SPL_LANG_TYPE_RANGE:
return n1.extra.elem != n2.extra.elem;
case SPL_LANG_TYPE_TUPLE:
if (vec_size(n1.extra.elems) != vec_size(n2.extra.elems))
return 1;
for (usize i = 0; i < vec_size(n1.extra.elems); i++) {
if (vec_at(n1.extra.elems, i) != vec_at(n2.extra.elems, i))
return 1;
}
return 0;
case SPL_LANG_TYPE_FN:
if (n1.extra.fn.ret != n2.extra.fn.ret)
return 1;
if (vec_size(n1.extra.fn.params) != vec_size(n2.extra.fn.params))
return 1;
for (usize i = 0; i < vec_size(n1.extra.fn.params); i++) {
if (vec_at(n1.extra.fn.params, i) != vec_at(n2.extra.fn.params, i))
return 1;
}
return 0;
case SPL_LANG_TYPE_STRUCT:
case SPL_LANG_TYPE_UNION:
case SPL_LANG_TYPE_ENUM:
case SPL_LANG_TYPE_SHAPE:
if (vec_size(n1.methods) != vec_size(n2.methods))
return 1;
for (usize i = 0; i < vec_size(n1.methods); i++) {
spl_lang_method_t *a = &vec_at(n1.methods, i);
spl_lang_method_t *b = &vec_at(n2.methods, i);
if (a->def_id != b->def_id)
return 1;
if ((a->name == NULL) != (b->name == NULL))
return 1;
if (a->name && strcmp(a->name, b->name) != 0)
return 1;
}
return 0;
default:
return 0;
}
}
static void spl_lang_node_free_extra(spl_lang_type_node_t *n) {
switch (n->kind) {
case SPL_LANG_TYPE_TUPLE:
vec_free(n->extra.elems);
break;
case SPL_LANG_TYPE_FN:
vec_free(n->extra.fn.params);
break;
case SPL_LANG_TYPE_STRUCT:
case SPL_LANG_TYPE_UNION:
case SPL_LANG_TYPE_ENUM:
case SPL_LANG_TYPE_SHAPE:
vec_free(n->methods);
break;
default:
break;
}
}
/* spl_fold 回调:只在链内做结构比较(同桶已保证 hash 相同)。 */
static int spl_lang_fold_eq(void *ctx, spl_fold_id_t node, const void *probe) {
spl_lang_type_t *type = (spl_lang_type_t *)ctx;
return spl_lang_type_eq(vec_at(type->lang_table, node),
*(const spl_lang_type_node_t *)probe) == 0;
}
static spl_fold_id_t spl_lang_fold_next(void *ctx, spl_fold_id_t node) {
spl_lang_type_t *type = (spl_lang_type_t *)ctx;
return vec_at(type->lang_table, node).hash_next;
}
static void spl_lang_fold_set_next(void *ctx, spl_fold_id_t node, spl_fold_id_t next) {
spl_lang_type_t *type = (spl_lang_type_t *)ctx;
vec_at(type->lang_table, node).hash_next = next;
}
spl_lang_type_id_t spl_lang_node_push(spl_lang_type_t *type, spl_lang_type_node_t *node) {
usize hash = spl_lang_type_hash(*node);
spl_lang_type_id_t existing = spl_fold_find(&type->lang_fold, hash, node);
if (existing) {
spl_lang_node_free_extra(node);
return existing;
}
spl_lang_type_id_t id = vec_size(type->lang_table);
vec_push(type->lang_table, *node);
spl_fold_insert(&type->lang_fold, hash, id);
node->kind = SPL_LANG_TYPE_ERROR;
return id;
}
void spl_lang_type_init(spl_lang_type_t *type) {
spl_type_init(&type->ir);
vec_init(type->lang_table);
spl_lang_type_node_t lang_node = {0};
vec_push(type->lang_table, lang_node);
spl_fold_init(&type->lang_fold, type, spl_lang_fold_eq, spl_lang_fold_next,
spl_lang_fold_set_next);
vec_init(type->def_table);
vec_push(type->def_table, (spl_def_node_t){0});
}
void spl_lang_type_drop(spl_lang_type_t *type) {
for (usize i = 0; i < vec_size(type->lang_table); i++) {
spl_lang_type_node_t *n = &vec_at(type->lang_table, i);
spl_lang_node_free_extra(n);
}
spl_fold_drop(&type->lang_fold);
vec_free(type->lang_table);
vec_free(type->def_table);
spl_type_drop(&type->ir);
}
spl_lang_type_node_t *spl_lang_type_node(const spl_lang_type_t *type, spl_lang_type_id_t id) {
if (!id || id >= vec_size(type->lang_table))
return NULL;
return &vec_at(type->lang_table, id);
}
spl_def_node_t *spl_def_node(const spl_lang_type_t *type, spl_def_id_t id) {
if (!id || id >= vec_size(type->def_table))
return NULL;
return &vec_at(type->def_table, id);
}
spl_type_id_t spl_lang_type_get_ir_type(spl_lang_type_t *type, spl_lang_type_id_t lang_id) {
spl_lang_type_node_t *n = spl_lang_type_node(type, lang_id);
return n ? n->ir_type_id : 0;
}
spl_type_id_t spl_lang_reserve_ir(spl_lang_type_t *type) {
spl_type_node_t empty = {0};
empty.kind = SPL_TYPE_ERROR;
empty.layout.mode = SPL_TYPE_LAYOUT_AUTO;
vec_push(type->ir.type_table, empty);
return vec_size(type->ir.type_table) - 1;
}
void spl_lang_define_ir(spl_lang_type_t *type, spl_type_id_t id, spl_type_node_t *node) {
if (!id || id >= vec_size(type->ir.type_table))
return;
spl_type_node_t *slot = &vec_at(type->ir.type_table, id);
/* slot 原是空 ERROR 节点,无 vec 字段可释放。node 的 vec 字段 move 进 slot。
占位 id 不写 type_fold,不参与 hash-consing。 */
*slot = *node;
node->kind = SPL_TYPE_ERROR;
}
static spl_lang_type_id_t spl_lang_node_simple(spl_lang_type_t *type,
spl_lang_type_kind_t lang_kind,
spl_type_id_t ir_type_id) {
spl_lang_type_node_t node = {0};
node.kind = lang_kind;
node.ir_type_id = ir_type_id;
return spl_lang_node_push(type, &node);
}
spl_lang_type_id_t spl_lang_make_error(spl_lang_type_t *type) {
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_ERROR);
return spl_lang_node_simple(type, SPL_LANG_TYPE_ERROR, ir);
}
spl_lang_type_id_t spl_lang_make_infer(spl_lang_type_t *type) {
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_INFER);
return spl_lang_node_simple(type, SPL_LANG_TYPE_INFER, ir);
}
spl_lang_type_id_t spl_lang_make_never(spl_lang_type_t *type) {
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_NEVER);
return spl_lang_node_simple(type, SPL_LANG_TYPE_NEVER, ir);
}
spl_lang_type_id_t spl_lang_make_any(spl_lang_type_t *type) {
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_ANY);
return spl_lang_node_simple(type, SPL_LANG_TYPE_ANY, ir);
}
spl_lang_type_id_t spl_lang_make_unit(spl_lang_type_t *type) {
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_UNIT);
return spl_lang_node_simple(type, SPL_LANG_TYPE_UNIT, ir);
}
spl_lang_type_id_t spl_lang_make_bool(spl_lang_type_t *type) {
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_BOOL);
return spl_lang_node_simple(type, SPL_LANG_TYPE_BOOL, ir);
}
spl_lang_type_id_t spl_lang_make_int(spl_lang_type_t *type, usize bits, int is_signed) {
spl_type_id_t ir = spl_type_builder_int(&type->ir, bits, is_signed);
return spl_lang_node_simple(type, SPL_LANG_TYPE_INT, ir);
}
spl_lang_type_id_t spl_lang_make_float(spl_lang_type_t *type, usize bits) {
spl_type_id_t ir = spl_type_builder_float(&type->ir, bits);
return spl_lang_node_simple(type, SPL_LANG_TYPE_FLOAT, ir);
}
spl_lang_type_id_t spl_lang_make_array(spl_lang_type_t *type, spl_lang_type_id_t elem_lang,
usize len) {
spl_type_id_t elem_ir = spl_lang_type_get_ir_type(type, elem_lang);
if (!elem_ir)
return 0;
spl_type_id_t ir = spl_type_builder_array(&type->ir, elem_ir, len);
spl_lang_type_node_t node = {0};
node.kind = SPL_LANG_TYPE_ARRAY;
node.ir_type_id = ir;
node.extra.elem = elem_lang;
return spl_lang_node_push(type, &node);
}
spl_lang_type_id_t spl_lang_make_ptr(spl_lang_type_t *type, spl_lang_type_id_t pointee_lang) {
spl_type_id_t pointee_ir = spl_lang_type_get_ir_type(type, pointee_lang);
if (!pointee_ir)
return 0;
spl_type_id_t ir = spl_type_builder_ptr(&type->ir, pointee_ir);
spl_lang_type_node_t node = {0};
node.kind = SPL_LANG_TYPE_PTR;
node.ir_type_id = ir;
node.extra.elem = pointee_lang;
return spl_lang_node_push(type, &node);
}
spl_lang_type_id_t spl_lang_make_slice(spl_lang_type_t *type, spl_lang_type_id_t elem_lang) {
spl_type_id_t elem_ir = spl_lang_type_get_ir_type(type, elem_lang);
if (!elem_ir)
return 0;
spl_lang_type_id_t ptr_lang = spl_lang_make_ptr(type, elem_lang);
spl_lang_type_id_t usize_lang = spl_lang_make_int(type, sizeof(void *) * 8, 0);
spl_type_id_t ptr_ir = spl_lang_type_get_ir_type(type, ptr_lang);
spl_type_id_t usize_ir = spl_lang_type_get_ir_type(type, usize_lang);
spl_type_member_vec_t members;
vec_init(members);
spl_type_member_t m1 = {.name = "ptr", .type_id = ptr_ir};
spl_type_member_t m2 = {.name = "len", .type_id = usize_ir};
vec_push(members, m1);
vec_push(members, m2);
spl_type_layout_t layout = {.mode = SPL_TYPE_LAYOUT_AUTO, .fixed_align_bits = 0};
spl_type_id_t struct_ir = spl_type_builder_agg(&type->ir, SPL_TYPE_STRUCT, &members, 0, layout);
spl_lang_type_node_t node = {0};
node.kind = SPL_LANG_TYPE_SLICE;
node.ir_type_id = struct_ir;
node.extra.elem = elem_lang;
return spl_lang_node_push(type, &node);
}
spl_lang_type_id_t spl_lang_make_tuple(spl_lang_type_t *type, const spl_lang_type_id_t *elems,
usize count) {
spl_type_member_vec_t members;
vec_init(members);
for (usize i = 0; i < count; i++) {
spl_type_id_t elem_ir = spl_lang_type_get_ir_type(type, elems[i]);
if (!elem_ir) {
vec_free(members);
return 0;
}
char name[32];
snprintf(name, sizeof(name), "%zu", i);
spl_type_member_t m = {.name = strdup(name), .type_id = elem_ir};
vec_push(members, m);
}
spl_type_layout_t layout = {.mode = SPL_TYPE_LAYOUT_AUTO, .fixed_align_bits = 0};
spl_type_id_t struct_ir = spl_type_builder_agg(&type->ir, SPL_TYPE_STRUCT, &members, 0, layout);
spl_lang_type_node_t node = {0};
node.kind = SPL_LANG_TYPE_TUPLE;
node.ir_type_id = struct_ir;
vec_init(node.extra.elems);
for (usize i = 0; i < count; i++) {
vec_push(node.extra.elems, elems[i]);
}
return spl_lang_node_push(type, &node);
}
spl_lang_type_id_t spl_lang_make_range(spl_lang_type_t *type, spl_lang_type_id_t elem_lang) {
spl_type_id_t elem_ir = spl_lang_type_get_ir_type(type, elem_lang);
if (!elem_ir)
return 0;
spl_type_member_vec_t members;
vec_init(members);
spl_type_member_t m1 = {.name = "start", .type_id = elem_ir};
spl_type_member_t m2 = {.name = "end", .type_id = elem_ir};
vec_push(members, m1);
vec_push(members, m2);
spl_type_layout_t layout = {.mode = SPL_TYPE_LAYOUT_AUTO, .fixed_align_bits = 0};
spl_type_id_t struct_ir = spl_type_builder_agg(&type->ir, SPL_TYPE_STRUCT, &members, 0, layout);
spl_lang_type_node_t node = {0};
node.kind = SPL_LANG_TYPE_RANGE;
node.ir_type_id = struct_ir;
node.extra.elem = elem_lang;
return spl_lang_node_push(type, &node);
}
spl_lang_type_id_t spl_lang_make_fn(spl_lang_type_t *type, spl_lang_type_id_t ret_lang,
const spl_lang_type_id_t *params_lang, usize nparams) {
spl_type_id_t ret_ir = spl_lang_type_get_ir_type(type, ret_lang);
if (!ret_ir)
return 0;
spl_type_id_vec_t param_irs;
vec_init(param_irs);
for (usize i = 0; i < nparams; i++) {
spl_type_id_t pir = spl_lang_type_get_ir_type(type, params_lang[i]);
if (!pir) {
vec_free(param_irs);
return 0;
}
vec_push(param_irs, pir);
}
spl_type_id_t fn_ir =
spl_type_builder_fn(&type->ir, ret_ir, param_irs.data, vec_size(param_irs));
vec_free(param_irs);
spl_lang_type_node_t node = {0};
node.kind = SPL_LANG_TYPE_FN;
node.ir_type_id = fn_ir;
node.extra.fn.ret = ret_lang;
vec_init(node.extra.fn.params);
for (usize i = 0; i < nparams; i++) {
vec_push(node.extra.fn.params, params_lang[i]);
}
return spl_lang_node_push(type, &node);
}
spl_lang_type_id_t spl_lang_make_aggregate(spl_lang_type_t *type, spl_lang_type_kind_t kind,
const char *name, const spl_lang_type_id_t *members_lang,
usize nmembers) {
(void)name; // TODO
spl_type_node_kind_t tk;
switch (kind) {
case SPL_LANG_TYPE_STRUCT:
tk = SPL_TYPE_STRUCT;
break;
case SPL_LANG_TYPE_UNION:
tk = SPL_TYPE_UNION;
break;
case SPL_LANG_TYPE_ENUM:
tk = SPL_TYPE_ENUM;
break;
case SPL_LANG_TYPE_SHAPE:
tk = SPL_TYPE_SHAPE;
break;
default:
return 0;
}
spl_type_member_vec_t members;
vec_init(members);
for (usize i = 0; i < nmembers; i++) {
spl_type_id_t member_ir = spl_lang_type_get_ir_type(type, members_lang[i]);
if (!member_ir) {
vec_free(members);
return 0;
}
char field_name[32];
snprintf(field_name, sizeof(field_name), "%zu", i);
spl_type_member_t m = {.name = strdup(field_name), .type_id = member_ir};
vec_push(members, m);
}
spl_type_layout_t layout = {.mode = SPL_TYPE_LAYOUT_AUTO, .fixed_align_bits = 0};
spl_type_id_t agg_ir = spl_type_builder_agg(&type->ir, tk, &members, 0, layout);
spl_lang_type_node_t node = {0};
node.kind = kind;
node.ir_type_id = agg_ir;
return spl_lang_node_push(type, &node);
}
spl_lang_type_id_t spl_lang_make_varargs(spl_lang_type_t *type) {
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_UNIT);
spl_lang_type_node_t node = {0};
node.kind = SPL_LANG_TYPE_VARARGS;
node.ir_type_id = ir;
return spl_lang_node_push(type, &node);
}
spl_lang_type_id_t spl_type_lookup_builtin(spl_lang_type_t *type, const char *name) {
if (!name || !*name)
return 0;
if (strcmp(name, "bool") == 0)
return spl_lang_make_bool(type);
if (strcmp(name, "void") == 0)
return spl_lang_make_unit(type);
if (strcmp(name, "isize") == 0)
return spl_lang_make_int(type, sizeof(void *) * 8, 1);
if (strcmp(name, "usize") == 0)
return spl_lang_make_int(type, sizeof(void *) * 8, 0);
if (strcmp(name, "any") == 0)
return spl_lang_make_any(type);
char prefix = name[0];
if (prefix == 'i' || prefix == 'u' || prefix == 'f') {
const char *rest = name + 1;
char *end;
long bits = strtol(rest, &end, 10);
if (end != rest && *end == '\0' && bits > 0) {
if (prefix == 'i')
return spl_lang_make_int(type, (usize)bits, 1);
if (prefix == 'u')
return spl_lang_make_int(type, (usize)bits, 0);
if (prefix == 'f')
return spl_lang_make_float(type, (usize)bits);
}
}
return 0;
}
bool spl_lang_type_is_subtype(const spl_lang_type_t *type, spl_lang_type_id_t sub,
spl_lang_type_id_t super) {
if (sub == 0 || super == 0)
return false;
if (sub == super)
return true;
spl_lang_type_node_t *nsub = spl_lang_type_node(type, sub);
spl_lang_type_node_t *nsuper = spl_lang_type_node(type, super);
if (!nsub || !nsuper)
return false;
if (nsub->kind == SPL_LANG_TYPE_NEVER)
return true;
if (nsuper->kind == SPL_LANG_TYPE_ANY)
return true;
if (nsub->kind == SPL_LANG_TYPE_INT && nsuper->kind == SPL_LANG_TYPE_INT) {
spl_type_node_t *ir_sub = spl_type_node(&type->ir, nsub->ir_type_id);
if (!ir_sub)
return false;
return ir_sub->int_type.bits == 0;
}
if (nsub->kind == SPL_LANG_TYPE_FLOAT && nsuper->kind == SPL_LANG_TYPE_FLOAT) {
spl_type_node_t *ir_sub = spl_type_node(&type->ir, nsub->ir_type_id);
if (!ir_sub)
return false;
return ir_sub->float_type.bits == 0;
}
/* null(*never)可隐式转换为函数指针:函数值按指针布局,可空。 */
if (nsuper->kind == SPL_LANG_TYPE_FN && nsub->kind == SPL_LANG_TYPE_PTR) {
spl_lang_type_node_t *sub_elem = spl_lang_type_node(type, nsub->extra.elem);
if (sub_elem && sub_elem->kind == SPL_LANG_TYPE_NEVER)
return true;
}
if (nsub->kind == SPL_LANG_TYPE_PTR && nsuper->kind == SPL_LANG_TYPE_PTR) {
if (nsub->extra.elem == nsuper->extra.elem)
return true;
spl_lang_type_node_t *sub_elem = spl_lang_type_node(type, nsub->extra.elem);
spl_lang_type_node_t *super_elem = spl_lang_type_node(type, nsuper->extra.elem);
if (sub_elem && sub_elem->kind == SPL_LANG_TYPE_NEVER)
return true;
if (super_elem && super_elem->kind == SPL_LANG_TYPE_ANY)
return true;
return false;
}
return false;
}
spl_lang_type_id_t spl_lang_type_join(const spl_lang_type_t *type, spl_lang_type_id_t a,
spl_lang_type_id_t b) {
if (a == 0 || b == 0)
return 0;
if (a == b)
return a;
spl_lang_type_node_t *na = spl_lang_type_node(type, a);
spl_lang_type_node_t *nb = spl_lang_type_node(type, b);
if (!na || !nb)
return 0;
if (na->kind == SPL_LANG_TYPE_NEVER)
return b;
if (nb->kind == SPL_LANG_TYPE_NEVER)
return a;
if (na->kind == SPL_LANG_TYPE_ANY || nb->kind == SPL_LANG_TYPE_ANY)
return spl_lang_make_any((spl_lang_type_t *)type);
bool a_int = na->kind == SPL_LANG_TYPE_INT;
bool b_int = nb->kind == SPL_LANG_TYPE_INT;
bool a_flt = na->kind == SPL_LANG_TYPE_FLOAT;
bool b_flt = nb->kind == SPL_LANG_TYPE_FLOAT;
if (a_int && b_int) {
spl_type_node_t *ira = spl_type_node(&type->ir, na->ir_type_id);
spl_type_node_t *irb = spl_type_node(&type->ir, nb->ir_type_id);
if (!ira || !irb)
return 0;
bool a_ct = ira->int_type.bits == 0;
bool b_ct = irb->int_type.bits == 0;
if (a_ct && b_ct)
return a;
if (a_ct)
return b;
if (b_ct)
return a;
return 0;
}
if (a_flt && b_flt) {
spl_type_node_t *ira = spl_type_node(&type->ir, na->ir_type_id);
spl_type_node_t *irb = spl_type_node(&type->ir, nb->ir_type_id);
if (!ira || !irb)
return 0;
bool a_ct = ira->float_type.bits == 0;
bool b_ct = irb->float_type.bits == 0;
if (a_ct && b_ct)
return a;
if (a_ct)
return b;
if (b_ct)
return a;
return 0;
}
if (a_int && b_flt) {
spl_type_node_t *ira = spl_type_node(&type->ir, na->ir_type_id);
spl_type_node_t *irb = spl_type_node(&type->ir, nb->ir_type_id);
if (ira && irb && ira->int_type.bits == 0 && irb->float_type.bits == 0)
return spl_lang_make_float((spl_lang_type_t *)type, 0);
return 0;
}
if (a_flt && b_int) {
spl_type_node_t *ira = spl_type_node(&type->ir, na->ir_type_id);
spl_type_node_t *irb = spl_type_node(&type->ir, nb->ir_type_id);
if (ira && irb && ira->float_type.bits == 0 && irb->int_type.bits == 0)
return spl_lang_make_float((spl_lang_type_t *)type, 0);
return 0;
}
/* 函数指针可空:fn 与 null(*never)求交得到 fn。 */
if (na->kind == SPL_LANG_TYPE_FN && nb->kind == SPL_LANG_TYPE_PTR) {
spl_lang_type_node_t *eb = spl_lang_type_node(type, nb->extra.elem);
if (eb && eb->kind == SPL_LANG_TYPE_NEVER)
return a;
}
if (na->kind == SPL_LANG_TYPE_PTR && nb->kind == SPL_LANG_TYPE_FN) {
spl_lang_type_node_t *ea = spl_lang_type_node(type, na->extra.elem);
if (ea && ea->kind == SPL_LANG_TYPE_NEVER)
return b;
}
if (na->kind == SPL_LANG_TYPE_PTR && nb->kind == SPL_LANG_TYPE_PTR) {
if (na->extra.elem == nb->extra.elem)
return a;
spl_lang_type_node_t *ea = spl_lang_type_node(type, na->extra.elem);
spl_lang_type_node_t *eb = spl_lang_type_node(type, nb->extra.elem);
if (ea && ea->kind == SPL_LANG_TYPE_NEVER)
return b;
if (eb && eb->kind == SPL_LANG_TYPE_NEVER)
return a;
if (ea && ea->kind == SPL_LANG_TYPE_ANY)
return a;
if (eb && eb->kind == SPL_LANG_TYPE_ANY)
return b;
return 0;
}
return 0;
}
bool spl_lang_mpa_int_fits(const spl_lang_type_t *type, spl_lang_type_id_t target,
spl_mpa_int_t v) {
spl_lang_type_node_t *nt = spl_lang_type_node(type, target);
if (!nt || nt->kind != SPL_LANG_TYPE_INT)
return false;
spl_type_node_t *ir = spl_type_node(&type->ir, nt->ir_type_id);
if (!ir)
return false;
uint32_t bits = (uint32_t)ir->int_type.bits;
if (bits == 0)
return true;
bool target_signed = ir->int_type.is_signed != 0;
uint64_t uval = v.bits;
if (target_signed) {
if (bits >= 64)
return true;
uint64_t hi = ((uint64_t)1 << (bits - 1)) - 1;
return uval <= hi;
}
if (bits >= 64)
return true;
uint64_t hi = ((uint64_t)1 << bits) - 1;
return uval <= hi;
}
spl_def_id_t spl_def_alloc(spl_lang_type_t *type, spl_def_node_kind_t kind, const char *name,
spl_dbg_node_t dbg, struct spl_ast *owner_ast, usize ast_node_ref) {
spl_def_node_t n = {0};
n.kind = kind;
n.name = name;
n.dbg_node = dbg;
n.lang_id = 0;
n.owner_ast = owner_ast;
n.ast_node_ref = ast_node_ref;
n.state = SPL_DECL_UNRESOLVED;
n.owner_def = 0;
vec_push(type->def_table, n);
return vec_size(type->def_table) - 1;
}
void spl_def_resolve(spl_lang_type_t *type, spl_def_id_t id, spl_lang_type_id_t lang_id) {
spl_def_node_t *d = spl_def_node(type, id);
if (d) {
d->lang_id = lang_id;
d->state = SPL_DECL_RESOLVED;
}
}
spl_lang_type_id_t spl_def_get_lang_id(spl_lang_type_t *type, spl_def_id_t def_id) {
spl_def_node_t *d = spl_def_node(type, def_id);
return d ? d->lang_id : 0;
}
spl_type_id_t spl_def_get_type_id(spl_lang_type_t *type, spl_def_id_t def_id) {
spl_lang_type_id_t lang = spl_def_get_lang_id(type, def_id);
return lang ? spl_lang_type_get_ir_type(type, lang) : 0;
}
static void spl_lang_type_dump_rec(spl_lang_type_t *type, spl_lang_type_id_t id,
spl_lang_type_id_vec_t *stack) {
for (usize i = 0; i < vec_size(*stack); i++) {
if (vec_at(*stack, i) == id) {
printf("<rec#%zu>", id);
return;
}
}
if (vec_size(*stack) > 32) {
printf("...");
return;
}
vec_push(*stack, id);
spl_lang_type_node_t *n = spl_lang_type_node(type, id);
if (!n) {
printf("none");
(void)vec_pop(*stack);
return;
}
switch (n->kind) {
case SPL_LANG_TYPE_ERROR:
printf("error");
break;
case SPL_LANG_TYPE_INFER:
printf("infer");
break;
case SPL_LANG_TYPE_NEVER:
printf("never");
break;
case SPL_LANG_TYPE_ANY:
printf("any");
break;
case SPL_LANG_TYPE_UNIT:
printf("unit");
break;
case SPL_LANG_TYPE_BOOL:
printf("bool");
break;
case SPL_LANG_TYPE_VARARGS:
printf("...");
break;
case SPL_LANG_TYPE_INT: {
spl_type_node_t *tn = spl_type_node(&type->ir, n->ir_type_id);
if (tn && tn->int_type.bits == 0)
printf("comptime_int");
else
spl_type_pure_dump(&type->ir, n->ir_type_id);
break;
}
case SPL_LANG_TYPE_FLOAT: {
spl_type_node_t *tn = spl_type_node(&type->ir, n->ir_type_id);
if (tn && tn->float_type.bits == 0)
printf("comptime_float");
else
spl_type_pure_dump(&type->ir, n->ir_type_id);
break;
}
case SPL_LANG_TYPE_PTR:
printf("*");
spl_lang_type_dump_rec(type, n->extra.elem, stack);
break;
case SPL_LANG_TYPE_ARRAY: {
spl_type_node_t *tn = spl_type_node(&type->ir, n->ir_type_id);
printf("[%zu]", tn->array_type.len);
spl_type_pure_dump(&type->ir, n->ir_type_id);
} break;
case SPL_LANG_TYPE_SLICE:
printf("[]");
spl_lang_type_dump_rec(type, n->extra.elem, stack);
break;
case SPL_LANG_TYPE_TUPLE:
printf("(");
for (usize i = 0; i < vec_size(n->extra.elems); i++) {
if (i)
printf(",");
spl_lang_type_dump_rec(type, vec_at(n->extra.elems, i), stack);
}
printf(")");
break;
case SPL_LANG_TYPE_RANGE:
spl_lang_type_dump_rec(type, n->extra.elem, stack);
printf("..");
spl_lang_type_dump_rec(type, n->extra.elem, stack);
break;
case SPL_LANG_TYPE_FN:
printf("fn(");
for (usize i = 0; i < vec_size(n->extra.fn.params); i++) {
if (i)
printf(",");
spl_lang_type_dump_rec(type, vec_at(n->extra.fn.params, i), stack);
}
printf(")");
spl_lang_type_dump_rec(type, n->extra.fn.ret, stack);
break;
case SPL_LANG_TYPE_STRUCT:
case SPL_LANG_TYPE_UNION:
case SPL_LANG_TYPE_ENUM:
case SPL_LANG_TYPE_SHAPE:
spl_type_pure_dump(&type->ir, n->ir_type_id);
break;
}
(void)vec_pop(*stack);
}
void spl_lang_type_dump(spl_lang_type_t *type, spl_lang_type_id_t id) {
spl_lang_type_id_vec_t stack;
vec_init(stack);
spl_lang_type_dump_rec(type, id, &stack);
vec_free(stack);
}
void spl_type_def_dump(spl_lang_type_t *type, spl_def_id_t id) {
spl_def_node_t *node = spl_def_node(type, id);
if (!node) {
printf("none");
return;
}
const char *def_kind_name = "unknown";
switch (node->kind) {
case SPL_DEF_ERROR:
def_kind_name = "error";
break;
case SPL_DEF_SCALAR:
def_kind_name = "scalar";
break;
case SPL_DEF_MEMBER:
def_kind_name = "member";
break;
case SPL_DEF_VAR:
def_kind_name = "var";
break;
case SPL_DEF_FN:
def_kind_name = "fn";
break;
case SPL_DEF_AGG:
def_kind_name = "agg";
break;
case SPL_DEF_BUILTIN:
def_kind_name = "builtin";
break;
case SPL_DEF_DISTINCT:
def_kind_name = "distinct";
break;
case SPL_DEF_ALIAS:
def_kind_name = "alias";
break;
}
printf("kind=%s lang_id=%zu name=%s", def_kind_name, node->lang_id,
node->name ? node->name : "?");
}
+153
View File
@@ -0,0 +1,153 @@
#ifndef __SPL_LANG_TYPE_H__
#define __SPL_LANG_TYPE_H__
#include "../stage0/include/utils.h"
#include "spl_dbg.h"
#include "spl_mpa.h"
#include "spl_type.h"
typedef usize spl_lang_type_id_t;
typedef VEC(spl_lang_type_id_t) spl_lang_type_id_vec_t;
typedef usize spl_def_id_t;
typedef VEC(spl_def_id_t) spl_def_id_vec_t;
typedef enum {
SPL_LANG_TYPE_ERROR,
SPL_LANG_TYPE_INFER,
SPL_LANG_TYPE_NEVER,
SPL_LANG_TYPE_ANY,
SPL_LANG_TYPE_UNIT,
SPL_LANG_TYPE_BOOL,
SPL_LANG_TYPE_INT,
SPL_LANG_TYPE_FLOAT,
SPL_LANG_TYPE_PTR,
SPL_LANG_TYPE_ARRAY,
SPL_LANG_TYPE_SLICE,
SPL_LANG_TYPE_TUPLE,
SPL_LANG_TYPE_RANGE,
SPL_LANG_TYPE_FN,
SPL_LANG_TYPE_STRUCT,
SPL_LANG_TYPE_UNION,
SPL_LANG_TYPE_ENUM,
SPL_LANG_TYPE_SHAPE,
SPL_LANG_TYPE_VARARGS,
} spl_lang_type_kind_t;
typedef struct {
const char *name;
spl_def_id_t def_id;
} spl_lang_method_t;
typedef VEC(spl_lang_method_t) spl_lang_method_vec_t;
typedef struct {
spl_lang_type_kind_t kind;
spl_type_id_t ir_type_id;
spl_lang_method_vec_t methods;
union {
spl_lang_type_id_t elem;
spl_lang_type_id_vec_t elems;
struct {
spl_lang_type_id_vec_t params;
spl_lang_type_id_t ret;
} fn;
} extra;
usize hash_next; /* 同 hash 桶内下一个类型 id;0 表示链尾。不参与相等比较。 */
} spl_lang_type_node_t;
typedef VEC(spl_lang_type_node_t) spl_lang_type_vec_t;
typedef enum {
SPL_DEF_ERROR,
SPL_DEF_SCALAR,
SPL_DEF_MEMBER,
SPL_DEF_VAR,
SPL_DEF_FN,
SPL_DEF_AGG,
SPL_DEF_DISTINCT,
SPL_DEF_ALIAS,
SPL_DEF_BUILTIN,
} spl_def_node_kind_t;
typedef enum {
SPL_DECL_UNRESOLVED,
SPL_DECL_RESOLVING,
SPL_DECL_RESOLVED,
SPL_DECL_ERROR,
} spl_decl_state_t;
struct spl_ast;
typedef struct {
spl_def_node_kind_t kind;
const char *name;
spl_dbg_node_t dbg_node;
spl_lang_type_id_t lang_id;
struct spl_ast *owner_ast;
usize ast_node_ref;
spl_decl_state_t state;
spl_def_id_t owner_def;
} spl_def_node_t;
typedef VEC(spl_def_node_t) spl_def_node_vec_t;
typedef struct {
spl_type_t ir;
spl_lang_type_vec_t lang_table;
spl_fold_t lang_fold;
spl_def_node_vec_t def_table;
} spl_lang_type_t;
void spl_lang_type_init(spl_lang_type_t *type);
void spl_lang_type_drop(spl_lang_type_t *type);
spl_lang_type_node_t *spl_lang_type_node(const spl_lang_type_t *type, spl_lang_type_id_t id);
spl_lang_type_id_t spl_lang_node_push(spl_lang_type_t *type, spl_lang_type_node_t *node);
spl_type_id_t spl_lang_type_get_ir_type(spl_lang_type_t *type, spl_lang_type_id_t lang_id);
/* 前向引用支持:reserve 一个 type_table 空槽位(kind=ERROR)作为身份锚点,
让 *Node / []Node 这类成员能拿到稳定 pointee;成员解析完后再调 define
把真实内容填进去。占位槽位**不写 type_fold**,不参与 hash-consing——
后续相同形状的类型走 spl_type_node_push 会生成独立 id,语义等价。 */
spl_type_id_t spl_lang_reserve_ir(spl_lang_type_t *type);
void spl_lang_define_ir(spl_lang_type_t *type, spl_type_id_t id, spl_type_node_t *node);
spl_lang_type_id_t spl_lang_make_error(spl_lang_type_t *type);
spl_lang_type_id_t spl_lang_make_infer(spl_lang_type_t *type);
spl_lang_type_id_t spl_lang_make_never(spl_lang_type_t *type);
spl_lang_type_id_t spl_lang_make_any(spl_lang_type_t *type);
spl_lang_type_id_t spl_lang_make_unit(spl_lang_type_t *type);
spl_lang_type_id_t spl_lang_make_bool(spl_lang_type_t *type);
spl_lang_type_id_t spl_lang_make_int(spl_lang_type_t *type, usize bits, int is_signed);
spl_lang_type_id_t spl_lang_make_float(spl_lang_type_t *type, usize bits);
spl_lang_type_id_t spl_lang_make_ptr(spl_lang_type_t *type, spl_lang_type_id_t pointee);
spl_lang_type_id_t spl_lang_make_array(spl_lang_type_t *type, spl_lang_type_id_t elem, usize len);
spl_lang_type_id_t spl_lang_make_slice(spl_lang_type_t *type, spl_lang_type_id_t elem);
spl_lang_type_id_t spl_lang_make_tuple(spl_lang_type_t *type, const spl_lang_type_id_t *elems,
usize count);
spl_lang_type_id_t spl_lang_make_range(spl_lang_type_t *type, spl_lang_type_id_t elem);
spl_lang_type_id_t spl_lang_make_fn(spl_lang_type_t *type, spl_lang_type_id_t ret,
const spl_lang_type_id_t *params, usize nparams);
spl_lang_type_id_t spl_lang_make_aggregate(spl_lang_type_t *type, spl_lang_type_kind_t kind,
const char *name, const spl_lang_type_id_t *members,
usize nmembers);
spl_lang_type_id_t spl_lang_make_varargs(spl_lang_type_t *type);
spl_lang_type_id_t spl_type_lookup_builtin(spl_lang_type_t *type, const char *name);
bool spl_lang_type_is_subtype(const spl_lang_type_t *type, spl_lang_type_id_t sub,
spl_lang_type_id_t super);
spl_lang_type_id_t spl_lang_type_join(const spl_lang_type_t *type, spl_lang_type_id_t a,
spl_lang_type_id_t b);
bool spl_lang_mpa_int_fits(const spl_lang_type_t *type, spl_lang_type_id_t target, spl_mpa_int_t v);
spl_def_id_t spl_def_alloc(spl_lang_type_t *type, spl_def_node_kind_t kind, const char *name,
spl_dbg_node_t dbg, struct spl_ast *owner_ast, usize ast_node_ref);
void spl_def_resolve(spl_lang_type_t *type, spl_def_id_t id, spl_lang_type_id_t lang_id);
spl_def_node_t *spl_def_node(const spl_lang_type_t *type, spl_def_id_t id);
spl_lang_type_id_t spl_def_get_lang_id(spl_lang_type_t *type, spl_def_id_t def_id);
spl_type_id_t spl_def_get_type_id(spl_lang_type_t *type, spl_def_id_t def_id);
void spl_lang_type_dump(spl_lang_type_t *type, spl_lang_type_id_t id);
void spl_type_def_dump(spl_lang_type_t *type, spl_def_id_t id);
#endif /* __SPL_LANG_TYPE_H__ */
+72 -14
View File
@@ -50,8 +50,9 @@ void spl_layout_init(spl_layout_t *layout, spl_layout_target_t *target) {
spl_layout_add_scalar(layout, 4, 4); /* i32 / u32 / f32 */
spl_layout_add_scalar(layout, 8, 8); /* i64 / u64 / f64 */
usize ps = sizeof(void *);
spl_layout_add_ptr(layout, 0, ps, ps);
usize ps = target && target->ptr_size ? target->ptr_size : sizeof(void *);
usize pa = target && target->ptr_align ? target->ptr_align : sizeof(void *);
spl_layout_add_ptr(layout, 0, ps, pa);
}
void spl_layout_drop(spl_layout_t *layout) {
@@ -130,6 +131,56 @@ static usize round_up_bits(usize x, usize align_bits) {
static bool layout_agg(spl_layout_t *layout, spl_type_node_t *tn, spl_layout_info_t *out) {
usize n = vec_size(tn->agg_type.variants);
if (tn->kind == SPL_TYPE_ENUM) {
spl_type_id_t tag_tid = tn->agg_type.tag_type;
if (!tag_tid)
return false;
spl_layout_info_t *tag_info = infos_at(layout, tag_tid);
if (!tag_info || !tag_info->resolved)
return false;
usize tag_align_bits = tag_info->align_bits ? tag_info->align_bits : 8;
usize payload_align_bits = 8;
usize payload_size_bits = 0;
for (usize i = 0; i < n; ++i) {
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
if (m->type_id == 0) /* 无载荷变体 */
continue;
spl_layout_info_t *fi = infos_at(layout, m->type_id);
if (!fi || !fi->resolved)
return false;
usize fa = fi->align_bits ? fi->align_bits : 8;
if (fa > payload_align_bits)
payload_align_bits = fa;
if (fi->size_bits > payload_size_bits)
payload_size_bits = fi->size_bits;
}
usize payload_off = round_up_bits(tag_info->size_bits, payload_align_bits);
usize final_align_bits =
tag_align_bits > payload_align_bits ? tag_align_bits : payload_align_bits;
out->size_bits = round_up_bits(payload_off + payload_size_bits, final_align_bits);
out->align_bits = final_align_bits;
/* 字段索引约定(与 ir2vm 一致):0 = tag,1..n = 各变体载荷(同一偏移)。 */
vec_push(out->field_bit_offsets, 0);
vec_push(out->field_bit_sizes, tag_info->size_bits);
for (usize i = 0; i < n; ++i) {
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
usize fsize = 0;
if (m->type_id) {
spl_layout_info_t *fi = infos_at(layout, m->type_id);
if (fi)
fsize = fi->size_bits;
}
vec_push(out->field_bit_offsets, payload_off);
vec_push(out->field_bit_sizes, fsize);
}
return true;
}
if (n == 0) {
out->size_bits = 0;
out->align_bits = 8;
@@ -142,28 +193,32 @@ static bool layout_agg(spl_layout_t *layout, spl_type_node_t *tn, spl_layout_inf
for (usize i = 0; i < n; ++i) {
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
spl_layout_info_t *fi = infos_at(layout, m->type_id);
if (!fi || !fi->resolved)
return false;
usize field_align_bits = fi->align_bits ? fi->align_bits : 8;
usize fsize_bits = 0;
usize fsize_align = 8;
if (m->type_id) {
spl_layout_info_t *fi = infos_at(layout, m->type_id);
if (!fi || !fi->resolved)
return false;
fsize_bits = fi->size_bits;
fsize_align = fi->align_bits ? fi->align_bits : 8;
}
usize field_align_bits = fsize_align;
if (is_union) {
vec_push(out->field_bit_offsets, 0);
vec_push(out->field_bit_sizes, fi->size_bits);
if (fi->size_bits > out->size_bits)
out->size_bits = fi->size_bits;
vec_push(out->field_bit_sizes, fsize_bits);
if (fsize_bits > out->size_bits)
out->size_bits = fsize_bits;
} else {
offset_bits = round_up_bits(offset_bits, field_align_bits);
vec_push(out->field_bit_offsets, offset_bits);
vec_push(out->field_bit_sizes, fi->size_bits);
offset_bits += fi->size_bits;
vec_push(out->field_bit_sizes, fsize_bits);
offset_bits += fsize_bits;
}
if (field_align_bits > max_align_bits)
max_align_bits = field_align_bits;
}
/* 聚合对齐策略 */
usize final_align_bits = max_align_bits;
if (layout->aggregate.align_rule == ALIGN_FIXED) {
final_align_bits = layout->aggregate.fixed_align * 8;
@@ -234,9 +289,12 @@ static void resolve_type(spl_layout_t *layout, spl_type_t *type, spl_type_id_t i
case SPL_TYPE_UNION:
case SPL_TYPE_ENUM:
case SPL_TYPE_SHAPE: {
if (tn->kind == SPL_TYPE_ENUM && tn->agg_type.tag_type)
resolve_type(layout, type, tn->agg_type.tag_type);
for (usize i = 0; i < vec_size(tn->agg_type.variants); ++i) {
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
resolve_type(layout, type, m->type_id);
if (m->type_id)
resolve_type(layout, type, m->type_id);
}
layout_agg(layout, tn, info);
return;
+2 -1
View File
@@ -32,7 +32,8 @@ typedef struct {
typedef struct {
enum { SPL_BIG_ENDIAN, SPL_LITTLE_ENDIAN } endian; // TODO 更多位存储方式
usize ptr_size;
usize ptr_align;
} spl_layout_target_t;
typedef struct {
+16 -16
View File
@@ -333,19 +333,20 @@ spl_tok_vec_t spl_lex(const char *source, const char *fname) {
continue;
}
emit_int: {
spl_tok_t tok;
memset(&tok, 0, sizeof(tok));
tok.type = TOK_INT_LITERAL;
tok.lexeme = num_start;
tok.len = (usize)((source + offset) - num_start);
tok.fname = fname;
tok.offset = num_start - source;
tok.line = line;
tok.col = col - tok.len;
vec_push(toks, tok);
continue;
}
emit_int:
{
spl_tok_t tok;
memset(&tok, 0, sizeof(tok));
tok.type = TOK_INT_LITERAL;
tok.lexeme = num_start;
tok.len = (usize)((source + offset) - num_start);
tok.fname = fname;
tok.offset = num_start - source;
tok.line = line;
tok.col = col - tok.len;
vec_push(toks, tok);
continue;
}
}
/* Multi-character operators (longest match) */
@@ -410,8 +411,8 @@ spl_tok_vec_t spl_lex(const char *source, const char *fname) {
}
/* Three-char operators first */
TRY_OP3('<', '-', '>', TOK_ARROW_BOTH, TOK_RIGHT_ARROW, TOK_LT)
TRY_OP3('<', '=', '>', TOK_FAT_ARROW_BOTH, TOK_RIGHT_FAT_ARROW, TOK_LT)
TRY_OP3('<', '-', '>', TOK_ARROW_BOTH, TOK_LEFT_ARROW, TOK_LT)
TRY_OP3('<', '=', '>', TOK_FAT_ARROW_BOTH, TOK_LE, TOK_LT)
TRY_OP3('<', '<', '=', TOK_ASSIGN_L_SH, TOK_L_SH, TOK_LT)
TRY_OP3('>', '>', '=', TOK_ASSIGN_R_SH, TOK_R_SH, TOK_GT)
TRY_OP3('.', '.', '.', TOK_ELLIPSIS, TOK_RANGE, TOK_DOT)
@@ -432,7 +433,6 @@ spl_tok_vec_t spl_lex(const char *source, const char *fname) {
TRY_OP2('&', '=', TOK_ASSIGN_AND, TOK_AND)
TRY_OP2('|', '=', TOK_ASSIGN_OR, TOK_OR)
TRY_OP2('^', '=', TOK_ASSIGN_XOR, TOK_XOR)
TRY_OP2('<', '-', TOK_LEFT_ARROW, TOK_LT)
TRY_OP2('-', '>', TOK_RIGHT_ARROW, TOK_SUB)
TRY_OP2(':', '=', TOK_COLON_ASSIGN, TOK_COLON)
+14
View File
@@ -0,0 +1,14 @@
#ifndef __SPL_MPA_H__
#define __SPL_MPA_H__
#include <stdint.h>
typedef struct {
uint64_t bits;
} spl_mpa_int_t;
typedef struct {
double bits;
} spl_mpa_float_t;
#endif /* __SPL_MPA_H__ */
+2305
View File
File diff suppressed because it is too large. Load diff
+21
View File
@@ -0,0 +1,21 @@
#ifndef __SPL_PARSER_H__
#define __SPL_PARSER_H__
#include "spl_ast.h"
#include "spl_diag.h"
typedef struct spl_parser {
spl_ast_t *ast;
spl_diag_t *diag;
usize pos;
int failed;
int stop_agg;
usize error_count;
} spl_parser_t;
void spl_parser_init(spl_parser_t *p);
void spl_parser_drop(spl_parser_t *p);
void spl_ast_parse(spl_parser_t *p, spl_ast_t *ast);
#endif /* __SPL_PARSER_H__ */
+46
View File
@@ -0,0 +1,46 @@
#include "spl_scope.h"
void spl_scope_init(spl_scope_t *scope) {
vec_init(scope->scopes);
vec_push(scope->scopes, (spl_scope_node_t){0});
scope->current_scope = 0;
scope->root_scope = 0;
}
void spl_scope_drop(spl_scope_t *scope) {
for (usize i = 0; i < vec_size(scope->scopes); ++i) {
map_free(vec_at(scope->scopes, i).symbols);
}
vec_free(scope->scopes);
scope->current_scope = 0;
scope->root_scope = 0;
}
spl_scope_id_t spl_scope_alloc(spl_scope_t *scope) {
spl_scope_node_t n = {0};
n.parent = scope->current_scope;
map_init(n.symbols, MAP_HASH_STR, MAP_CMP_STR);
vec_push(scope->scopes, n);
return vec_size(scope->scopes) - 1;
}
bool spl_scope_insert(spl_scope_t *scope, spl_scope_id_t id, spl_symbol_t symbol) {
Assert(id != 0 && id < vec_size(scope->scopes));
spl_scope_node_t *node = &vec_at(scope->scopes, id);
spl_symbol_t old = {0};
if (map_get(node->symbols, symbol.name, &old))
return false;
map_put(node->symbols, symbol.name, symbol);
return true;
}
bool spl_scope_find(spl_scope_t *scope, const char *name, spl_symbol_t *out) {
if (!scope || !name || !out)
return false;
for (spl_scope_id_t id = scope->current_scope; id != 0; id = vec_at(scope->scopes, id).parent) {
spl_scope_node_t *s = &vec_at(scope->scopes, id);
if (map_get(s->symbols, name, out))
return true;
}
return false;
}
+42
View File
@@ -0,0 +1,42 @@
#ifndef __SPL_SCOPE_H__
#define __SPL_SCOPE_H__
#include "spl_lang_type.h"
typedef usize spl_scope_id_t;
typedef enum {
SPL_SYMBOL_KIND_ERROR,
SPL_SYMBOL_KIND_VAR,
SPL_SYMBOL_KIND_MEMBER,
SPL_SYMBOL_KIND_FN,
SPL_SYMBOL_KIND_TYPE,
} spl_symbol_kind_t;
typedef struct {
const char *name;
spl_symbol_kind_t kind;
spl_def_id_t node;
} spl_symbol_t;
typedef VEC(const char *) spl_symbol_path_t;
typedef struct {
spl_scope_id_t parent;
MAP(const char *, spl_symbol_t) symbols;
} spl_scope_node_t;
typedef VEC(spl_scope_node_t) spl_scope_node_vec_t;
typedef struct {
spl_scope_node_vec_t scopes;
spl_scope_id_t root_scope;
spl_scope_id_t current_scope;
} spl_scope_t;
void spl_scope_init(spl_scope_t *scope);
void spl_scope_drop(spl_scope_t *scope);
spl_scope_id_t spl_scope_alloc(spl_scope_t *scope);
bool spl_scope_insert(spl_scope_t *scope, spl_scope_id_t id, spl_symbol_t symbol);
bool spl_scope_find(spl_scope_t *scop, const char *symbol_name, spl_symbol_t *out);
#endif /* __SPL_SCOPE_H__ */
+1989 -621
View File
File diff suppressed because it is too large. Load diff
+24 -40
View File
@@ -2,56 +2,40 @@
#define __SPL_SEMA_H__
#include "spl_ast.h"
#include "spl_attr.h"
#include "spl_diag.h"
#include "spl_lang_type.h"
#include "spl_layout.h"
#include "spl_mpa.h"
#include "spl_scope.h"
#include "spl_type.h"
#include "spl_attr.h"
typedef enum {
SPL_SYMBOL_KIND_ERROR,
SPL_SYMBOL_KIND_VAR,
SPL_SYMBOL_KIND_MEMBER,
SPL_SYMBOL_KIND_FN,
SPL_SYMBOL_KIND_TYPE,
} spl_symbol_kind_t;
typedef struct {
const char *name;
spl_symbol_kind_t kind;
spl_def_id_t node;
} spl_symbol_t;
typedef usize spl_scope_id_t; /* 0 is error */
typedef struct {
spl_scope_id_t parent;
MAP(const char *, spl_symbol_t) symbols;
} spl_scope_node_t;
typedef VEC(spl_scope_node_t) spl_scope_node_vec_t;
typedef struct {
spl_scope_node_vec_t scopes;
spl_scope_id_t root_scope;
spl_scope_id_t current_scope;
} spl_scope_t;
void spl_scope_init(spl_scope_t *scope);
void spl_scope_drop(spl_scope_t *scope);
spl_scope_id_t spl_scope_alloc(spl_scope_t *scope);
bool spl_scope_insert(spl_scope_t *scope, spl_scope_id_t id, spl_symbol_t symbol);
typedef VEC(const char *) spl_symbol_path_t;
bool spl_scope_find(spl_scope_t *scop, const char *symbol_name, spl_symbol_t *out);
typedef struct spl_module_loader spl_module_loader_t;
struct spl_module_loader {
/* 从 from_fname 所在目录解析 path;返回该文件的 ast(已 init + parse)。
失败(文件不存在 / parse 错误 / 环)返回 NULL。
loader 拥有 ast 的生命周期;sema 不负责释放。 */
spl_ast_t *(*load)(void *user, const char *from_fname, const char *path, spl_diag_t *diag);
void *user;
};
typedef struct {
spl_ast_t *ast;
spl_type_t *type;
spl_lang_type_t *type;
spl_layout_t *layout; /* 由 splc0 拥有并解析;sema 仅借用 */
spl_scope_t *scope;
spl_symbol_t root;
int error_count;
spl_scope_id_t store_scope;
spl_attr_t *attr;
spl_type_id_t current_fn_ret_tid; // TODO
spl_lang_type_id_t current_aggregate_lang;
spl_lang_type_id_t current_fn_ret_tid;
spl_lang_type_id_t current_loop_break_tid;
spl_symbol_path_t label_stack;
const spl_module_loader_t *loader;
spl_diag_t *diag;
} spl_sema_t;
void spl_sema_init(spl_sema_t *sema, spl_ast_t *ast, spl_type_t *type, spl_scope_t *scope,
spl_attr_t *attr);
void spl_sema_init(spl_sema_t *sema, spl_ast_t *ast, spl_lang_type_t *type,
spl_layout_t *layout, spl_scope_t *scope, spl_attr_t *attr);
void spl_sema_drop(spl_sema_t *sema);
void spl_sema_run_module(spl_sema_t *sema, spl_ast_node_ref_t module_ref);
+15
View File
@@ -0,0 +1,15 @@
#include "spl_tok.h"
const char *spl_tok_type_name(spl_tok_type_t t) {
static const char *const names[] = {
#define X(name, enum_name, dummy) #enum_name,
KEYWORD_TABLE
#undef X
#define X(name, enum_name, dummy) #enum_name,
TOKEN_TABLE
#undef X
};
if ((usize)t < sizeof(names) / sizeof(names[0]))
return names[t];
return "?";
}
+2
View File
@@ -127,4 +127,6 @@ typedef struct {
typedef VEC(spl_tok_t) spl_tok_vec_t;
const char *spl_tok_type_name(spl_tok_type_t t);
#endif /* __SPL_TOK_H__ */
+146 -793
View File
File diff suppressed because it is too large. Load diff
+34 -141
View File
@@ -2,14 +2,30 @@
#define __SPL_TYPE_H__
#include "../stage0/include/utils.h"
#include "spl_dbg.h"
#include "spl_fold.h"
typedef usize spl_type_id_t; /* 0 is error */
/* 类型 intern 用的哈希混合(FNV-1a 风格,顺序敏感)。
hash 只用于选择桶;相等判定始终由结构比较完成。 */
static inline usize spl_hash_mix(usize h, usize x) {
h ^= x;
h *= (usize)1099511628211ULL;
return h;
}
static inline usize spl_hash_mix_str(usize h, const char *s) {
if (!s)
return spl_hash_mix(h, 0);
while (*s) {
h = spl_hash_mix(h, (usize)(unsigned char)*s);
s++;
}
return spl_hash_mix(h, 0); /* 终止符参与混合,避免名称拼接歧义 */
}
static inline usize spl_hash_seed(void) { return (usize)14695981039346656037ULL; }
typedef usize spl_type_id_t;
typedef VEC(spl_type_id_t) spl_type_id_vec_t;
typedef usize spl_lang_type_id_t; /* 0 is error */
typedef VEC(spl_lang_type_id_t) spl_lang_type_id_vec_t;
typedef usize spl_def_id_t; /* 0 is error */
typedef VEC(spl_def_id_t) spl_def_id_vec_t;
typedef enum {
SPL_TYPE_ERROR,
@@ -45,13 +61,13 @@ typedef struct {
typedef struct {
const char *name;
spl_type_id_t type_id;
usize ast_node_ref; // 回 AST member_decl 节点引用 dbg/IDE 定位用 不参与 hash
usize ast_node_ref;
} spl_type_member_t;
typedef VEC(spl_type_member_t) spl_type_member_vec_t;
typedef struct {
spl_type_node_kind_t kind;
spl_type_layout_t layout; // attribute
spl_type_layout_t layout;
union {
struct {
usize bits;
@@ -70,137 +86,29 @@ typedef struct {
struct {
spl_type_member_vec_t variants;
spl_type_id_t tag_type;
} agg_type; // adt record
} agg_type;
struct {
spl_type_id_vec_t params;
spl_type_id_t ret;
} fn_type;
struct {
// TODO
} shape_type;
};
usize hash_next; /* 同 hash 桶内下一个类型 id;0 表示链尾。不参与相等比较。 */
} spl_type_node_t;
typedef VEC(spl_type_node_t) spl_type_node_vec_t;
typedef enum {
SPL_LANG_TYPE_ERROR,
SPL_LANG_TYPE_INFER,
SPL_LANG_TYPE_NEVER,
SPL_LANG_TYPE_ANY,
SPL_LANG_TYPE_UNIT,
SPL_LANG_TYPE_BOOL,
SPL_LANG_TYPE_INT,
SPL_LANG_TYPE_FLOAT,
SPL_LANG_TYPE_PTR,
SPL_LANG_TYPE_ARRAY,
SPL_LANG_TYPE_SLICE,
SPL_LANG_TYPE_TUPLE,
SPL_LANG_TYPE_RANGE,
SPL_LANG_TYPE_FN,
SPL_LANG_TYPE_STRUCT,
SPL_LANG_TYPE_UNION,
SPL_LANG_TYPE_ENUM,
SPL_LANG_TYPE_SHAPE,
SPL_LANG_TYPE_VARARGS,
} spl_lang_type_kind_t;
typedef struct {
spl_lang_type_kind_t kind;
spl_type_id_t ir_type_id;
union {
spl_lang_type_id_t elem;
spl_lang_type_id_vec_t elems;
struct {
spl_lang_type_id_vec_t params;
spl_lang_type_id_t ret;
} fn;
} extra;
} spl_lang_type_node_t;
typedef VEC(spl_lang_type_node_t) spl_lang_type_vec_t;
typedef MAP(usize, spl_lang_type_id_t) spl_lang_type_map_t;
typedef enum {
SPL_DEF_ERROR,
SPL_DEF_SCALAR,
SPL_DEF_MEMBER,
SPL_DEF_VAR,
SPL_DEF_FN,
SPL_DEF_AGG,
SPL_DEF_DISTINCT,
SPL_DEF_ALIAS,
SPL_DEF_BUILTIN,
} spl_def_node_kind_t;
typedef struct {
spl_def_node_kind_t kind;
enum {
SPL_BUILTIN_NONE,
SPL_BUILTIN_SLICE,
SPL_BUILTIN_RANGE,
} builtin_kind;
const char *name;
spl_dbg_node_t dbg_node;
spl_lang_type_id_t lang_id;
usize ast_node_ref;
} spl_def_node_t;
typedef VEC(spl_def_node_t) spl_def_node_vec_t;
typedef MAP(usize, spl_type_id_t) spl_type_node_map_t;
typedef struct {
spl_type_node_vec_t type_table;
spl_type_node_map_t type_map;
spl_lang_type_vec_t lang_table;
spl_lang_type_map_t lang_map;
spl_def_node_vec_t def_table;
spl_fold_t type_fold;
} spl_type_t;
void spl_type_init(spl_type_t *type);
void spl_type_drop(spl_type_t *type);
void spl_type_def_dump(spl_type_t *type, spl_def_id_t id);
void spl_type_pure_dump(spl_type_t *type, spl_type_id_t id);
void spl_lang_type_dump(spl_type_t *type, spl_lang_type_id_t id);
spl_type_node_t *spl_type_node(const spl_type_t *type, spl_type_id_t id);
spl_lang_type_node_t *spl_lang_type_node(const spl_type_t *type, spl_lang_type_id_t id);
spl_def_node_t *spl_def_node(const spl_type_t *type, spl_def_id_t id);
bool spl_lang_type_is_subtype(const spl_type_t *type, spl_lang_type_id_t sub,
spl_lang_type_id_t super);
bool spl_type_is_subtype(const spl_type_t *type, spl_type_id_t sub, spl_type_id_t super);
spl_lang_type_id_t spl_lang_node_push(spl_type_t *type, spl_lang_type_node_t *node);
/* 计算 from 是否可以隐式转换到 to。
* - to 是 INFER 时视为"接受任意 from",返回 from。
* - 成功返回"转换后的类型"(正常是 to,INFER 时是 from)。
* - 失败返回 0。
* 纯函数:不修改任何 AST,不产生副作用。 */
spl_lang_type_id_t spl_lang_type_coerce_to(spl_type_t *type, spl_lang_type_id_t to,
spl_lang_type_id_t from);
/* 合并两个类型(用于 if 分支、算术运算的对称平衡)。
* - NEVER 与任意 T 平衡得到 T。
* - ANY 与任意 T 平衡得到 ANY。
* - 子类型与父类型平衡得到父类型。
* - 两个浮点取位宽较大者。
* - 两个整数不自动提升,除非一方是另一方的子类型。
* 无法合并返回 0。纯函数。 */
spl_lang_type_id_t spl_lang_type_balance(spl_type_t *type, spl_lang_type_id_t t1,
spl_lang_type_id_t t2);
spl_def_id_t spl_def_alloc(spl_type_t *type, spl_def_node_kind_t kind, const char *name,
spl_dbg_node_t dbg, usize ast_node_ref);
void spl_def_resolve(spl_type_t *type, spl_def_id_t id, spl_lang_type_id_t lang_id);
spl_type_id_t spl_lang_type_get_ir_type(spl_type_t *type, spl_lang_type_id_t lang_id);
spl_lang_type_id_t spl_def_get_lang_id(spl_type_t *type, spl_def_id_t def_id);
spl_type_id_t spl_def_get_type_id(spl_type_t *type, spl_def_id_t def_id);
spl_type_id_t spl_type_fn_param_tid(spl_type_t *type, spl_type_id_t fn_tid, usize idx);
const spl_type_member_t *spl_type_agg_member(spl_type_t *type, spl_type_id_t tid, const char *name);
bool spl_type_agg_member_idx(spl_type_t *type, spl_type_id_t tid, const char *name, usize *out_idx);
spl_type_id_t spl_type_node_push(spl_type_t *type, spl_type_node_t *type_node);
spl_type_id_t spl_type_builder_simple(spl_type_t *type, spl_type_node_kind_t kind);
spl_type_id_t spl_type_builder_int(spl_type_t *type, usize bits, int is_signed);
spl_type_id_t spl_type_builder_float(spl_type_t *type, usize bits);
@@ -212,27 +120,12 @@ spl_type_id_t spl_type_builder_agg(spl_type_t *type, spl_type_node_kind_t tk,
spl_type_member_vec_t *variants, spl_type_id_t tag_type,
spl_type_layout_t layout);
spl_lang_type_id_t spl_lang_make_error(spl_type_t *type);
spl_lang_type_id_t spl_lang_make_infer(spl_type_t *type);
spl_lang_type_id_t spl_lang_make_never(spl_type_t *type);
spl_lang_type_id_t spl_lang_make_any(spl_type_t *type);
spl_lang_type_id_t spl_lang_make_unit(spl_type_t *type);
spl_lang_type_id_t spl_lang_make_bool(spl_type_t *type);
spl_lang_type_id_t spl_lang_make_int(spl_type_t *type, usize bits, int is_signed);
spl_lang_type_id_t spl_lang_make_float(spl_type_t *type, usize bits);
spl_lang_type_id_t spl_lang_make_ptr(spl_type_t *type, spl_lang_type_id_t pointee);
spl_lang_type_id_t spl_lang_make_array(spl_type_t *type, spl_lang_type_id_t elem, usize len);
spl_lang_type_id_t spl_lang_make_slice(spl_type_t *type, spl_lang_type_id_t elem);
spl_lang_type_id_t spl_lang_make_tuple(spl_type_t *type, const spl_lang_type_id_t *elems,
usize count);
spl_lang_type_id_t spl_lang_make_range(spl_type_t *type, spl_lang_type_id_t elem);
spl_lang_type_id_t spl_lang_make_fn(spl_type_t *type, spl_lang_type_id_t ret,
const spl_lang_type_id_t *params, usize nparams);
spl_lang_type_id_t spl_lang_make_aggregate(spl_type_t *type, spl_lang_type_kind_t kind,
const char *name, const spl_lang_type_id_t *members,
usize nmembers);
spl_lang_type_id_t spl_lang_make_varargs(spl_type_t *type);
void spl_type_pure_dump(spl_type_t *type, spl_type_id_t id);
spl_lang_type_id_t spl_type_lookup_builtin(spl_type_t *type, const char *name);
bool spl_type_is_subtype(const spl_type_t *type, spl_type_id_t sub, spl_type_id_t super);
spl_type_id_t spl_type_fn_param_tid(spl_type_t *type, spl_type_id_t fn_tid, usize idx);
const spl_type_member_t *spl_type_agg_member(spl_type_t *type, spl_type_id_t tid, const char *name);
bool spl_type_agg_member_idx(spl_type_t *type, spl_type_id_t tid, const char *name, usize *out_idx);
#endif /* __SPL_TYPE_H__ */
+341 -86
View File
@@ -1,7 +1,7 @@
/* splc0.c - SPL compiler CLI (stage 1, 引导用)
*
* splc0 --dump tokens|ast|all <file> dump 前端产物
* splc0 <in> <out> 编译 (阶段 B 实现)
* splc0 --dump tokens|ast|sema|ir|vm <file> dump 前端产物
* splc0 <in> <out> 编译
*/
#define __SCC_LOG_IMPL_IMPORT_SRC__
#include "../stage0/include/utils.h"
@@ -10,10 +10,15 @@
#include <stdlib.h>
#include <string.h>
#include "ir_opt.h"
#include "vm_opt.h"
#include "spl_ast.h"
#include "spl_ast2ir.h"
#include "spl_diag.h"
#include "spl_ir2vm.h"
#include "spl_lexer.h"
#include "spl_parser.h"
#include "spl_sema.h"
#include "spl_tok.h"
@@ -26,6 +31,24 @@ typedef enum {
DUMP_VM,
} dump_t;
typedef struct {
spl_ast_t *ast;
char *abs_path;
char *src;
} spl_driver_module_t;
typedef VEC(spl_driver_module_t) spl_driver_module_vec_t;
typedef struct {
spl_ast_t main_ast;
spl_tok_vec_t main_toks;
char *main_path;
spl_driver_module_vec_t modules;
MAP(const char *, usize) path_to_idx;
spl_diag_t *diag;
} spl_driver_t;
static char *read_file(const char *path, long *out_len) {
FILE *f = fopen(path, "rb");
if (!f) {
@@ -41,118 +64,338 @@ static char *read_file(const char *path, long *out_len) {
fclose(f);
return NULL;
}
fread(buf, 1, (size_t)len, f);
size_t nread = fread(buf, 1, (size_t)len, f);
fclose(f);
buf[len] = '\0';
*out_len = len;
buf[nread] = '\0';
if (out_len)
*out_len = (long)nread;
return buf;
}
static const char *const tok_type_names[] = {
#define X(name, enum_name, dummy) #enum_name,
KEYWORD_TABLE
#undef X
#define X(name, enum_name, dummy) #enum_name,
TOKEN_TABLE
#undef X
};
/* 注意:按值接收,内部 vec_free 释放与调用方共享的 data 指针。
调用后原 vec 视为已消费,不要再使用。 */
static void dump_tokens(spl_tok_vec_t toks) {
printf("tokens got (%zu)\n", toks.size);
for (usize i = 0; i < toks.size; i++) {
const spl_tok_t *t = &toks.data[i];
printf("[%s] %.*s (%zu:%zu)\n", tok_type_names[t->type], (int)t->len, t->lexeme, t->line,
printf("[%s] %.*s (%zu:%zu)\n", spl_tok_type_name(t->type), (int)t->len, t->lexeme, t->line,
t->col);
}
vec_free(toks);
}
/* 折叠重复分隔符、解析 "." 与 ".." 路径分量,使同一模块的 import 解析结果唯一。
就地重写,'\' 统一成 '/';保留前导分隔符(绝对路径)。
".." 会弹出上一个分量;无分量可弹时按相对语义保留(绝对根则忽略)。 */
static void normalize_import_path(char *p) {
usize n = strlen(p);
char *out = malloc(n + 1);
if (!out)
return;
char *base = out;
char *w = out;
int has_root = (p[0] == '/' || p[0] == '\\');
if (has_root)
*w++ = '/';
char *r = p;
while (*r) {
while (*r == '/' || *r == '\\')
r++;
if (!*r)
break;
char *seg = r;
while (*r && *r != '/' && *r != '\\')
r++;
usize len = (usize)(r - seg);
if (len == 1 && seg[0] == '.')
continue;
if (len == 2 && seg[0] == '.' && seg[1] == '.') {
if (w == base + (has_root ? 1 : 0)) {
if (!has_root) {
*w++ = '.';
*w++ = '.';
}
} else {
char *q = w;
while (q > base && q[-1] != '/')
q--;
usize sl = (usize)(w - q);
if (sl == 2 && q[0] == '.' && q[1] == '.') {
*w++ = '.';
*w++ = '.';
} else {
if (q > base && q[-1] == '/')
q--;
w = q;
if (w == base && has_root)
w = base + 1;
}
}
continue;
}
if (w != base && w[-1] != '/')
*w++ = '/';
memcpy(w, seg, len);
w += len;
}
*w = 0;
strcpy(p, out);
free(out);
}
/* 把 import_path 拼接到 importing_file 所在目录。
from_fname 为空时按相对 cwd 处理。绝对路径直接透传。 */
static void resolve_relative(const char *from, const char *path, char *out, usize cap) {
if (!from || !path) {
if (cap)
out[0] = 0;
return;
}
if (path[0] == '/' || path[0] == '\\' || (path[0] && path[1] == ':')) {
snprintf(out, cap, "%s", path);
normalize_import_path(out);
return;
}
const char *last_sep = NULL;
for (const char *p = from; *p; ++p)
if (*p == '/' || *p == '\\')
last_sep = p;
if (!last_sep) {
snprintf(out, cap, "%s", path);
normalize_import_path(out);
return;
}
usize dir_len = (usize)(last_sep - from) + 1;
snprintf(out, cap, "%.*s%s", (int)dir_len, from, path);
normalize_import_path(out);
}
static spl_ast_t *driver_load(void *user, const char *from_fname, const char *path,
spl_diag_t *diag) {
spl_driver_t *d = user;
char abs[1024];
resolve_relative(from_fname, path, abs, sizeof abs);
usize idx;
if (map_get(d->path_to_idx, abs, &idx)) {
spl_ast_t *a = vec_at(d->modules, idx).ast;
if (!a) {
spl_diag_error(diag, SPL_DIAG_KIND_CIRCULAR_DEP, NULL, "circular import: `%s`", abs);
return NULL;
}
return a;
}
/* 先登记 ast=NULL 占位:若后续解析过程中又有 @import 指回本路径,
会命中这里,判定为环。 */
char *key = strdup(abs);
idx = vec_size(d->modules);
spl_driver_module_t placeholder = {.ast = NULL, .abs_path = key, .src = NULL};
vec_push(d->modules, placeholder);
map_put(d->path_to_idx, key, idx);
long len = 0;
char *src = read_file(abs, &len);
if (!src) {
spl_diag_error(diag, SPL_DIAG_KIND_LEX_UNEXPECTED, NULL, "@import: cannot open `%s`", abs);
return NULL;
}
(void)len;
/* key 同时用作 lexer 的 fname:token.fname 与 map key 共享一份 strdup,
减少一次分配;cleanup 时只 free module.abs_path */
spl_tok_vec_t toks = spl_lex(src, key);
spl_ast_t *ast = malloc(sizeof *ast);
spl_ast_init(ast, &toks);
spl_parser_t parser;
spl_parser_init(&parser);
parser.diag = diag;
spl_ast_parse(&parser, ast);
spl_parser_drop(&parser);
vec_at(d->modules, idx).ast = ast;
vec_at(d->modules, idx).src = src;
return ast;
}
static int compile_spl(const char *src, const char *fname, const char *outpath, int gen_debug,
dump_t dump) {
dump_t dump, int opt) {
(void)gen_debug;
spl_tok_vec_t toks = spl_lex(src, fname);
spl_diag_t diag;
spl_diag_init(&diag, stderr);
spl_driver_t drv = {0};
drv.diag = &diag;
drv.main_path = strdup(fname);
drv.main_toks = spl_lex(src, drv.main_path);
spl_ast_init(&drv.main_ast, &drv.main_toks);
map_init(drv.path_to_idx, MAP_HASH_STR, MAP_CMP_STR);
vec_init(drv.modules);
int rc = 0;
/* dump_tokens 消费 drv.main_toks(共享 data),之后 main_ast.input 悬空,
所以这条分支必须在 spl_ast_parse 之前、且不能再 drop main_ast。 */
if (dump == DUMP_TOKEN) {
dump_tokens(toks);
// memory leak
return 0;
}
spl_ast_t ast;
spl_ast_init(&ast, &toks);
spl_ast_parse(&ast);
if (ast.parsed < 0) {
printf("parse failed, no output\n");
// memory leak
return 1;
}
// spl_ast_valid(&ast); TODO
if (dump == DUMP_AST) {
spl_ast_dump(&ast, ast.root);
// memory leak
dump_tokens(drv.main_toks);
free(drv.main_path);
map_free(drv.path_to_idx);
spl_diag_drop(&diag);
return 0;
}
spl_attr_t attr;
spl_attr_init(&attr);
spl_sema_t sema;
spl_type_t type;
spl_lang_type_t lang_type;
spl_layout_target_t layout_target;
spl_layout_t layout;
spl_scope_t scope;
spl_type_init(&type);
spl_sema_t sema;
spl_ast2ir_t a2ir;
spl_ir_builder_t ir_builder;
spl_ir2vm_t ir2vm;
spl_ast_ptr_vec_t all_asts;
int attr_ok = 0, lang_type_ok = 0, layout_ok = 0, scope_ok = 0;
int sema_ok = 0, a2ir_ok = 0, irb_ok = 0, ir2vm_ok = 0;
spl_diag_set_stage(&diag, SPL_STAGE_PARSE);
spl_parser_t parser;
spl_parser_init(&parser);
parser.diag = &diag;
spl_ast_parse(&parser, &drv.main_ast);
spl_parser_drop(&parser);
if (spl_diag_has_error(&diag) || spl_diag_has_fatal(&diag)) {
spl_diag_summary(&diag);
rc = 1;
goto cleanup;
}
if (dump == DUMP_AST) {
spl_ast_dump(&drv.main_ast, drv.main_ast.root);
goto cleanup;
}
spl_attr_init(&attr);
attr_ok = 1;
spl_lang_type_init(&lang_type);
lang_type_ok = 1;
/* 目标描述符:剥离目标机器细节,layout 是 size/align/offset 的唯一来源。 */
memset(&layout_target, 0, sizeof layout_target);
layout_target.endian = SPL_LITTLE_ENDIAN;
layout_target.ptr_size = sizeof(void *);
layout_target.ptr_align = sizeof(void *);
spl_layout_init(&layout, &layout_target);
layout_ok = 1;
spl_scope_init(&scope);
spl_sema_init(&sema, &ast, &type, &scope, &attr);
sema.ast = &ast;
scope_ok = 1;
spl_sema_init(&sema, &drv.main_ast, &lang_type, &layout, &scope, &attr);
sema_ok = 1;
spl_module_loader_t loader = {.load = driver_load, .user = &drv};
sema.loader = &loader;
spl_diag_set_stage(&diag, SPL_STAGE_SEMA);
sema.diag = &diag;
spl_sema_run(&sema);
if (sema.error_count) {
printf("sema errors=%d, no output\n", sema.error_count);
// memory leak
return 1;
if (spl_diag_has_error(&diag) || spl_diag_has_fatal(&diag)) {
spl_diag_summary(&diag);
rc = 1;
goto cleanup;
}
if (dump == DUMP_SEMA) {
spl_sema_dump(&sema);
spl_ast_dump(&ast, ast.root);
// memory leak
return 0;
spl_ast_dump(&drv.main_ast, drv.main_ast.root);
goto cleanup;
}
spl_ast2ir_t a2ir;
spl_ir_builder_t ir_builder;
/* 收集所有 ast:主文件在前,被 import 的文件按加载顺序在后。
ast2ir 内部会 vec_free(all_asts)。 */
vec_init(all_asts);
vec_push(all_asts, &drv.main_ast);
for (usize i = 0; i < vec_size(drv.modules); ++i)
vec_push(all_asts, vec_at(drv.modules, i).ast);
spl_ir_builder_init(&ir_builder);
spl_ast2ir_init(&a2ir, &ir_builder, &sema);
spl_ast2ir_run(&a2ir);
if (a2ir.err_count) {
printf("ast2ir errors=%d, no output\n", a2ir.err_count);
spl_ast2ir_drop(&a2ir);
spl_sema_drop(&sema);
spl_ast_drop(&ast);
return 1;
irb_ok = 1;
spl_ast2ir_init(&a2ir, &ir_builder, &layout, &sema);
a2ir_ok = 1;
spl_diag_set_stage(&diag, SPL_STAGE_AST2IR);
a2ir.diag = &diag;
spl_ast2ir_run(&a2ir, all_asts);
if (spl_diag_has_error(&diag) || spl_diag_has_fatal(&diag)) {
spl_diag_summary(&diag);
rc = 1;
goto cleanup;
}
/* IR 层优化 pass(默认关闭,--opt 开启)。 */
if (opt)
spl_ir_opt(&ir_builder.ir, &attr);
if (dump == DUMP_IR) {
spl_ir_dump(&ir_builder.ir, &type, &attr);
// memory leak
return 0;
spl_ir_dump(&ir_builder.ir, &lang_type.ir, &attr);
goto cleanup;
}
spl_ir2vm_t ir2vm;
spl_ir2vm_init(&ir2vm, &a2ir.ir->ir, &type, &attr);
int rc = spl_ir2vm_run(&ir2vm);
if (rc != 0) {
printf("ir2vm error %d", rc);
return rc;
/* 类型已全部就绪:解析 layout,供 ir2vm 的 size/align/offset 使用。 */
spl_layout_resolve(&layout, &lang_type.ir);
spl_ir2vm_init(&ir2vm, &a2ir.ir->ir, &lang_type.ir, &layout, &attr, opt);
ir2vm_ok = 1;
spl_diag_set_stage(&diag, SPL_STAGE_IR2VM);
ir2vm.diag = &diag;
rc = spl_ir2vm_run(&ir2vm);
if (spl_diag_has_error(&diag) || spl_diag_has_fatal(&diag)) {
spl_diag_summary(&diag);
goto cleanup;
}
if (dump == DUMP_VM) {
spl_prog_dump(&ir2vm.prog);
// memory leak
return 0;
goto cleanup;
}
spl_prog_store_to_file(outpath, &ir2vm.prog);
/* ir2vm 之后的字节码优化 pass(默认关闭,--opt 开启)。 */
if (opt)
spl_prog_opt(&ir2vm.prog);
spl_ir2vm_drop(&ir2vm);
spl_ast2ir_drop(&a2ir);
spl_sema_drop(&sema);
spl_ast_drop(&ast);
// memory leak
return 0;
spl_prog_store_to_file(outpath, &ir2vm.prog);
rc = 0;
cleanup:
if (ir2vm_ok)
spl_ir2vm_drop(&ir2vm);
if (a2ir_ok)
spl_ast2ir_drop(&a2ir);
if (irb_ok)
spl_ir_builder_drop(&ir_builder);
if (sema_ok)
spl_sema_drop(&sema);
if (scope_ok)
spl_scope_drop(&scope);
if (layout_ok)
spl_layout_drop(&layout);
if (lang_type_ok)
spl_lang_type_drop(&lang_type);
if (attr_ok)
spl_attr_drop(&attr);
spl_ast_drop(&drv.main_ast);
for (usize i = 0; i < vec_size(drv.modules); ++i) {
spl_ast_t *a = vec_at(drv.modules, i).ast;
if (a) {
spl_ast_drop(a);
free(a);
}
free(vec_at(drv.modules, i).abs_path);
free(vec_at(drv.modules, i).src);
}
vec_free(drv.modules);
/* map 的 key 是 module 里 strdup 出的 abs_path,已随 module 释放 */
map_free(drv.path_to_idx);
free(drv.main_path);
spl_diag_drop(&diag);
return rc;
}
int main(int argc, char **argv) {
@@ -165,6 +408,11 @@ int main(int argc, char **argv) {
LOG_INFO("splc0 --dump <flags> <file> dump: lex,ast,sema,ir,vm,none");
return 0;
}
int opt = 0;
for (int ai = 1; ai < argc; ai++)
if (strcmp(argv[ai], "--opt") == 0)
opt = 1;
int argi = 1;
if (argi >= argc) {
LOG_FATAL("Usage: splc0 [--dump <flags>] <in> [out]");
@@ -179,20 +427,20 @@ int main(int argc, char **argv) {
}
const char *flags = argv[argi + 1];
if (strstr(flags, "lex") != NULL) {
if (strcmp(flags, "lex") == 0) {
dump = DUMP_TOKEN;
} else if (strstr(flags, "ast") != NULL) {
} else if (strcmp(flags, "ast") == 0) {
dump = DUMP_AST;
} else if (strstr(flags, "sema") != NULL) {
} else if (strcmp(flags, "sema") == 0) {
dump = DUMP_SEMA;
} else if (strstr(flags, "ir") != NULL) {
} else if (strcmp(flags, "ir") == 0) {
dump = DUMP_IR;
} else if (strstr(flags, "vm") != NULL) {
} else if (strcmp(flags, "vm") == 0) {
dump = DUMP_VM;
} else if (strstr(flags, "none") != NULL) {
} else if (strcmp(flags, "none") == 0) {
dump = DUMP_NONE;
} else {
Panic("--dump unknown flags");
LOG_FATAL("splc0: --dump takes one of lex|ast|sema|ir|vm|none, got `%s`", flags);
return 1;
}
argi += 2;
@@ -203,19 +451,26 @@ int main(int argc, char **argv) {
return 1;
}
int gen_debug = 0;
if (strcmp(argv[argi], "-g") == 0) {
gen_debug = 1;
argi++;
while (argi < argc - 1) {
if (strcmp(argv[argi], "-g") == 0) {
gen_debug = 1;
argi++;
} else if (strcmp(argv[argi], "--opt") == 0) {
opt = 1;
argi++;
} else {
break;
}
}
if (argc < argi + 2) {
LOG_FATAL("Usage: splc0 <in> <out> [-g] or splc0 --dump <flags> <file>\n");
LOG_FATAL("Usage: splc0 <in> <out> [-g] [--opt] or splc0 --dump <flags> <file>\n");
return 1;
}
long len;
char *src = read_file(argv[argi], &len);
if (!src)
return 1;
int rc = compile_spl(src, argv[argi], argv[argi + 1], gen_debug, dump);
int rc = compile_spl(src, argv[argi], argv[argi + 1], gen_debug, dump, opt);
free(src);
return rc;
}
+165
View File
@@ -0,0 +1,165 @@
/* vm_opt.c - 字节码(VM 层)优化 pass
*
* 从 stage0/spl_mcode.c 迁出:对已生成的 spl_prog_t 做 prog->prog 优化。
* 默认不启用(由 splc0 --opt 驱动)。 */
#include "vm_opt.h"
#include "../stage0/include/core_map.h"
#include <stdlib.h>
/* 消除“store 后立刻 load 同一槽位”的往返。
*
* 模式(5 条):laddr X; swap; store k; laddr X; load k
* 变换(3 条):laddr X; pick 1; store k
* [.., v] -> laddr X => [.., v, X]
* -> pick 1 => [.., v, X, v]
* -> store k => [.., v] (存 v 到 X,同时栈上保留 v)
* 与原来 store 后再 load 的效果等价,但去掉 2 条指令与一次内存读。
*
* 会真的删除指令,因此必须重定位:相对跳转、函数表 address、
* 以及作为函数指针的 `push ptr <addr>`。仅当被删/被改写的指令不是
* 跳转目标或函数入口时才变换(已收集 mark)。 */
static usize peep_hash_int(usize k) { return MAP_HASH_INT(k); }
static int peep_cmp_int(usize a, usize b) { return MAP_CMP_INT(a, b); }
void spl_prog_peephole(spl_prog_t *prog) {
if (!prog)
return;
usize n = vec_size(prog->insns);
if (n < 5)
return;
spl_vm_ins_t *ins = prog->insns.data;
unsigned char *mark = (unsigned char *)calloc(n, 1); /* 跳转目标/函数入口,受保护 */
unsigned char *rem = (unsigned char *)calloc(n, 1);
if (!mark || !rem) {
free(mark);
free(rem);
return;
}
for (usize i = 0; i < n; i++) {
spl_vm_ins_t *x = &ins[i];
if (x->opcode == SPL_JMP || x->opcode == SPL_BZ || x->opcode == SPL_BNZ) {
isize t = (isize)i + 1 + (isize)x->imm;
if (t >= 0 && (usize)t < n)
mark[(usize)t] = 1;
}
}
vec_for(prog->funcs, fi) {
spl_vm_func_t *f = &vec_at(prog->funcs, fi);
if (f->address < n)
mark[f->address] = 1;
}
for (usize i = 0; i + 4 < n; i++) {
spl_vm_ins_t *a = &ins[i];
spl_vm_ins_t *b = &ins[i + 1];
spl_vm_ins_t *c = &ins[i + 2];
spl_vm_ins_t *d = &ins[i + 3];
spl_vm_ins_t *e = &ins[i + 4];
if (a->opcode == SPL_LADDR && b->opcode == SPL_SWAP && c->opcode == SPL_STORE &&
d->opcode == SPL_LADDR && e->opcode == SPL_LOAD && a->imm == d->imm &&
c->type == e->type) {
if (mark[i + 1] || mark[i + 3] || mark[i + 4])
continue;
b->opcode = SPL_PICK;
b->type = SPL_VOID;
b->imm = 1;
rem[i + 3] = 1;
rem[i + 4] = 1;
i += 4;
}
}
usize kept = 0;
for (usize i = 0; i < n; i++)
if (!rem[i])
kept++;
if (kept == n) {
free(mark);
free(rem);
return;
}
spl_vm_ins_t *fresh = (spl_vm_ins_t *)malloc(sizeof(spl_vm_ins_t) * (kept ? kept : 1));
usize *map = (usize *)malloc(sizeof(usize) * (n + 1)); /* old index -> new index */
usize *old_of_new = (usize *)malloc(sizeof(usize) * (kept ? kept : 1));
if (!fresh || !map || !old_of_new) {
free(mark);
free(rem);
free(fresh);
free(map);
free(old_of_new);
return;
}
usize out = 0;
for (usize i = 0; i < n; i++) {
if (!rem[i]) {
map[i] = out;
old_of_new[out] = i;
fresh[out] = ins[i];
out++;
}
}
map[n] = kept;
for (usize i = n; i-- > 0;)
if (rem[i])
map[i] = map[i + 1];
/* 函数地址 old->new */
MAP(usize, usize) addr_map;
map_init(addr_map, peep_hash_int, peep_cmp_int);
vec_for(prog->funcs, fi) {
spl_vm_func_t *f = &vec_at(prog->funcs, fi);
usize old_a = f->address;
if (old_a < n) {
usize new_a = map[old_a];
map_put(addr_map, old_a, new_a);
f->address = new_a;
}
}
/* 修正相对跳转与函数指针常量 */
for (usize j = 0; j < kept; j++) {
spl_vm_ins_t *x = &fresh[j];
if (x->opcode == SPL_JMP || x->opcode == SPL_BZ || x->opcode == SPL_BNZ) {
isize t = (isize)old_of_new[j] + 1 + (isize)x->imm;
if (t >= 0 && (usize)t <= n) {
usize nt = map[(usize)t];
x->imm = (spl_vm_val_t)(isize)((isize)nt - (isize)(j + 1));
}
} else if (x->opcode == SPL_PUSH && x->type == SPL_PTR && x->imm != 0) {
usize nv = 0;
if (map_get(addr_map, (usize)x->imm, &nv))
x->imm = nv;
}
}
map_free(addr_map);
/* 指令删除后函数长度变化,重算 ninsns(函数按地址升序排列)。 */
{
usize nf = vec_size(prog->funcs);
for (usize i = 0; i < nf; i++) {
spl_vm_func_t *f = &vec_at(prog->funcs, i);
usize next = (i + 1 < nf) ? vec_at(prog->funcs, i + 1).address : kept;
f->ninsns = next > f->address ? (spl_vm_val_t)(next - f->address) : 0;
}
}
free(prog->insns.data);
prog->insns.data = fresh;
prog->insns.size = kept;
prog->insns.cap = kept;
free(mark);
free(rem);
free(map);
free(old_of_new);
}
/* 优化 pass 流水线(纯 prog->prog):当前 P1,后续可追加。 */
void spl_prog_opt(spl_prog_t *prog) { spl_prog_peephole(prog); }
+11
View File
@@ -0,0 +1,11 @@
#ifndef __VM_OPT_H__
#define __VM_OPT_H__
/* 字节码(VM 层)优化 pass:输入输出都是 spl_prog_t,纯函数式、可开关。 */
#include "../stage0/spl_mcode.h"
void spl_prog_peephole(spl_prog_t *prog);
void spl_prog_opt(spl_prog_t *prog);
#endif /* __VM_OPT_H__ */
+687
View File
@@ -0,0 +1,687 @@
// Expect retcode = 0
// AST:节点为带载荷 ADT,节点引用是 arena 序号(0 为 NONE 哨兵)。最终 dump/语义须与
// splc0 一致。
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 .{ .Punct } = @import("token.spl");
type BigInt = @import("utils/mp.spl");
type .{ .Span, .span_none, .span_make, .span_is_none } = @import("./diag.spl");
type .{ .Sources, .Source, .set_at } = @import("utils/source.spl");
// 算子不另立 enum:AST 直接存词法 Punct。一元/二元由节点 kind 区分
// (一元:Sub=负 / Not=! / BitNot=~ / And=& / Mul=.*;二元即对应 Punct)。
type Body = enum {
None,
Root: struct { members: usize },
ContainerMembers: struct { external_tags: Vec, declarations: Vec },
ExternalTagItem: struct { is_at: bool, is_external: bool, name: ByteView, args: Vec },
TagItem: struct { is_at: bool, is_external: bool, name: ByteView, args: Vec },
ComptimeDecl: struct { body: usize },
TopLevelBlock: struct { tag_list: Vec, block: usize },
FnDecl: struct {
tag_list: Vec,
is_comptime: bool,
name: ByteView,
params: Vec,
ret_type: usize,
body: usize,
},
VarDecl: struct { attr_list: Vec, pattern: usize, type_expr: usize, init_expr: usize },
ConstDecl: struct { attr_list: Vec, pattern: usize, type_expr: usize, init_expr: usize },
TypeDecl: struct { attr_list: Vec, pattern: usize, type_expr: usize },
MemberDecl: struct {
attr_list: Vec,
name: ByteView,
type_expr: usize,
init_expr: usize,
},
ParamDecl: struct { attr_list: Vec, is_comptime: bool, name: ByteView, type_expr: usize },
StatementRet: struct { expr: usize },
StatementBreak: struct { label: usize, expr: usize },
StatementContinue: struct { label: usize },
StatementDefer: struct { body: usize },
StatementErrdefer: struct { body: usize }, // errdefer:仅错误退出时执行
StatementComptime: struct { body: usize },
StatementDecl: struct { decl: usize },
StatementExpr: struct { expr: usize },
AggregateMember: struct {
attr_list: Vec,
name: ByteView,
type_expr: usize,
init_expr: usize,
},
StructInitItem: struct { name: ByteView, expr: usize },
MatchArm: struct { pattern: usize, body: usize },
Label: struct { name: ByteView },
ExprIntLit: struct { value: BigInt }, // 任意宽度(mp),解析后由 Ast 拥有
ExprFloatLit: struct { text: ByteView },
ExprCharLit: struct { value: u8 },
ExprStringLit: struct { data: ByteView },
ExprBoolLit: struct { value: bool },
ExprNullLit,
ExprUndefinedLit,
ExprIdent: struct { name: ByteView },
ExprUnary: struct { op: Punct, operand: usize },
ExprBinary: struct { op: Punct, left: usize, right: usize },
ExprCall: struct { callee: usize, args: Vec },
ExprField: struct { obj: usize, name: ByteView },
ExprIndex: struct { obj: usize, index: usize },
ExprSlice: struct { obj: usize, begin: usize, end: usize },
ExprCast: struct { expr: usize, type_expr: usize },
ExprTuple: struct { items: Vec },
ExprArrayLit: struct {
has_len: bool,
len: usize,
is_underscore_len: bool,
elem_type: usize,
elems: Vec,
},
ExprAggregateLit: struct { name: ByteView, fields: Vec },
ExprComptimeCall: struct { name: ByteView, args: Vec },
ExprTry: struct { value: usize }, // `try expr`
ExprCatch: struct { value: usize, pattern: usize, handler: usize }, // `expr catch <pat> { }`
ExprBlock: struct { stmts: Vec, tail_expr: usize },
ExprIf: struct { cond: usize, then_block: usize, else_branch: usize },
ExprIfvar: struct { var_decl: usize, then_block: usize, else_branch: usize },
ExprWhile: struct { label: usize, cond: usize, body: usize, else_branch: usize },
ExprLoop: struct { label: usize, body: usize },
ExprFor: struct {
label: usize,
iter_exprs: Vec,
pattern: usize,
body: usize,
else_branch: usize,
},
ExprMatch: struct { matched_expr: usize, arms: Vec },
ExprFnType: struct { is_comptime: bool, params: Vec, ret_type: usize },
TypePath: struct { name: ByteView, generic_args: Vec },
TypePointer: struct { pointee: usize },
TypeArray: struct {
has_len: bool,
len: isize,
is_underscore_len: bool,
elem_type: usize,
},
TypeSlice: struct { elem_type: usize },
TypeFn: struct { is_comptime: bool, params: Vec, ret_type: usize },
TypeParen: struct { types: Vec },
TypeAggregate: struct { kind_name: ByteView, members: Vec },
TypeComptimeCall: struct { name: ByteView, args: Vec },
PatternIdent: struct { name: ByteView },
PatternUnderscore,
PatternDot: struct { name: ByteView, sub_pattern: usize },
PatternTuple: struct { items: Vec },
PatternList: struct { items: Vec },
PatternAggregate: struct { items: Vec },
PatternField: struct { name: ByteView, pattern: usize },
PatternRef: struct { pat: usize },
ExprComptime: struct { body: usize },
ExprComptimeInit: struct { name: ByteView, args: Vec, fields: Vec },
}
fn kind_name(body: Body): []u8 {
match body {
.None => { ret "none"; },
.Root(_) => { ret "root"; },
.ContainerMembers(_) => { ret "container_members"; },
.ExternalTagItem(_) => { ret "external_tag_item"; },
.TagItem(_) => { ret "tag_item"; },
.ComptimeDecl(_) => { ret "comptime_decl"; },
.TopLevelBlock(_) => { ret "top_level_block"; },
.FnDecl(_) => { ret "fn_decl"; },
.VarDecl(_) => { ret "var_decl"; },
.ConstDecl(_) => { ret "const_decl"; },
.TypeDecl(_) => { ret "type_decl"; },
.MemberDecl(_) => { ret "member_decl"; },
.ParamDecl(_) => { ret "param_decl"; },
.StatementRet(_) => { ret "statement_ret"; },
.StatementBreak(_) => { ret "statement_break"; },
.StatementContinue(_) => { ret "statement_continue"; },
.StatementDefer(_) => { ret "statement_defer"; },
.StatementErrdefer(_) => { ret "statement_errdefer"; },
.StatementComptime(_) => { ret "statement_comptime"; },
.ExprComptime(_) => { ret "expr_comptime"; },
.StatementDecl(_) => { ret "statement_decl"; },
.StatementExpr(_) => { ret "statement_expr"; },
.AggregateMember(_) => { ret "aggregate_member"; },
.StructInitItem(_) => { ret "struct_init_item"; },
.MatchArm(_) => { ret "match_arm"; },
.Label(_) => { ret "label"; },
.ExprIntLit(_) => { ret "int_lit"; },
.ExprFloatLit(_) => { ret "float_lit"; },
.ExprCharLit(_) => { ret "char_lit"; },
.ExprStringLit(_) => { ret "string_lit"; },
.ExprBoolLit(_) => { ret "bool_lit"; },
.ExprNullLit => { ret "null_lit"; },
.ExprUndefinedLit => { ret "undefined_lit"; },
.ExprIdent(_) => { ret "ident"; },
.ExprUnary(_) => { ret "unary"; },
.ExprBinary(_) => { ret "binary"; },
.ExprCall(_) => { ret "call"; },
.ExprField(_) => { ret "field"; },
.ExprIndex(_) => { ret "index"; },
.ExprSlice(_) => { ret "slice"; },
.ExprCast(_) => { ret "cast"; },
.ExprTuple(_) => { ret "tuple"; },
.ExprArrayLit(_) => { ret "array_literal"; },
.ExprAggregateLit(_) => { ret "aggregate_literal"; },
.ExprComptimeCall(_) => { ret "comptime_call"; },
.ExprTry(_) => { ret "try_expr"; },
.ExprCatch(_) => { ret "catch_expr"; },
.ExprComptimeInit(_) => { ret "comptime_init"; },
.ExprBlock(_) => { ret "block"; },
.ExprIf(_) => { ret "if_expr"; },
.ExprIfvar(_) => { ret "ifvar_expr"; },
.ExprWhile(_) => { ret "while_expr"; },
.ExprLoop(_) => { ret "loop_expr"; },
.ExprFor(_) => { ret "for_expr"; },
.ExprMatch(_) => { ret "match_expr"; },
.ExprFnType(_) => { ret "fn_type"; },
.TypePath(_) => { ret "type_path"; },
.TypePointer(_) => { ret "type_pointer"; },
.TypeArray(_) => { ret "type_array"; },
.TypeSlice(_) => { ret "type_slice"; },
.TypeFn(_) => { ret "type_fn"; },
.TypeParen(_) => { ret "type_paren"; },
.TypeAggregate(_) => { ret "type_aggregate"; },
.TypeComptimeCall(_) => { ret "type_comptime_call"; },
.PatternIdent(_) => { ret "pattern_ident"; },
.PatternUnderscore => { ret "pattern_underscore"; },
.PatternDot(_) => { ret "pattern_dot"; },
.PatternTuple(_) => { ret "pattern_tuple"; },
.PatternList(_) => { ret "pattern_list"; },
.PatternAggregate(_) => { ret "pattern_aggregate"; },
.PatternField(_) => { ret "pattern_field"; },
.PatternRef(_) => { ret "pattern_ref"; },
}
ret "?";
}
type Node = struct {
span: Span,
resolved_def_id: usize,
resolved_lang_tid: usize,
expected_lang_tid: usize,
attrs: Vec,
body: Body,
}
type Ast = @self;
nodes: Vec, // Node
root: usize,
sources: *Sources, // 借用;可为 null(dump 时不解析行列)
strings: Vec, // ByteBuffer:已解码的字符串字面量字节(ByteView 指向其中)
type AstResult = enum {
Ok: Ast,
Err: Error,
}
fn new_node(body: Body, span: Span): Node {
ret Node {
.span = span,
.resolved_def_id = 0,
.resolved_lang_tid = 0,
.expected_lang_tid = 0,
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
.body = body,
};
}
fn new(sources: *Sources): AstResult {
var ast = Ast {
.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) {
.Ok => {},
.Err(error) => {
Vec.drop(&ast.nodes);
ret .Err(error);
},
}
ret .Ok(ast);
}
fn drop(self: *Ast) {
var i: usize = 0;
while i < self.nodes.len {
var n = node(self, i);
if n != null {
Vec.drop(&n.attrs);
match n.body {
.ExprIntLit(x) => { BigInt.drop(&x.value); },
_ => {},
}
}
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;
}
fn node(self: *Ast, ref: usize): *Node {
if ref >= self.nodes.len { ret null; }
ret (self.nodes.data as *Node) + ref;
}
// 追加节点,返回其引用(失败返回 0)。
fn alloc(self: *Ast, body: Body, span: Span): usize {
var n = new_node(body, span);
match Vec.push(&self.nodes, &n as *u8) {
.Ok => {},
.Err(error) => {
Vec.drop(&n.attrs);
ret 0;
},
}
ret self.nodes.len - 1;
}
// 注册一段已解码的字符串字节(Ast 拥有;ByteView 指向其中,生命周期同 Ast)。
// 空字符串也保证非空 data 指针(空字面量仍有稳定非空地址)。
fn push_string(self: *Ast, bytes: ByteBuffer): ByteView {
var b = bytes;
if b.storage.len == 0 {
ByteBuffer.reserve(&b, 1);
}
@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;
}
fn ref_vec(): Vec {
ret Vec.blank(@sizeof(usize), @alignof(usize));
}
//
// AST:
// <indent>`-/|-kind #ref [line:col]
// indent:栈上每层 last 为真输出 " ",否则 "| "。
fn push(buf: *ByteBuffer, bytes: []u8): bool {
match ByteBuffer.append(buf, bytes) {
.Ok => { ret true; },
.Err(error) => { ret false; },
}
ret false;
}
fn push_byte(buf: *ByteBuffer, byte: u8): bool {
match ByteBuffer.append_byte(buf, byte) {
.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 ref_at(list: Vec, index: usize): usize {
match Vec.get(&list, index) {
.Ok(ptr) => { ret (ptr as *usize).*; },
.Err(error) => { ret 0; },
}
ret 0;
}
fn dump_opt(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool): bool {
if ref == 0 { ret true; }
ret dump_node(ast, buf, stack, ref, last);
}
fn dump_list(ast: *Ast, buf: *ByteBuffer, stack: *Vec, list: Vec): bool {
var i: usize = 0;
while i < list.len {
var last = (i + 1 == list.len);
if !dump_node(ast, buf, stack, ref_at(list, i), last) { ret false; }
i = i + 1;
}
ret true;
}
fn dump_node(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool): bool {
var n = node(ast, ref);
if n == null { ret false; }
var i: usize = 0;
while i < stack.len {
var flag: u8 = 0;
match Vec.get(stack, i) {
.Ok(ptr) => { flag = ptr.*; },
.Err(error) => { ret false; },
}
if !push(buf, if flag != 0 { " " } else { "| " }) { ret false; }
i = i + 1;
}
if !push(buf, if last { "`-" } else { "|-" }) { ret false; }
if !push(buf, kind_name(n.body)) { ret false; }
if !push(buf, " #") { ret false; }
if !push_usize(buf, ref) { ret false; }
if !span_is_none(n.span) && ast.sources != null {
var src = set_at(ast.sources, n.span.file);
if src != null {
var pos = Source.line_col(src.*, n.span.start);
if !push(buf, " [") { ret false; }
if !push_usize(buf, pos.line) { ret false; }
if !push_byte(buf, ':') { ret false; }
if !push_usize(buf, pos.col) { ret false; }
if !push_byte(buf, ']') { ret false; }
}
}
if !push_byte(buf, '\n') { ret false; }
var l: u8 = 0;
if last { l = 1; }
match Vec.push(stack, &l as *u8) {
.Ok => {},
.Err(error) => { ret false; },
}
var ok = true;
match n.body {
.None => {},
.Root(r) => { ok = dump_opt(ast, buf, stack, r.members, true); },
.ContainerMembers(c) => {
ok = dump_list(ast, buf, stack, c.external_tags) &&
dump_list(ast, buf, stack, c.declarations);
},
.ExternalTagItem(t) => { ok = dump_list(ast, buf, stack, t.args); },
.TagItem(t) => { ok = dump_list(ast, buf, stack, t.args); },
.ComptimeDecl(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
.TopLevelBlock(t) => {
ok = dump_list(ast, buf, stack, t.tag_list) &&
dump_opt(ast, buf, stack, t.block, true);
},
.FnDecl(f) => {
ok = dump_list(ast, buf, stack, f.tag_list) &&
dump_list(ast, buf, stack, f.params) &&
dump_opt(ast, buf, stack, f.ret_type, false) &&
dump_opt(ast, buf, stack, f.body, true);
},
.VarDecl(d) => {
ok = dump_list(ast, buf, stack, d.attr_list) &&
dump_opt(ast, buf, stack, d.pattern, false) &&
dump_opt(ast, buf, stack, d.type_expr, false) &&
dump_opt(ast, buf, stack, d.init_expr, true);
},
.ConstDecl(d) => {
ok = dump_list(ast, buf, stack, d.attr_list) &&
dump_opt(ast, buf, stack, d.pattern, false) &&
dump_opt(ast, buf, stack, d.type_expr, false) &&
dump_opt(ast, buf, stack, d.init_expr, true);
},
.TypeDecl(d) => {
ok = dump_list(ast, buf, stack, d.attr_list) &&
dump_opt(ast, buf, stack, d.pattern, false) &&
dump_opt(ast, buf, stack, d.type_expr, true);
},
.MemberDecl(d) => {
ok = dump_list(ast, buf, stack, d.attr_list) &&
dump_opt(ast, buf, stack, d.type_expr, false) &&
dump_opt(ast, buf, stack, d.init_expr, true);
},
.ParamDecl(p) => {
ok = dump_list(ast, buf, stack, p.attr_list) &&
dump_opt(ast, buf, stack, p.type_expr, true);
},
.StatementRet(e) => { ok = dump_opt(ast, buf, stack, e.expr, true); },
.StatementBreak(b) => {
ok = dump_opt(ast, buf, stack, b.label, false) &&
dump_opt(ast, buf, stack, b.expr, true);
},
.StatementContinue(x) => { ok = dump_opt(ast, buf, stack, x.label, true); },
.StatementDefer(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
.StatementErrdefer(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
.StatementComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
.ExprComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
.StatementDecl(d) => { ok = dump_opt(ast, buf, stack, d.decl, true); },
.StatementExpr(e) => { ok = dump_opt(ast, buf, stack, e.expr, true); },
.AggregateMember(d) => {
ok = dump_list(ast, buf, stack, d.attr_list) &&
dump_opt(ast, buf, stack, d.type_expr, false) &&
dump_opt(ast, buf, stack, d.init_expr, true);
},
.StructInitItem(s) => { ok = dump_opt(ast, buf, stack, s.expr, true); },
.MatchArm(m) => {
ok = dump_opt(ast, buf, stack, m.pattern, false) &&
dump_opt(ast, buf, stack, m.body, true);
},
.Label(x) => {},
.ExprIntLit(x) => {},
.ExprFloatLit(x) => {},
.ExprCharLit(x) => {},
.ExprStringLit(x) => {},
.ExprBoolLit(x) => {},
.ExprNullLit => {},
.ExprUndefinedLit => {},
.ExprIdent(x) => {},
.ExprUnary(u) => { ok = dump_opt(ast, buf, stack, u.operand, true); },
.ExprBinary(b) => {
ok = dump_opt(ast, buf, stack, b.left, false) &&
dump_opt(ast, buf, stack, b.right, true);
},
.ExprCall(c) => {
ok = dump_opt(ast, buf, stack, c.callee, false) &&
dump_list(ast, buf, stack, c.args);
},
.ExprField(f) => { ok = dump_opt(ast, buf, stack, f.obj, true); },
.ExprIndex(x) => {
ok = dump_opt(ast, buf, stack, x.obj, false) &&
dump_opt(ast, buf, stack, x.index, true);
},
.ExprSlice(s) => {
ok = dump_opt(ast, buf, stack, s.obj, false) &&
dump_opt(ast, buf, stack, s.begin, false) &&
dump_opt(ast, buf, stack, s.end, true);
},
.ExprCast(c) => {
ok = dump_opt(ast, buf, stack, c.expr, false) &&
dump_opt(ast, buf, stack, c.type_expr, true);
},
.ExprTuple(v) => { ok = dump_list(ast, buf, stack, v.items); },
.ExprArrayLit(a) => {
ok = dump_opt(ast, buf, stack, a.elem_type, false) &&
dump_list(ast, buf, stack, a.elems);
},
.ExprAggregateLit(a) => { ok = dump_list(ast, buf, stack, a.fields); },
.ExprComptimeCall(c) => { ok = dump_list(ast, buf, stack, c.args); },
.ExprTry(t2) => { ok = dump_opt(ast, buf, stack, t2.value, true); },
.ExprCatch(c2) => {
ok = dump_opt(ast, buf, stack, c2.value, false) &&
dump_opt(ast, buf, stack, c2.pattern, false) &&
dump_opt(ast, buf, stack, c2.handler, true);
},
.ExprComptimeInit(x) => { ok = dump_list(ast, buf, stack, x.fields); },
.ExprBlock(b) => {
ok = dump_list(ast, buf, stack, b.stmts) &&
dump_opt(ast, buf, stack, b.tail_expr, true);
},
.ExprIf(x) => {
ok = dump_opt(ast, buf, stack, x.cond, false) &&
dump_opt(ast, buf, stack, x.then_block, false) &&
dump_opt(ast, buf, stack, x.else_branch, true);
},
.ExprIfvar(x) => {
ok = dump_opt(ast, buf, stack, x.var_decl, false) &&
dump_opt(ast, buf, stack, x.then_block, false) &&
dump_opt(ast, buf, stack, x.else_branch, true);
},
.ExprWhile(w) => {
ok = dump_opt(ast, buf, stack, w.label, false) &&
dump_opt(ast, buf, stack, w.cond, false) &&
dump_opt(ast, buf, stack, w.body, false) &&
dump_opt(ast, buf, stack, w.else_branch, true);
},
.ExprLoop(x) => {
ok = dump_opt(ast, buf, stack, x.label, false) &&
dump_opt(ast, buf, stack, x.body, true);
},
.ExprFor(f) => {
ok = dump_opt(ast, buf, stack, f.label, false) &&
dump_list(ast, buf, stack, f.iter_exprs) &&
dump_opt(ast, buf, stack, f.pattern, false) &&
dump_opt(ast, buf, stack, f.body, false) &&
dump_opt(ast, buf, stack, f.else_branch, true);
},
.ExprMatch(m) => {
ok = dump_opt(ast, buf, stack, m.matched_expr, false) &&
dump_list(ast, buf, stack, m.arms);
},
.ExprFnType(f) => {
ok = dump_list(ast, buf, stack, f.params) &&
dump_opt(ast, buf, stack, f.ret_type, true);
},
.TypePath(p) => {},
.TypePointer(p) => { ok = dump_opt(ast, buf, stack, p.pointee, true); },
.TypeArray(a) => { ok = dump_opt(ast, buf, stack, a.elem_type, true); },
.TypeSlice(s) => { ok = dump_opt(ast, buf, stack, s.elem_type, true); },
.TypeFn(f) => {
ok = dump_list(ast, buf, stack, f.params) &&
dump_opt(ast, buf, stack, f.ret_type, true);
},
.TypeParen(p) => { ok = dump_list(ast, buf, stack, p.types); },
.TypeAggregate(a) => { ok = dump_list(ast, buf, stack, a.members); },
.TypeComptimeCall(c) => { ok = dump_list(ast, buf, stack, c.args); },
.PatternIdent(x) => {},
.PatternUnderscore => {},
.PatternDot(p) => { ok = dump_opt(ast, buf, stack, p.sub_pattern, true); },
.PatternTuple(v) => { ok = dump_list(ast, buf, stack, v.items); },
.PatternList(v) => { ok = dump_list(ast, buf, stack, v.items); },
.PatternAggregate(v) => { ok = dump_list(ast, buf, stack, v.items); },
.PatternField(p) => { ok = dump_opt(ast, buf, stack, p.pattern, true); },
.PatternRef(p) => { ok = dump_opt(ast, buf, stack, p.pat, true); },
}
stack.len = stack.len - 1;
ret ok;
}
// 把整棵树 dump 到 out(追加,不 clear)。
fn dump(ast: *Ast, out: *ByteBuffer): bool {
if !push(out, "AST:\n") { ret false; }
var stack = Vec.blank(1, 1);
defer { Vec.drop(&stack); }
ret dump_node(ast, out, &stack, ast.root, true);
}
fn test_kind_names() {
@assert(ByteView.eq(ByteView.from_slice(kind_name(.None)), ByteView.from_slice("none")),
"none name");
@assert(ByteView.eq(ByteView.from_slice(kind_name(.ExprBinary(.{
.op = .Add,
.left = 0,
.right = 0,
}))),
ByteView.from_slice("binary")),
"binary name");
}
fn test_alloc_and_payload() {
var ast: Ast = undefined;
match new(null) {
.Ok(v) => { ast = v; },
.Err(error) => { @assert(false, "ast new failed"); },
}
defer { drop(&ast); }
@assert(count(&ast) == 1, "sentinel only");
var r = alloc(&ast, .ExprIdent(.{ .name = ByteView.from_slice("main") }), span_make(0, 5, 9));
@assert(r == 1, "first alloc is 1");
var n = node(&ast, r);
n.resolved_def_id = 99;
@assert(n.span.file == 0 && n.span.start == 5 && n.span.end == 9, "span stored");
@assert(n.resolved_def_id == 99, "header field");
match n.body {
.ExprIdent(i) => {
@assert(ByteView.eq(i.name, ByteView.from_slice("main")), "payload name");
},
_ => { @assert(false, "expected ident"); },
}
}
fn expect_bytes(buf: *ByteBuffer, expected: []u8) {
var actual = ByteBuffer.as_slice(buf.*);
@assert(actual.len == expected.len, "dump length");
var i: usize = 0;
while i < expected.len {
@assert(actual.ptr[i] == expected.ptr[i], "dump byte");
i = i + 1;
}
}
fn test_dump() {
var ast: Ast = undefined;
match new(null) {
.Ok(v) => { ast = v; },
.Err(error) => { @assert(false, "ast new failed"); },
}
defer { drop(&ast); }
var id_ref = alloc(&ast, .ExprIdent(.{ .name = ByteView.from_slice("main") }), span_none()); // #1
var decls = ref_vec();
match Vec.push(&decls, &id_ref as *u8) {
.Ok => {},
.Err(error) => { @assert(false, "push decl"); },
}
var cm_ref = alloc(&ast, .ContainerMembers(.{
.external_tags = ref_vec(),
.declarations = decls,
}), span_none()); // #2
var root_ref = alloc(&ast, .Root(.{ .members = cm_ref }), span_none()); // #3
ast.root = root_ref;
var buf = ByteBuffer.new();
defer { ByteBuffer.drop(&buf); }
@assert(dump(&ast, &buf), "dump ok");
// sources=null → 不解析行列;无 span 时也不打印。
expect_bytes(&buf, "AST:\n`-root #3\n `-container_members #2\n `-ident #1\n");
}
fn main() {
test_kind_names();
test_alloc_and_payload();
test_dump();
}
+2312
View File
File diff suppressed because it is too large. Load diff
+417
View File
@@ -0,0 +1,417 @@
// Expect retcode = 0
// 属性注册表:内置属性在 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) {
Vec.push(&self.registry, &info as *u8);
}
// 返回注册表下标(>=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));
}
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();
}
+304
View File
@@ -0,0 +1,304 @@
// Expect retcode = 0
// 声明句柄表。DefId 是声明的稳定身份:作用域符号、节点 resolved_def_id、comptime
// 生成的声明都用它;前向/环形引用由 state 状态机表达。owner_ast 为不透明 *any。
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。
fn node(self: *DefTable, id: usize): *DefNode {
if id == 0 || id >= self.table.len { ret null; }
ret (self.table.data as *DefNode) + id;
}
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。
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;
}
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();
}
+778
View File
@@ -0,0 +1,778 @@
// Expect retcode = 0
// 丰富诊断:Severity / 稳定 E 码 / Span / Label / Child,渲染为人类可读或 JSON。
// 位置只存下标,渲染时经 Sources 解析行列;条目拥有其字符串与 labels/children。
type .{ .Error, .Status, .make } = @import("./utils/error.spl");
type .{ .Vec } = @import("./utils/vec.spl");
type .{ .ByteBuffer } = @import("./utils/byte_buffer.spl");
type .{ .Str } = @import("./utils/str.spl");
type .{ .ByteView } = @import("./utils/byte_view.spl");
type .{ .append_usize } = @import("./utils/fmt.spl");
type .{ .Sources, .Source, .set_new, .set_add, .set_at, .set_drop } = @import("./utils/source.spl");
type Severity = enum {
Note,
Help,
Warning,
Error,
Fatal,
}
type Stage = enum {
None,
Lex,
Parse,
Sema,
Lower,
Vm,
Driver,
}
type Code = enum {
None,
// 词法 / 语法
LexUnexpected,
ParseExpected,
ParseUnrecognized,
// sema
UndefinedSymbol,
DuplicateSymbol,
TypeMismatch,
ArgCountMismatch,
NotAFunction,
NoField,
NoMethod,
IndexBaseInvalid,
TupleIndexOob,
FitsOverflow,
CircularDep,
BreakOutsideLoop,
ContinueOutsideLoop,
UndefinedLabel,
CastInvalid,
UnknownAttr,
UnknownIntrin,
IntrinArgCount,
VarUninitialized,
CannotInfer,
MustReturnValue,
// lowering
UnhandledNode,
ImplicitCastFail,
NoLvalue,
BadFieldIndex,
BadBlock,
// vm
IrUnknownKind,
FrameOverflow,
// 通用
Internal,
}
// file 下标不可用时的哨兵。
fn no_file(): usize {
ret ~(0 as usize);
}
type Span = struct {
file: usize,
start: usize,
end: usize,
}
fn span_none(): Span {
ret Span { .file = no_file(), .start = 0, .end = 0 };
}
fn span_make(file: usize, start: usize, end: usize): Span {
ret Span { .file = file, .start = start, .end = end };
}
fn span_is_none(span: Span): bool {
ret span.file == no_file();
}
type Label = struct {
span: Span,
message: Str,
primary: bool,
}
type Child = struct {
severity: Severity,
span: Span,
message: Str,
}
type Item = struct {
severity: Severity,
stage: Stage,
code: Code,
span: Span,
message: Str,
labels: Vec, // Label
children: Vec, // Child
}
type Diag = @self;
items: Vec, // Item
sources: *Sources, // 借用;可为 null(则只输出消息)
errors: usize,
warnings: usize,
fatals: usize,
fn new(sources: *Sources): Diag {
ret Diag {
.items = Vec.blank(@sizeof(Item), @alignof(Item)),
.sources = sources,
.errors = 0,
.warnings = 0,
.fatals = 0,
};
}
fn item_at(self: *Diag, index: usize): *Item {
match Vec.get(&self.items, index) {
.Ok(ptr) => { ret ptr as *Item; },
.Err(error) => { ret null; },
}
ret null;
}
fn label_at(it: *Item, index: usize): *Label {
match Vec.get(&it.labels, index) {
.Ok(ptr) => { ret ptr as *Label; },
.Err(error) => { ret null; },
}
ret null;
}
fn child_at(it: *Item, index: usize): *Child {
match Vec.get(&it.children, index) {
.Ok(ptr) => { ret ptr as *Child; },
.Err(error) => { ret null; },
}
ret null;
}
fn last(self: *Diag): usize {
if self.items.len == 0 { ret no_file(); }
ret self.items.len - 1;
}
fn count(self: Diag): usize { ret self.items.len; }
fn error_count(self: Diag): usize { ret self.errors; }
fn warning_count(self: Diag): usize { ret self.warnings; }
fn fatal_count(self: Diag): usize { ret self.fatals; }
fn has_error(self: Diag): bool { ret self.errors > 0; }
fn has_fatal(self: Diag): bool { ret self.fatals > 0; }
// 追加一条诊断,返回其下标(失败返回 no_file())。span 为主位置。
fn emit(self: *Diag, severity: Severity, stage: Stage, code: Code, span: Span,
msg: []u8): usize {
var message = Str.new();
match Str.from_slice(msg) {
.Ok(v) => { message = v; },
.Err(error) => { ret no_file(); },
}
var item = Item {
.severity = severity,
.stage = stage,
.code = code,
.span = span,
.message = message,
.labels = Vec.blank(@sizeof(Label), @alignof(Label)),
.children = Vec.blank(@sizeof(Child), @alignof(Child)),
};
match Vec.push(&self.items, &item as *u8) {
.Ok => {},
.Err(error) => { Str.drop(&item.message); ret no_file(); },
}
match severity {
.Error => { self.errors = self.errors + 1; },
.Fatal => { self.errors = self.errors + 1; self.fatals = self.fatals + 1; },
.Warning => { self.warnings = self.warnings + 1; },
_ => {},
}
ret self.items.len - 1;
}
fn err(self: *Diag, span: Span, code: Code, msg: []u8): usize {
ret emit(self, .Error, .None, code, span, msg);
}
fn warn(self: *Diag, span: Span, code: Code, msg: []u8): usize {
ret emit(self, .Warning, .None, code, span, msg);
}
fn note(self: *Diag, span: Span, code: Code, msg: []u8): usize {
ret emit(self, .Note, .None, code, span, msg);
}
// 给最近一条诊断附加标签(primary 决定主/次)。
fn attach_label(self: *Diag, span: Span, msg: []u8, primary: bool): Status {
var idx = last(self);
if idx == no_file() { ret .Err(make(.OutOfRange)); }
var it = item_at(self, idx);
if it == null { ret .Err(make(.OutOfRange)); }
var message = Str.new();
match Str.from_slice(msg) {
.Ok(v) => { message = v; },
.Err(error) => { ret .Err(error); },
}
var lab = Label { .span = span, .message = message, .primary = primary };
match Vec.push(&it.labels, &lab as *u8) {
.Ok => {},
.Err(error) => { Str.drop(&lab.message); ret .Err(error); },
}
ret .Ok;
}
// 给最近一条诊断附加子诊断(note/help 等)。
fn attach_child(self: *Diag, severity: Severity, span: Span, msg: []u8): Status {
var idx = last(self);
if idx == no_file() { ret .Err(make(.OutOfRange)); }
var it = item_at(self, idx);
if it == null { ret .Err(make(.OutOfRange)); }
var message = Str.new();
match Str.from_slice(msg) {
.Ok(v) => { message = v; },
.Err(error) => { ret .Err(error); },
}
var ch = Child { .severity = severity, .span = span, .message = message };
match Vec.push(&it.children, &ch as *u8) {
.Ok => {},
.Err(error) => { Str.drop(&ch.message); ret .Err(error); },
}
ret .Ok;
}
fn severity_name(severity: Severity): []u8 {
match severity {
.Note => { ret "note"; },
.Help => { ret "help"; },
.Warning => { ret "warning"; },
.Error => { ret "error"; },
.Fatal => { ret "fatal"; },
}
ret "?";
}
fn stage_name(stage: Stage): []u8 {
match stage {
.None => { ret "none"; },
.Lex => { ret "lex"; },
.Parse => { ret "parse"; },
.Sema => { ret "sema"; },
.Lower => { ret "lower"; },
.Vm => { ret "vm"; },
.Driver => { ret "driver"; },
}
ret "?";
}
// 稳定 E 码(diff 依赖;新增 Code 时分配新码,勿改旧值)。
fn code_str(code: Code): []u8 {
match code {
.None => { ret "E0000"; },
.LexUnexpected => { ret "E0001"; },
.ParseExpected => { ret "E0002"; },
.ParseUnrecognized => { ret "E0003"; },
.UndefinedSymbol => { ret "E0101"; },
.DuplicateSymbol => { ret "E0102"; },
.TypeMismatch => { ret "E0103"; },
.ArgCountMismatch => { ret "E0104"; },
.NotAFunction => { ret "E0105"; },
.NoField => { ret "E0106"; },
.NoMethod => { ret "E0107"; },
.IndexBaseInvalid => { ret "E0108"; },
.TupleIndexOob => { ret "E0109"; },
.FitsOverflow => { ret "E0110"; },
.CircularDep => { ret "E0111"; },
.BreakOutsideLoop => { ret "E0112"; },
.ContinueOutsideLoop => { ret "E0113"; },
.UndefinedLabel => { ret "E0114"; },
.CastInvalid => { ret "E0115"; },
.UnknownAttr => { ret "E0116"; },
.UnknownIntrin => { ret "E0117"; },
.IntrinArgCount => { ret "E0118"; },
.VarUninitialized => { ret "E0119"; },
.CannotInfer => { ret "E0120"; },
.MustReturnValue => { ret "E0121"; },
.UnhandledNode => { ret "E0201"; },
.ImplicitCastFail => { ret "E0202"; },
.NoLvalue => { ret "E0203"; },
.BadFieldIndex => { ret "E0204"; },
.BadBlock => { ret "E0205"; },
.IrUnknownKind => { ret "E0301"; },
.FrameOverflow => { ret "E0302"; },
.Internal => { ret "E9000"; },
}
ret "E0000";
}
fn code_name(code: Code): []u8 {
match code {
.None => { ret "none"; },
.LexUnexpected => { ret "lex_unexpected"; },
.ParseExpected => { ret "parse_expected"; },
.ParseUnrecognized => { ret "parse_unrecognized"; },
.UndefinedSymbol => { ret "undefined_symbol"; },
.DuplicateSymbol => { ret "duplicate_symbol"; },
.TypeMismatch => { ret "type_mismatch"; },
.ArgCountMismatch => { ret "arg_count_mismatch"; },
.NotAFunction => { ret "not_a_function"; },
.NoField => { ret "no_field"; },
.NoMethod => { ret "no_method"; },
.IndexBaseInvalid => { ret "index_base_invalid"; },
.TupleIndexOob => { ret "tuple_index_oob"; },
.FitsOverflow => { ret "fits_overflow"; },
.CircularDep => { ret "circular_dep"; },
.BreakOutsideLoop => { ret "break_outside_loop"; },
.ContinueOutsideLoop => { ret "continue_outside_loop"; },
.UndefinedLabel => { ret "undefined_label"; },
.CastInvalid => { ret "cast_invalid"; },
.UnknownAttr => { ret "unknown_attr"; },
.UnknownIntrin => { ret "unknown_intrin"; },
.IntrinArgCount => { ret "intrin_arg_count"; },
.VarUninitialized => { ret "var_uninitialized"; },
.CannotInfer => { ret "cannot_infer"; },
.MustReturnValue => { ret "must_return_value"; },
.UnhandledNode => { ret "unhandled_node"; },
.ImplicitCastFail => { ret "implicit_cast_fail"; },
.NoLvalue => { ret "no_lvalue"; },
.BadFieldIndex => { ret "bad_field_index"; },
.BadBlock => { ret "bad_block"; },
.IrUnknownKind => { ret "ir_unknown_kind"; },
.FrameOverflow => { ret "frame_overflow"; },
.Internal => { ret "internal"; },
}
ret "none";
}
fn append_num(out: *ByteBuffer, value: usize): Status {
ret append_usize(out, value, 10, false);
}
fn append_str(out: *ByteBuffer, s: *Str): Status {
ret ByteBuffer.append(out, Str.as_slice(s));
}
fn append_view(out: *ByteBuffer, view: ByteView): Status {
ret ByteBuffer.append(out, ByteView.as_slice(view));
}
type Resolved = struct {
ok: bool,
fname: ByteView,
line: usize,
col: usize,
text: ByteView,
width: usize,
}
fn resolve(self: *Diag, span: Span): Resolved {
var r = Resolved {
.ok = false,
.fname = ByteView.empty(),
.line = 0,
.col = 0,
.text = ByteView.empty(),
.width = 1,
};
if self.sources == null { ret r; }
if span_is_none(span) { ret r; }
var src = set_at(self.sources, span.file);
if src == null { ret r; }
var pos = Source.line_col(src.*, span.start);
var width: usize = 1;
if span.end > span.start { width = span.end - span.start; }
r.ok = true;
r.fname = src.fname;
r.line = pos.line;
r.col = pos.col;
r.text = Source.line_text(src.*, pos.line);
r.width = width;
ret r;
}
fn render_header(it: *Item, r: *Resolved, out: *ByteBuffer): Status {
if r.ok {
match append_view(out, r.fname) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_num(out, r.line) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_num(out, r.col) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
match ByteBuffer.append(out, severity_name(it.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append_byte(out, '[') { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, code_str(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, "]: ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_str(out, &it.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
ret ByteBuffer.append_byte(out, '\n');
}
// 输出一行源码 + caret 下划线(列范围按字节;制表符不特殊处理)。
fn render_span_visual(self: *Diag, span: Span, out: *ByteBuffer): Status {
var r = resolve(self, span);
if !r.ok { ret .Ok; }
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_view(out, r.text) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
var pad: usize = 0;
if r.col > 0 { pad = r.col - 1; }
match ByteBuffer.append_repeat(out, ' ', pad) { .Ok => {}, .Err(e) => { ret .Err(e); } }
var width = r.width;
if width == 0 { width = 1; }
if r.col > 0 {
var room: usize = 0;
if r.text.len >= pad { room = r.text.len - pad; }
if room == 0 { room = 1; }
if width > room { width = room; }
}
var k: usize = 0;
while k < width {
var ch: u8 = '~';
if k == 0 { ch = '^'; }
match ByteBuffer.append_byte(out, ch) { .Ok => {}, .Err(e) => { ret .Err(e); } }
k = k + 1;
}
ret ByteBuffer.append_byte(out, '\n');
}
fn render_child_line(ch: *Child, out: *ByteBuffer): Status {
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, severity_name(ch.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_str(out, &ch.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
ret ByteBuffer.append_byte(out, '\n');
}
fn render_human(self: *Diag, out: *ByteBuffer): Status {
var i: usize = 0;
while i < self.items.len {
var it = item_at(self, i);
if it == null { ret .Err(make(.OutOfRange)); }
var r = resolve(self, it.span);
match render_header(it, &r, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match render_span_visual(self, it.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
var j: usize = 0;
while j < it.labels.len {
var lab = label_at(it, j);
if lab != null {
match render_span_visual(self, lab.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
if Str.size(lab.message) != 0 {
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_str(out, &lab.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
}
j = j + 1;
}
var c: usize = 0;
while c < it.children.len {
var ch = child_at(it, c);
if ch != null {
match render_child_line(ch, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match render_span_visual(self, ch.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
c = c + 1;
}
if i + 1 < self.items.len {
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
i = i + 1;
}
ret .Ok;
}
fn append_json_hex4(out: *ByteBuffer, b: u8): Status {
var hex: []u8 = "0123456789abcdef";
match ByteBuffer.append(out, "\\u00") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append_byte(out, hex[(b >> 4) as usize]) { .Ok => {}, .Err(e) => { ret .Err(e); } }
ret ByteBuffer.append_byte(out, hex[(b & 15) as usize]);
}
fn append_json_str(out: *ByteBuffer, bytes: []u8): Status {
match ByteBuffer.append_byte(out, '"') { .Ok => {}, .Err(e) => { ret .Err(e); } }
var i: usize = 0;
while i < bytes.len {
var b = bytes[i];
if b == '"' {
match ByteBuffer.append(out, "\\\"") { .Ok => {}, .Err(e) => { ret .Err(e); } }
} else if b == '\\' {
match ByteBuffer.append(out, "\\\\") { .Ok => {}, .Err(e) => { ret .Err(e); } }
} else if b == '\n' {
match ByteBuffer.append(out, "\\n") { .Ok => {}, .Err(e) => { ret .Err(e); } }
} else if b == '\r' {
match ByteBuffer.append(out, "\\r") { .Ok => {}, .Err(e) => { ret .Err(e); } }
} else if b == '\t' {
match ByteBuffer.append(out, "\\t") { .Ok => {}, .Err(e) => { ret .Err(e); } }
} else if b < ' ' {
match append_json_hex4(out, b) { .Ok => {}, .Err(e) => { ret .Err(e); } }
} else {
match ByteBuffer.append_byte(out, b) { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
i = i + 1;
}
ret ByteBuffer.append_byte(out, '"');
}
fn append_str_json(out: *ByteBuffer, s: *Str): Status {
ret append_json_str(out, Str.as_slice(s));
}
fn append_bool(out: *ByteBuffer, value: bool): Status {
if value { ret ByteBuffer.append(out, "true"); }
ret ByteBuffer.append(out, "false");
}
fn append_loc_json(self: *Diag, span: Span, out: *ByteBuffer): Status {
var r = resolve(self, span);
match ByteBuffer.append(out, "{\"file\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
if r.ok {
match append_json_str(out, ByteView.as_slice(r.fname)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
} else {
match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
match ByteBuffer.append(out, ",\"start\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_num(out, span.start) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"end\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_num(out, span.end) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"line\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
if r.ok {
match append_num(out, r.line) { .Ok => {}, .Err(e) => { ret .Err(e); } }
} else {
match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
match ByteBuffer.append(out, ",\"col\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
if r.ok {
match append_num(out, r.col) { .Ok => {}, .Err(e) => { ret .Err(e); } }
} else {
match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
ret ByteBuffer.append_byte(out, '}');
}
fn render_item_json(self: *Diag, it: *Item, out: *ByteBuffer): Status {
match ByteBuffer.append(out, "{\"severity\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_json_str(out, severity_name(it.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"stage\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_json_str(out, stage_name(it.stage)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"code\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_json_str(out, code_str(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"name\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_json_str(out, code_name(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_str_json(out, &it.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_loc_json(self, it.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"labels\":[") { .Ok => {}, .Err(e) => { ret .Err(e); } }
var j: usize = 0;
while j < it.labels.len {
var lab = label_at(it, j);
if lab != null {
if j != 0 {
match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
match ByteBuffer.append(out, "{\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_str_json(out, &lab.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"primary\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_bool(out, lab.primary) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_loc_json(self, lab.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append_byte(out, '}') { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
j = j + 1;
}
match ByteBuffer.append_byte(out, ']') { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"children\":[") { .Ok => {}, .Err(e) => { ret .Err(e); } }
var c: usize = 0;
while c < it.children.len {
var ch = child_at(it, c);
if ch != null {
if c != 0 {
match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
match ByteBuffer.append(out, "{\"severity\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_json_str(out, severity_name(ch.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_str_json(out, &ch.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_loc_json(self, ch.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append_byte(out, '}') { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
c = c + 1;
}
match ByteBuffer.append_byte(out, ']') { .Ok => {}, .Err(e) => { ret .Err(e); } }
ret ByteBuffer.append_byte(out, '}');
}
fn render_json(self: *Diag, out: *ByteBuffer): Status {
match ByteBuffer.append_byte(out, '[') { .Ok => {}, .Err(e) => { ret .Err(e); } }
var i: usize = 0;
while i < self.items.len {
var it = item_at(self, i);
if it == null { ret .Err(make(.OutOfRange)); }
if i != 0 {
match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } }
}
match render_item_json(self, it, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
i = i + 1;
}
ret ByteBuffer.append_byte(out, ']');
}
fn drop_item(it: *Item) {
Str.drop(&it.message);
var j: usize = 0;
while j < it.labels.len {
var lab = label_at(it, j);
if lab != null { Str.drop(&lab.message); }
j = j + 1;
}
Vec.drop(&it.labels);
var c: usize = 0;
while c < it.children.len {
var ch = child_at(it, c);
if ch != null { Str.drop(&ch.message); }
c = c + 1;
}
Vec.drop(&it.children);
}
fn clear(self: *Diag) {
var i: usize = 0;
while i < self.items.len {
var it = item_at(self, i);
if it != null { drop_item(it); }
i = i + 1;
}
Vec.clear(&self.items);
self.errors = 0;
self.warnings = 0;
self.fatals = 0;
}
fn drop(self: *Diag) {
clear(self);
Vec.drop(&self.items);
self.sources = null;
}
fn view(text: []u8): ByteView {
ret ByteView.from_slice(text);
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected diag error"); },
}
}
fn has_bytes(out: *ByteBuffer, needle: []u8): bool {
ret ByteView.contains(ByteView.from_slice(ByteBuffer.as_slice(out.*)), view(needle));
}
fn test_collect_render_human() {
var sources = set_new();
match set_add(&sources, view("a.spl"), view("fn main() {\n y;\n}\n")) {
.Ok(index) => {},
.Err(error) => { @assert(false, "add source"); },
}
var d = new(&sources);
var idx = err(&d, span_make(0, 16, 17), .UndefinedSymbol, "undefined symbol `y`");
@assert(idx != no_file(), "emit");
expect_ok(attach_label(&d, span_make(0, 16, 17), "not found in scope", false));
expect_ok(attach_child(&d, .Note, span_none(), "did you mean `x`?"));
@assert(count(d) == 1, "count");
@assert(error_count(d) == 1 && has_error(d), "errors");
@assert(warning_count(d) == 0 && !has_fatal(d), "warnings/fatal");
var out = ByteBuffer.new();
expect_ok(render_human(&d, &out));
@assert(has_bytes(&out, "a.spl:2:5: error[E0101]: undefined symbol `y`"), "human header");
@assert(has_bytes(&out, " y;"), "human source line");
@assert(has_bytes(&out, " ^"), "human caret");
@assert(has_bytes(&out, "not found in scope"), "human label");
@assert(has_bytes(&out, "note: did you mean `x`?"), "human child");
ByteBuffer.drop(&out);
clear(&d);
@assert(count(d) == 0 && error_count(d) == 0, "clear");
drop(&d);
set_drop(&sources);
}
fn test_render_json() {
var sources = set_new();
match set_add(&sources, view("b.spl"), view("let z = 1;\n")) {
.Ok(index) => {},
.Err(error) => { @assert(false, "add source"); },
}
var d = new(&sources);
err(&d, span_make(0, 4, 5), .TypeMismatch, "expected i32, got bool");
expect_ok(attach_child(&d, .Help, span_none(), "annotate the type"));
var out = ByteBuffer.new();
expect_ok(render_json(&d, &out));
@assert(has_bytes(&out, "\"severity\":\"error\""), "json severity");
@assert(has_bytes(&out, "\"code\":\"E0103\""), "json code");
@assert(has_bytes(&out, "\"name\":\"type_mismatch\""), "json name");
@assert(has_bytes(&out, "\"line\":1"), "json line");
@assert(has_bytes(&out, "\"col\":5"), "json col");
@assert(has_bytes(&out, "\"children\":[{\"severity\":\"help\""), "json child");
ByteBuffer.drop(&out);
drop(&d);
set_drop(&sources);
}
fn test_empty_and_headerless() {
var d = new(null);
err(&d, span_none(), .Internal, "no location");
var out = ByteBuffer.new();
expect_ok(render_human(&d, &out));
@assert(has_bytes(&out, "error[E9000]: no location"), "headerless");
ByteBuffer.drop(&out);
drop(&d);
}
fn main() {
test_collect_render_human();
test_render_json();
test_empty_and_headerless();
}
+130
View File
@@ -0,0 +1,130 @@
// Expect retcode = 0
// comptime/const 求值的接口 seam:sema 只在此调用,未实现时返回 NotConst(放弃折叠、
// 不报错)。编译期生成的声明经 DeclSink 登记,不改 sema 表。
type .{ .Error } = @import("utils/error.spl");
type .{ .Vec } = @import("utils/vec.spl");
type Ast = @import("./ast.spl");
type Diag = @import("./diag.spl");
// 编译期值(当前仅标量)。
type EvalValue = enum {
Int: usize,
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 = 完全不求值
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;
}
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 块;当前不实现,直接 NotConst。
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();
}
+326
View File
@@ -0,0 +1,326 @@
// 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();
}
+610
View File
@@ -0,0 +1,610 @@
// Expect retcode = 0
// SIR 中间表示:节点用 arena 序号引用(0=error),按函数分桶;公共字段在 IrEntry,
// kind 专属载荷在 IrBody ADT(kind 与载荷一一对应)。builder 负责发射与跳转回填。
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,
Global: usize, // 全局变量下标(Ir.globals);值 = 指向该全局的 *T
}
// 模块级全局变量:编译期分配的静态存储,引用时是一个 *T 指针常量(同 LLVM GlobalVariable)。
type Global = struct {
tid: usize,
zero: bool, // true = 零初始化(BSS,不携带字节)
data: Vec, // u8 初始字节(zero=false 时使用)
}
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
globals: Vec, // Global
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)),
.globals = Vec.blank(@sizeof(Global), @alignof(Global)) };
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();
Vec.push(&sentinel.nodes, &sn as *u8);
Vec.push(&ir.funcs, &sentinel as *u8);
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 {
var f = (self.funcs.data as *IrFunc) + i;
var j: usize = 0;
while j < f.nodes.len {
var e = (f.nodes.data as *IrEntry) + j;
Vec.drop(&e.attrs);
drop_body(e.body);
j = j + 1;
}
Vec.drop(&f.attrs);
Vec.drop(&f.nodes);
i = i + 1;
}
Vec.drop(&self.funcs);
var g: usize = 0;
while g < self.globals.len {
var ge = (self.globals.data as *Global) + g;
Vec.drop(&ge.data);
g = g + 1;
}
Vec.drop(&self.globals);
}
// 追加一个全局变量;返回其下标(gidx)。bytes 的所有权转移进表。
fn add_global(self: *Ir, tid: usize, zero: bool, bytes: Vec): usize {
var g = Global { .tid = tid, .zero = zero, .data = bytes };
match Vec.push(&self.globals, &g as *u8) { .Ok => {}, .Err(error) => { ret 0; } }
ret self.globals.len - 1;
}
fn global_at(self: *Ir, gidx: usize): *Global {
if gidx >= self.globals.len { ret null; }
ret (self.globals.data as *Global) + gidx;
}
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; }
var f = (self.funcs.data as *IrFunc) + fn_id;
var n = new_entry();
match Vec.push(&f.nodes, &n as *u8) { .Ok => {}, .Err(error) => { ret 0; } }
ret f.nodes.len - 1;
}
fn func(self: *Ir, fn_id: usize): *IrFunc {
if fn_id == 0 || fn_id >= self.funcs.len { ret null; }
ret (self.funcs.data as *IrFunc) + fn_id;
}
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; }
ret (f.nodes.data as *IrEntry) + node_id;
}
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 "?";
}
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 {
if i >= v.len { ret 0; }
ret ((v.data as *usize) + i).*;
}
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。
match e.kind {
.ArithNeg, .ArithAbs, .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; }
match c.lit {
.Global(g) => {
if !out_str(out, " global#") { ret false; }
ret out_u(out, g);
},
_ => {},
}
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, "'");
}
// 其他常量统一按 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();
}
+1320
View File
File diff suppressed because it is too large. Load diff
+1405
View File
File diff suppressed because it is too large. Load diff
+475
View File
@@ -0,0 +1,475 @@
// Expect retcode = 0
// IR 构建器:单独成文件,以免 ast2ir 在同文件 @self 嵌套时无限递归。
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 {
if i >= v.len { ret 0; }
ret ((v.data as *usize) + i).*;
}
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 {
var slot = (self.blocks.data as *usize) + self.current_block;
if slot.* == 0 { slot.* = ref; }
}
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 {
var fx = (self.fixups.data as *Fix) + k;
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 {
match fx.slot {
.BrTrue => { match e.body { .Br(&b) => { b.true_label = target; }, _ => {} } },
.BrFalse => { match e.body { .Br(&b) => { b.false_label = target; }, _ => {} } },
.Jmp => { match e.body { .Jmp(&j) => { j.label = target; }, _ => {} } },
}
}
}
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;
Vec.push(&self.blocks, &zero as *u8);
var block = self.blocks.len - 1;
self.current_block = block;
self.terminated = false;
ret block;
}
fn block_end(self: *IrBuilder) {
// 空实现:本实现不做 terminated/空块校验(不终止进程)。
}
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); }
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;
}
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)); }
fn type_const_global(self: *IrBuilder, tid: usize, gidx: usize): usize { ret const_node(self, tid, .Global(gidx)); }
// 追加模块级全局变量;返回 gidx。bytes 所有权转移进 Ir。
fn add_global(self: *IrBuilder, tid: usize, zero: bool, bytes: Vec): usize {
ret Ir.add_global(&self.ir, tid, zero, bytes);
}
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;
}
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) => { Vec.push(&a.fields, &field as *u8); },
_ => {},
}
}
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;
}
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 };
Vec.push(&self.fixups, &f1 as *u8);
var f2 = Fix { .node = ref, .slot = .BrFalse, .block = false_block };
Vec.push(&self.fixups, &f2 as *u8);
}
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 };
Vec.push(&self.fixups, &fx as *u8);
}
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) => { Vec.push(&c.params, &arg as *u8); },
_ => {},
}
}
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();
}
+1162
View File
File diff suppressed because it is too large. Load diff
+349
View File
@@ -0,0 +1,349 @@
// Expect retcode = 0
// 目标布局:由 IR 类型表计算 size/align/字段位偏移,供 ir2vm 使用。默认 64 位平台,
// 聚合按最大字段对齐且不重排。
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; }
ret (self.infos.data as *LayoutInfo) + id;
}
// 未解析或越界返回 null。
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) {
Vec.push(v, &x as *u8);
}
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;
}
+711
View File
@@ -0,0 +1,711 @@
// Expect retcode = 0
// 字节流 -> Vec<Token>。行为镜像 splc0(保证 diff):行注释作为 token 产出,块注释
//(可嵌套)跳过,换行产 Endline,EOF 产 Eof。Token.text 借用输入,不复制。
type .{ .Vec, .VecResult } = @import("utils/vec.spl");
type .{ .ByteView } = @import("utils/byte_view.spl");
type .{ .is_digit, .is_alpha, .is_alnum, .is_hex_digit, .digit_value } = @import("utils/ascii.spl");
type .{
.Token, .TokenKind, .Radix, .Keyword, .Punct, .keyword_of
} = @import("token.spl");
type .{ .Span, .span_make } = @import("./diag.spl");
type Diag = @import("./diag.spl");
type Lexer = @self;
src: ByteView,
pos: usize,
line: usize,
col: usize,
diag: *Diag, // 借用;可为 null(合法输入不产生诊断)
file: usize,
fn new(src: ByteView, diag: *Diag, file: usize): Lexer {
ret Lexer { .src = src, .pos = 0, .line = 1, .col = 1, .diag = diag, .file = file };
}
// 记录一条词法诊断(非法输入路径)。合法输入不应触发。
fn report(self: *Lexer, start: usize, end: usize, message: []u8) {
if self.diag == null { ret; }
Diag.err(self.diag, span_make(self.file, start, end), .LexUnexpected, message);
}
fn is_ident_start(c: u8): bool { ret is_alpha(c) || c == '_'; }
fn is_ident_cont(c: u8): bool { ret is_alnum(c) || c == '_'; }
fn hex_val(c: u8): u8 {
match digit_value(c, 16) {
.Ok(v) => { ret v; },
.Err(error) => { ret 0; },
}
ret 0;
}
// 文本形如 'a' / '\n' / '\x41';返回解码后的字节。
fn decode_char(text: ByteView): u8 {
var start: usize = 1;
var end = text.len;
if text.len >= 2 && text.ptr[text.len - 1] == '\'' { end = text.len - 1; }
if end <= start { ret 0; }
var b = text.ptr[start];
if b != '\\' { ret b; }
start = start + 1;
if start >= end { ret 0; }
var e = text.ptr[start];
if e == 'n' { ret '\n'; }
if e == 't' { ret '\t'; }
if e == 'r' { ret '\r'; }
if e == '\\' { ret '\\'; }
if e == '"' { ret '"'; }
if e == '\'' { ret '\''; }
if e == '0' { ret 0; }
if e == 'x' || e == 'X' {
var q = start + 1;
var value: u8 = 0;
var digits: usize = 0;
while q < end && digits < 2 && is_hex_digit(text.ptr[q]) {
value = value * 16 + hex_val(text.ptr[q]);
q = q + 1;
digits = digits + 1;
}
if digits == 0 { ret e; }
ret value;
}
ret e;
}
fn has2(self: *Lexer, ch: u8): bool {
ret self.pos + 1 < self.src.len && self.src.ptr[self.pos + 1] == ch;
}
fn has3(self: *Lexer, ch1: u8, ch2: u8): bool {
ret self.pos + 2 < self.src.len && self.src.ptr[self.pos + 1] == ch1 &&
self.src.ptr[self.pos + 2] == ch2;
}
fn sub(self: *Lexer, start: usize, end: usize): ByteView {
if start >= end { ret ByteView.empty(); }
ret ByteView { .ptr = self.src.ptr + start, .len = end - start };
}
// 产出 token 并把 pos/col 推进到 end。Vec.push 失败视为致命(同 vec.make_vec 风格)。
fn emit_to(self: *Lexer, out: *Vec, kind: TokenKind, start: usize, end: usize, line: usize,
col: usize) {
var t = Token.make(kind, sub(self, start, end), line, col, start);
match Vec.push(out, &t as *u8) {
.Ok => {},
.Err(error) => { @assert(false, "lexer: Vec.push failed"); },
}
self.pos = end;
self.col = col + (end - start);
}
fn op1(self: *Lexer, out: *Vec, punct: Punct, start: usize, line: usize, col: usize) {
emit_to(self, out, .Punct(punct), start, start + 1, line, col);
}
fn op2(self: *Lexer, out: *Vec, punct: Punct, start: usize, line: usize, col: usize) {
emit_to(self, out, .Punct(punct), start, start + 2, line, col);
}
fn op3(self: *Lexer, out: *Vec, punct: Punct, start: usize, line: usize, col: usize) {
emit_to(self, out, .Punct(punct), start, start + 3, line, col);
}
fn scan_int(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
var p = self.pos;
if self.src.ptr[p] == '0' && p + 1 < self.src.len {
var nc = self.src.ptr[p + 1];
if nc == 'x' || nc == 'X' {
p = p + 2;
while p < self.src.len && (is_hex_digit(self.src.ptr[p]) || self.src.ptr[p] == '_') {
p = p + 1;
}
emit_to(self, out, .Int(.Hex), start, p, line, col);
ret;
}
if nc == 'b' || nc == 'B' {
p = p + 2;
while p < self.src.len && (self.src.ptr[p] == '0' || self.src.ptr[p] == '1' ||
self.src.ptr[p] == '_') {
p = p + 1;
}
emit_to(self, out, .Int(.Bin), start, p, line, col);
ret;
}
if nc == 'o' || nc == 'O' {
p = p + 2;
while p < self.src.len &&
((self.src.ptr[p] >= '0' && self.src.ptr[p] <= '7') ||
self.src.ptr[p] == '_') {
p = p + 1;
}
emit_to(self, out, .Int(.Oct), start, p, line, col);
ret;
}
}
while p < self.src.len && (is_digit(self.src.ptr[p]) || self.src.ptr[p] == '_') { p = p + 1; }
if p + 1 < self.src.len && self.src.ptr[p] == '.' && is_digit(self.src.ptr[p + 1]) {
p = p + 1;
// 小数部分允许下划线分隔(Rust 风格)。
while p < self.src.len && (is_digit(self.src.ptr[p]) || self.src.ptr[p] == '_') {
p = p + 1;
}
// 可选指数:e/E [+-] 数字(数字允许下划线);仅当确有数字时纳入。
if p < self.src.len && (self.src.ptr[p] == 'e' || self.src.ptr[p] == 'E') {
var q = p + 1;
if q < self.src.len && (self.src.ptr[q] == '+' || self.src.ptr[q] == '-') { q = q + 1; }
if q < self.src.len && is_digit(self.src.ptr[q]) {
p = q + 1;
while p < self.src.len && (is_digit(self.src.ptr[p]) || self.src.ptr[p] == '_') {
p = p + 1;
}
}
}
emit_to(self, out, .Float, start, p, line, col);
ret;
}
emit_to(self, out, .Int(.Dec), start, p, line, col);
}
fn scan_char(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
var p = self.pos + 1;
var closed = false;
var escape_ok = true;
if p < self.src.len {
if self.src.ptr[p] == '\\' {
p = p + 1;
if p < self.src.len && (self.src.ptr[p] == 'x' || self.src.ptr[p] == 'X') {
p = p + 1;
var digits: usize = 0;
while p < self.src.len && digits < 2 && is_hex_digit(self.src.ptr[p]) {
p = p + 1;
digits = digits + 1;
}
if digits == 0 { escape_ok = false; }
} else if p < self.src.len {
var e = self.src.ptr[p];
if !(e == 'n' || e == 't' || e == 'r' || e == '\\' || e == '"' || e == '\'' ||
e == '0') {
escape_ok = false;
}
p = p + 1;
} else {
escape_ok = false;
}
} else {
p = p + 1;
}
}
if p < self.src.len && self.src.ptr[p] == '\'' {
p = p + 1;
closed = true;
}
if !closed {
report(self, start, p, "unterminated character literal");
} else if !escape_ok {
report(self, start, p, "invalid escape in character literal");
}
emit_to(self, out, .Char(decode_char(sub(self, start, p))), start, p, line, col);
}
fn scan_string(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
var p = self.pos + 1;
var l = line;
var cc = col + 1;
var closed = false;
while p < self.src.len {
var b = self.src.ptr[p];
if b == '\\' {
p = p + 1;
cc = cc + 1;
if p < self.src.len {
p = p + 1;
cc = cc + 1;
}
} else if b == '"' {
p = p + 1;
cc = cc + 1;
closed = true;
break;
} else if b == '\n' {
l = l + 1;
cc = 1;
p = p + 1;
} else {
p = p + 1;
cc = cc + 1;
}
}
if !closed { report(self, start, p, "unterminated string literal"); }
emit_to(self, out, .Str, start, p, line, col);
self.line = l;
self.col = cc;
}
fn scan_ident(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
var p = self.pos;
while p < self.src.len && is_ident_cont(self.src.ptr[p]) { p = p + 1; }
var text = sub(self, start, p);
match keyword_of(text) {
.Yes(kw) => { emit_to(self, out, .Keyword(kw), start, p, line, col); },
.No => { emit_to(self, out, .Ident, start, p, line, col); },
}
}
// 块注释:可嵌套,整体跳过(不产出 token)。未闭合时报诊断。
fn skip_block_comment(self: *Lexer, start: usize) {
self.pos = self.pos + 2;
self.col = self.col + 2;
var depth: usize = 1;
while self.pos + 1 < self.src.len && depth > 0 {
if self.src.ptr[self.pos] == '*' && self.src.ptr[self.pos + 1] == '/' {
depth = depth - 1;
self.pos = self.pos + 2;
self.col = self.col + 2;
} else if self.src.ptr[self.pos] == '/' && self.src.ptr[self.pos + 1] == '*' {
depth = depth + 1;
self.pos = self.pos + 2;
self.col = self.col + 2;
} else {
if self.src.ptr[self.pos] == '\n' {
self.line = self.line + 1;
self.col = 1;
} else {
self.col = self.col + 1;
}
self.pos = self.pos + 1;
}
}
if depth > 0 {
report(self, start, self.src.len, "unterminated block comment");
}
}
fn run(self: *Lexer, out: *Vec) {
while self.pos < self.src.len {
var start = self.pos;
var line = self.line;
var col = self.col;
var c = self.src.ptr[self.pos];
if c == ' ' || c == '\t' || c == '\r' {
self.pos = self.pos + 1;
self.col = self.col + 1;
continue;
}
if c == '\n' {
emit_to(self, out, .Endline, start, start + 1, line, col);
self.line = line + 1;
self.col = 1;
continue;
}
if c == '/' && has2(self, '/') {
var p = self.pos;
while p < self.src.len && self.src.ptr[p] != '\n' { p = p + 1; }
emit_to(self, out, .LineComment, start, p, line, col);
continue;
}
if c == '/' && has2(self, '*') {
skip_block_comment(self, start);
continue;
}
if c == '\'' {
scan_char(self, out, start, line, col);
continue;
}
if c == '"' {
scan_string(self, out, start, line, col);
continue;
}
if is_ident_start(c) {
scan_ident(self, out, start, line, col);
continue;
}
if is_digit(c) {
scan_int(self, out, start, line, col);
continue;
}
if c == '<' {
if has3(self, '-', '>') { op3(self, out, .ArrowBoth, start, line, col); continue; }
if has3(self, '=', '>') { op3(self, out, .FatArrowBoth, start, line, col); continue; }
if has3(self, '<', '=') { op3(self, out, .LShAssign, start, line, col); continue; }
if has2(self, '<') { op2(self, out, .LSh, start, line, col); continue; }
if has2(self, '-') { op2(self, out, .LeftArrow, start, line, col); continue; }
if has2(self, '=') { op2(self, out, .Le, start, line, col); continue; }
op1(self, out, .Lt, start, line, col);
continue;
}
if c == '>' {
if has3(self, '>', '=') { op3(self, out, .RShAssign, start, line, col); continue; }
if has2(self, '>') { op2(self, out, .RSh, start, line, col); continue; }
if has2(self, '=') { op2(self, out, .Ge, start, line, col); continue; }
op1(self, out, .Gt, start, line, col);
continue;
}
if c == '.' {
if has3(self, '.', '.') { op3(self, out, .Ellipsis, start, line, col); continue; }
if has2(self, '.') { op2(self, out, .Range, start, line, col); continue; }
op1(self, out, .Dot, start, line, col);
continue;
}
if c == '=' {
if has2(self, '=') { op2(self, out, .Eq, start, line, col); continue; }
if has2(self, '>') { op2(self, out, .RightFatArrow, start, line, col); continue; }
op1(self, out, .Assign, start, line, col);
continue;
}
if c == '!' {
if has2(self, '=') { op2(self, out, .Neq, start, line, col); continue; }
op1(self, out, .Not, start, line, col);
continue;
}
if c == '&' {
if has2(self, '&') { op2(self, out, .AndAnd, start, line, col); continue; }
if has2(self, '=') { op2(self, out, .AndAssign, start, line, col); continue; }
op1(self, out, .And, start, line, col);
continue;
}
if c == '|' {
if has2(self, '|') { op2(self, out, .OrOr, start, line, col); continue; }
if has2(self, '=') { op2(self, out, .OrAssign, start, line, col); continue; }
op1(self, out, .Or, start, line, col);
continue;
}
if c == '+' {
if has2(self, '=') { op2(self, out, .AddAssign, start, line, col); continue; }
op1(self, out, .Add, start, line, col);
continue;
}
if c == '-' {
if has2(self, '=') { op2(self, out, .SubAssign, start, line, col); continue; }
if has2(self, '>') { op2(self, out, .RightArrow, start, line, col); continue; }
op1(self, out, .Sub, start, line, col);
continue;
}
if c == '*' {
if has2(self, '=') { op2(self, out, .MulAssign, start, line, col); continue; }
op1(self, out, .Mul, start, line, col);
continue;
}
if c == '/' {
if has2(self, '=') { op2(self, out, .DivAssign, start, line, col); continue; }
op1(self, out, .Div, start, line, col);
continue;
}
if c == '%' {
if has2(self, '=') { op2(self, out, .ModAssign, start, line, col); continue; }
op1(self, out, .Mod, start, line, col);
continue;
}
if c == '^' {
if has2(self, '=') { op2(self, out, .XorAssign, start, line, col); continue; }
op1(self, out, .Xor, start, line, col);
continue;
}
if c == ':' {
if has2(self, '=') { op2(self, out, .ColonAssign, start, line, col); continue; }
op1(self, out, .Colon, start, line, col);
continue;
}
if c == '~' { op1(self, out, .BitNot, start, line, col); continue; }
if c == ',' { op1(self, out, .Comma, start, line, col); continue; }
if c == ';' { op1(self, out, .Semicolon, start, line, col); continue; }
if c == '(' { op1(self, out, .LParen, start, line, col); continue; }
if c == ')' { op1(self, out, .RParen, start, line, col); continue; }
if c == '[' { op1(self, out, .LBracket, start, line, col); continue; }
if c == ']' { op1(self, out, .RBracket, start, line, col); continue; }
if c == '{' { op1(self, out, .LBrace, start, line, col); continue; }
if c == '}' { op1(self, out, .RBrace, start, line, col); continue; }
if c == '#' { op1(self, out, .Sharp, start, line, col); continue; }
if c == '@' { op1(self, out, .At, start, line, col); continue; }
if c == '$' { op1(self, out, .Dollar, start, line, col); continue; }
if c == '?' { op1(self, out, .Cond, start, line, col); continue; }
report(self, start, start + 1, "unexpected character");
emit_to(self, out, .Unknown(c), start, start + 1, line, col);
}
var end = self.src.len;
emit_to(self, out, .Eof, end, end, self.line, self.col);
}
fn lex(src: ByteView, diag: *Diag, file: usize): VecResult {
var out = Vec.blank(@sizeof(Token), @alignof(Token));
match Vec.new(@sizeof(Token), @alignof(Token)) {
.Ok(v) => { out = v; },
.Err(error) => { ret .Err(error); },
}
var self = new(src, diag, file);
run(&self, &out);
ret .Ok(out);
}
fn view(text: []u8): ByteView {
ret ByteView.from_slice(text);
}
fn lex_ok(src: []u8): Vec {
var out = Vec.blank(@sizeof(Token), @alignof(Token));
match lex(view(src), null, 0) {
.Ok(v) => { out = v; },
.Err(error) => { @assert(false, "lex failed"); },
}
ret out;
}
fn at(v: *Vec, index: usize): Token {
match Vec.get(v, index) {
.Ok(ptr) => { ret (ptr as *Token).*; },
.Err(error) => { ret Token.make(.Unknown(0), ByteView.empty(), 0, 0, 0); },
}
ret Token.make(.Unknown(0), ByteView.empty(), 0, 0, 0);
}
fn expect_punct(tok: Token, expected: Punct) {
match tok.kind {
.Punct(p) => { @assert(@enum2int(p) == @enum2int(expected), "punct kind"); },
_ => { @assert(false, "expected punct"); },
}
}
fn expect_keyword(tok: Token, expected: Keyword) {
match tok.kind {
.Keyword(k) => { @assert(@enum2int(k) == @enum2int(expected), "keyword kind"); },
_ => { @assert(false, "expected keyword"); },
}
}
fn expect_ident(tok: Token) {
match tok.kind {
.Ident => {},
_ => { @assert(false, "expected ident"); },
}
}
fn expect_int(tok: Token, radix: Radix) {
match tok.kind {
.Int(r) => {
@assert(@enum2int(r) == @enum2int(radix), "int radix");
},
_ => { @assert(false, "expected int"); },
}
}
fn expect_char(tok: Token, value: u8) {
match tok.kind {
.Char(v) => { @assert(v == value, "char value"); },
_ => { @assert(false, "expected char"); },
}
}
fn expect_endline(tok: Token) {
match tok.kind {
.Endline => {},
_ => { @assert(false, "expected endline"); },
}
}
fn expect_eof(tok: Token) {
match tok.kind {
.Eof => {},
_ => { @assert(false, "expected eof"); },
}
}
fn expect_text(tok: Token, literal: []u8) {
@assert(ByteView.eq(tok.text, view(literal)), "token text");
}
fn expect_pos(tok: Token, line: usize, col: usize) {
@assert(tok.line == line && tok.col == col, "token position");
}
fn test_basic() {
var v = lex_ok("fn main() {\n ret 0;\n}\n");
defer { Vec.drop(&v); }
@assert(v.len == 13, "basic token count");
var t0 = at(&v, 0);
expect_keyword(t0, .Fn);
expect_text(t0, "fn");
expect_pos(t0, 1, 1);
expect_ident(at(&v, 1));
expect_text(at(&v, 1), "main");
expect_punct(at(&v, 2), .LParen);
expect_punct(at(&v, 3), .RParen);
expect_punct(at(&v, 4), .LBrace);
expect_endline(at(&v, 5));
expect_keyword(at(&v, 6), .Ret);
expect_pos(at(&v, 6), 2, 5);
expect_int(at(&v, 7), .Dec);
expect_punct(at(&v, 8), .Semicolon);
expect_endline(at(&v, 9));
expect_punct(at(&v, 10), .RBrace);
expect_endline(at(&v, 11));
expect_eof(at(&v, 12));
}
fn test_operators() {
var v = lex_ok("<-> <- -> <=> <<= << >>= ... .. . := == != < <= > >= && || & | ^ + - * / % ~ ! ? # @ $ , ; : ( ) [ ] { }");
defer { Vec.drop(&v); }
@assert(v.len == 43, "operator token count");
expect_punct(at(&v, 0), .ArrowBoth);
expect_text(at(&v, 0), "<->");
expect_punct(at(&v, 1), .LeftArrow);
expect_punct(at(&v, 2), .RightArrow);
expect_punct(at(&v, 3), .FatArrowBoth);
expect_punct(at(&v, 4), .LShAssign);
expect_punct(at(&v, 5), .LSh);
expect_text(at(&v, 5), "<<");
expect_punct(at(&v, 6), .RShAssign);
expect_punct(at(&v, 7), .Ellipsis);
expect_punct(at(&v, 8), .Range);
expect_punct(at(&v, 9), .Dot);
expect_punct(at(&v, 10), .ColonAssign);
expect_punct(at(&v, 11), .Eq);
expect_punct(at(&v, 12), .Neq);
expect_punct(at(&v, 13), .Lt);
expect_punct(at(&v, 14), .Le);
expect_punct(at(&v, 15), .Gt);
expect_punct(at(&v, 16), .Ge);
expect_punct(at(&v, 17), .AndAnd);
expect_punct(at(&v, 18), .OrOr);
expect_punct(at(&v, 19), .And);
expect_punct(at(&v, 20), .Or);
expect_punct(at(&v, 21), .Xor);
expect_punct(at(&v, 22), .Add);
expect_punct(at(&v, 23), .Sub);
expect_punct(at(&v, 24), .Mul);
expect_punct(at(&v, 25), .Div);
expect_punct(at(&v, 26), .Mod);
expect_punct(at(&v, 27), .BitNot);
expect_punct(at(&v, 28), .Not);
expect_punct(at(&v, 29), .Cond);
expect_punct(at(&v, 30), .Sharp);
expect_punct(at(&v, 31), .At);
expect_punct(at(&v, 32), .Dollar);
expect_punct(at(&v, 33), .Comma);
expect_punct(at(&v, 34), .Semicolon);
expect_punct(at(&v, 35), .Colon);
expect_punct(at(&v, 36), .LParen);
expect_punct(at(&v, 37), .RParen);
expect_punct(at(&v, 38), .LBracket);
expect_punct(at(&v, 39), .RBracket);
expect_punct(at(&v, 40), .LBrace);
expect_punct(at(&v, 41), .RBrace);
expect_eof(at(&v, 42));
}
fn test_comments_and_literals() {
var v = lex_ok("// c\n/* a /* nested */ b */\n'a' '\\n' '\\x41' \"hi\\n\" 0xFF 0b1010 0o17 1_000 3.14");
defer { Vec.drop(&v); }
@assert(v.len == 13, "literal token count");
var comment = at(&v, 0);
match comment.kind {
.LineComment => {},
_ => { @assert(false, "expected line comment"); },
}
expect_text(comment, "// c");
expect_pos(comment, 1, 1);
expect_endline(at(&v, 1));
expect_endline(at(&v, 2));
var ch3 = at(&v, 3);
expect_char(ch3, 'a');
@assert(ByteView.eq(ch3.text, view("'a'")), "char text");
expect_pos(ch3, 3, 1);
expect_char(at(&v, 4), '\n');
expect_char(at(&v, 5), 0x41);
var s = at(&v, 6);
match s.kind {
.Str => {},
_ => { @assert(false, "expected string"); },
}
expect_text(s, "\"hi\\n\"");
expect_int(at(&v, 7), .Hex);
expect_int(at(&v, 8), .Bin);
expect_int(at(&v, 9), .Oct);
expect_int(at(&v, 10), .Dec);
var fl = at(&v, 11);
match fl.kind {
.Float => {},
_ => { @assert(false, "expected float"); },
}
expect_eof(at(&v, 12));
}
// 非法输入走 diag:仍产出 token 以便继续收集错误,但 diag 必须记录。
// Rust 风格数字字面量:分隔符下划线 + 多进制 + 小数/指数字面量。
fn test_number_forms() {
var v = lex_ok("1_000_000 0xFF_FF 0x_ff 0b_1010 0o_7 1_000.5 1.5_0 1_000.500 1.5e-3 1_0.5_0E2");
defer { Vec.drop(&v); }
@assert(v.len == 11, "number token count");
expect_int(at(&v, 0), .Dec);
expect_text(at(&v, 0), "1_000_000");
expect_int(at(&v, 1), .Hex);
expect_text(at(&v, 1), "0xFF_FF");
expect_int(at(&v, 2), .Hex);
expect_text(at(&v, 2), "0x_ff");
expect_int(at(&v, 3), .Bin);
expect_text(at(&v, 3), "0b_1010");
expect_int(at(&v, 4), .Oct);
expect_text(at(&v, 4), "0o_7");
var i: usize = 5;
while i < 10 {
match at(&v, i).kind {
.Float => {},
_ => { @assert(false, "expected float"); },
}
i = i + 1;
}
expect_text(at(&v, 5), "1_000.5");
expect_text(at(&v, 6), "1.5_0");
expect_text(at(&v, 7), "1_000.500");
expect_text(at(&v, 8), "1.5e-3");
expect_text(at(&v, 9), "1_0.5_0E2");
expect_eof(at(&v, 10));
}
fn test_diag_errors() {
var diag = Diag.new(null);
defer { Diag.drop(&diag); }
match lex(view("'\\q' \"abc"), &diag, 0) {
.Ok(v) => { Vec.drop(&v); },
.Err(error) => { @assert(false, "lex should return tokens"); },
}
@assert(Diag.has_error(diag), "lex errors recorded");
var clean = Diag.new(null);
defer { Diag.drop(&clean); }
match lex(view("fn main() { ret 0; }"), &clean, 0) {
.Ok(v) => { Vec.drop(&v); },
.Err(error) => { @assert(false, "legal lex failed"); },
}
@assert(!Diag.has_error(clean), "legal input has no diagnostics");
}
fn main() {
test_basic();
test_operators();
test_comments_and_literals();
test_number_forms();
test_diag_errors();
}
+788
View File
@@ -0,0 +1,788 @@
// Expect retcode = 0
// VM 字节码容器:内存中的 Prog 与 .sir 序列化(反序列化在 C 侧 spl_cli)。
// 字符串用 ByteView 借用、不拥有;gdata 条目为 ByteBuffer,由本模块拥有释放。
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("utils/memory.spl");
type .{ .open_cstr, .write_all, .read_all, .File } = @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,
Memcpy,
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"; },
.Memcpy => { ret "memcpy"; },
.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 i = self.insns.len;
if i < self.insns.cap {
var p = (self.insns.data as *VmIns) + i;
p.opcode = op;
p.ty = ty;
p.imm = imm;
self.insns.len = i + 1;
ret i + 1;
}
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; }
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;
}
// 追加一条 size 字节的零初始化全局数据(BSS 语义);返回其索引。
fn add_zero_data(self: *Prog, size: usize): usize {
var entry = ByteBuffer.new();
match ByteBuffer.reserve(&entry, size) {
.Ok => {},
.Err(error) => { ByteBuffer.drop(&entry); ret 0; },
}
if size != 0 {
entry.storage.len = size;
var p = entry.storage.data as *u8;
var i: usize = 0;
while i < size { p[i] = 0; i = i + 1; }
}
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;
}
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; }
match ins.opcode {
.Jmp, .Bz, .Bnz => {
if !out_s(out, " ; -> ") { ret false; }
if !out_u(out, i + 1 + ins.imm) { ret false; }
},
.Call => {
if !out_s(out, " ; nargs=") { ret false; }
if !out_u(out, ins.imm) { ret false; }
if !out_s(out, ", from stack") { ret false; }
},
.Calli => { if !out_s(out, " ; indirect call") { ret false; } },
.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 {
if i >= self.insns.len { ret VmIns { .opcode = .Error, .ty = .Void, .imm = 0 }; }
ret ((self.insns.data as *VmIns) + i).*;
}
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));
}
// put_* 直接写入已预留容量的 ByteBuffer 存储(store 已按精确总大小 reserve),跳过
// append 的校验/拷贝;容量不足会越界,故 store 先校验容量。
fn put_u8(out: *ByteBuffer, b: u8): bool {
out.storage.data[out.storage.len] = b;
out.storage.len = out.storage.len + 1;
ret true;
}
// 直接按小端整型写(x86 小端;VM 的 STORE 用 memcpy,未对齐也安全)。
fn put_u16(out: *ByteBuffer, v: usize): bool {
var d = out.storage.data + out.storage.len;
(d as *u16).* = v as u16;
out.storage.len = out.storage.len + 2;
ret true;
}
fn put_u64(out: *ByteBuffer, v: usize): bool {
var d = out.storage.data + out.storage.len;
(d as *u64).* = v as u64;
out.storage.len = out.storage.len + 8;
ret true;
}
fn put_zeros(out: *ByteBuffer, n: usize): bool {
if n != 0 {
Mem.set((out.storage.data + out.storage.len) as *any, 0, n);
out.storage.len = out.storage.len + n;
}
ret true;
}
fn put_view(out: *ByteBuffer, v: ByteView): bool {
if v.len == 0 { ret true; }
Mem.copy((out.storage.data + out.storage.len) as *any, v.ptr as *any, v.len);
out.storage.len = out.storage.len + v.len;
ret true;
}
// 写入「带结尾 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);
}
// 把 prog 序列化为二进制写入 out(不落盘,供内嵌 VM 直接用)。
fn encode(self: *Prog, out: *ByteBuffer): Status {
// 精确预估输出大小并一次性预留,避免大文件逐字节增长导致的重复拷贝。
var total: usize = 48;
var fi: usize = 0;
var func_base = self.funcs.data as *VmFunc;
var nat_base = self.natives.data as *VmNative;
var str_base = self.strtab.data as *ByteView;
var gd_base = self.gdata.data as *ByteBuffer;
while fi < self.funcs.len {
var f = func_base + fi;
var nlen = f.name.len + 1;
total = total + 8 + align8(nlen) + 32;
fi = fi + 1;
}
total = total + self.insns.len * 12;
fi = 0;
while fi < self.natives.len {
var n = nat_base + fi;
var nlen = n.name.len + 1;
total = total + 8 + align8(nlen) + 8;
fi = fi + 1;
}
fi = 0;
while fi < self.strtab.len {
var s = str_base + fi;
total = total + 8 + align8(s.len);
fi = fi + 1;
}
fi = 1;
while fi < self.gdata.len {
var e = gd_base + fi;
total = total + 8 + align8(e.storage.len);
fi = fi + 1;
}
ByteBuffer.reserve(out, total);
if out.storage.cap < total || out.storage.data == null { ret .Err(make(.Io)); }
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_base + i;
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;
var ins_base = self.insns.data as *VmIns;
var out_base = out.storage.data;
var out_len = out.storage.len;
while i < ninsns {
var p = ins_base + i;
var d = out_base + out_len;
(d as *u16).* = @enum2int(p.opcode) as u16;
((d + 2) as *u16).* = @enum2int(p.ty) as u16;
((d + 4) as *u64).* = p.imm as u64;
out_len = out_len + 12;
i = i + 1;
}
out.storage.len = out_len;
i = 0;
while i < nnatives {
var n = nat_base + i;
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_base + 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 = gd_base + i;
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;
}
ret .Ok;
}
fn store(self: *Prog, path: ByteView): Status {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
match encode(self, &out) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
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;
}
// 序列化到内存缓冲(供内嵌 VM 直接装载)。
fn serialize(self: *Prog, out: *ByteBuffer): Status {
ret encode(self, out);
}
fn test_dump_basic() {
var pr: Prog = undefined;
match new() {
.Ok(v) => { pr = v; },
.Err(error) => { @assert(false, "prog init"); ret; },
}
defer { drop(&pr); }
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); }
add_func(&pr, ByteView.from_slice("main"), 0, 0);
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();
}
+2448
View File
File diff suppressed because it is too large. Load diff
+217
View File
@@ -0,0 +1,217 @@
// Expect retcode = 0
// 作用域树:名字 -> Symbol,节点经 parent 上溯查找(支持遮蔽)。名字借用源字节,
// 存活由调用方保证;node 为 def.spl 的 DefId。
type .{ .Error } = @import("utils/error.spl");
type .{ .Vec } = @import("utils/vec.spl");
type .{ .Map, .hash_kind_view } = @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_kind_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; }
ret (self.scopes.data as *ScopeNode) + id;
}
// 新作用域 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();
}
+3087
View File
File diff suppressed because it is too large. Load diff
+821
View File
@@ -0,0 +1,821 @@
// Expect retcode = 0
// splc1:自举编译器入口。`splc1 <in> <out>` 编译;`--dump <mode> <file>` 打印各阶段
// 产物,格式必须与 splc0 逐字节一致以通过 diff。
type .{ .ByteView } = @import("./utils/byte_view.spl");
type .{ .ByteBuffer } = @import("./utils/byte_buffer.spl");
type .{ .Vec } = @import("./utils/vec.spl");
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, .open_cstr, .write_all, .File } = @import("./utils/io.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 Ir2CM = @import("./ir2c.spl");
type .{ .alloc, .free } = @import("./utils/memory.spl");
fn token_at(tokens: *Vec, index: usize): Token {
match Vec.get(tokens, index) {
.Ok(ptr) => { ret (ptr as *Token).*; },
.Err(error) => { ret Token.make(.Unknown(0), ByteView.empty(), 0, 0, 0); },
}
ret Token.make(.Unknown(0), ByteView.empty(), 0, 0, 0);
}
// 追加成功返回 true;失败(仅 OOM)返回 false。
fn push(out: *ByteBuffer, bytes: []u8): bool {
match ByteBuffer.append(out, bytes) {
.Ok => { ret true; },
.Err(error) => { ret false; },
}
ret false;
}
fn push_byte(out: *ByteBuffer, byte: u8): bool {
match ByteBuffer.append_byte(out, byte) {
.Ok => { ret true; },
.Err(error) => { ret false; },
}
ret false;
}
fn push_usize(out: *ByteBuffer, value: usize): bool {
match append_usize(out, value, 10, false) {
.Ok => { ret true; },
.Err(error) => { ret false; },
}
ret false;
}
// 输出格式与 splc0.c 的 dump_tokens 一致:
// tokens got (N)\n
// [TOK_TYPE] <lexeme> (line:col)\n
fn dump_tokens(tokens: *Vec): bool {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
if !push(&out, "tokens got (") { ret false; }
if !push_usize(&out, tokens.len) { ret false; }
if !push(&out, ")\n") { ret false; }
var i: usize = 0;
while i < tokens.len {
var tok = token_at(tokens, i);
if !push(&out, "[") { ret false; }
if !push(&out, kind_name(tok.kind)) { ret false; }
if !push(&out, "] ") { ret false; }
if !push(&out, ByteView.as_slice(tok.text)) { ret false; }
if !push(&out, " (") { ret false; }
if !push_usize(&out, tok.line) { ret false; }
if !push_byte(&out, ':') { ret false; }
if !push_usize(&out, tok.col) { ret false; }
if !push(&out, ")\n") { ret false; }
i = i + 1;
}
match print(ByteBuffer.as_slice(out)) {
.Ok => { ret true; },
.Err(error) => { ret false; },
}
ret false;
}
fn dump_lex(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); }
// 非法输入:走诊断(显式报错,不静默)。
if Diag.has_error(diag) {
var rendered = ByteBuffer.new();
defer { ByteBuffer.drop(&rendered); }
Diag.render_human(&diag, &rendered);
eprint(ByteBuffer.as_slice(rendered));
ret 1;
}
if !dump_tokens(&tokens) {
Logger.fatal(log, "out of memory while dumping tokens");
ret 1;
}
ret 0;
}
fn dump_ast(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); }
Diag.render_human(&diag, &rendered);
eprint(ByteBuffer.as_slice(rendered));
ret 1;
}
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
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 ModEntry = struct {
path: ByteView,
ast: *Ast,
ok: bool,
}
type LoaderCtx = struct {
sources: *Sources,
mods: Vec, // ModEntry
buffers: Vec, // ByteBuffer(路径/文本稳定存储)
diag: *Diag,
}
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 {
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] == '.' {
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] == '\\') {
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] != '/' {
append_byte(out, '/');
}
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 {
append_bytes(&out, sub(from, 0, (last as usize) + 1));
}
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); }
Diag.render_human(&diag, &rendered);
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); }
Diag.render_human(&diag, &rendered);
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, // 只逐步打日志(诊断“卡在哪一步”)
C, // 继续 layout + ir2c,dump C
}
fn step(mode: PipeMode, s: []u8) {
if @enum2int(mode) == @enum2int(PipeMode.State) {
eprint(s);
}
}
// 完整前端流水线: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); }
Diag.render_human(&diag, &rendered);
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); }
Diag.render_human(&diag, &rendered);
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 = &ast;
Vec.push(&asts, &ap as *u8);
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;
Vec.push(&asts, &mp as *u8);
}
mi = mi + 1;
}
Ast2Ir.run(&a2ir, asts);
if Diag.has_error(diag) {
var rendered = ByteBuffer.new();
defer { ByteBuffer.drop(&rendered); }
Diag.render_human(&diag, &rendered);
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");
if @enum2int(mode) == @enum2int(PipeMode.C) {
var cout = ByteBuffer.new();
defer { ByteBuffer.drop(&cout); }
var ok = Ir2CM.compile(&builder.ir, &sema.types.ir, &layout, &sema.attr, &cout);
if outpath.len != 0 {
var f = open_cstr(outpath, ByteView.from_slice("wb"));
if !File.valid(&f) {
Logger.error(log, "cannot open C output file");
ret 1;
}
write_all(&f, ByteBuffer.as_slice(cout));
File.close(&f);
} else {
match print(ByteBuffer.as_slice(cout)) {
.Ok => {},
.Err(error) => { ret 1; },
}
}
if !ok {
Logger.error(log, "ir2c failed (see generated C for unsupported markers)");
ret 1;
}
ret 0;
}
var ir2vm: Ir2Vm = undefined;
Ir2VmM.init(&ir2vm, &builder.ir, &sema.types.ir, &layout, &sema.attr);
defer { Ir2VmM.drop(&ir2vm); }
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 main(argc: i32, argv: **u8): i32 {
// 无参数:自测运行(`nob test stage2`)视为成功。
if argc <= 1 { ret 0; }
var log = Logger.new(ByteView.from_slice("splc1"));
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);
}
if argc < 4 {
Logger.fatal(&log, "usage: splc1 --dump <lex|ast|sema|ir|vm|c> <file> [out]");
ret 1;
}
var mode = ByteView.from_cstr(argv[2]);
var path = ByteView.from_cstr(argv[3]);
var dout = ByteView.empty();
if argc >= 5 { dout = ByteView.from_cstr(argv[4]); }
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());
}
if ByteView.eq(mode, ByteView.from_slice("c")) {
ret run_pipeline(&log, path, .C, dout);
}
Logger.fatal(&log, "dump mode not implemented");
ret 1;
}
+22
View File
@@ -0,0 +1,22 @@
// Expect retcode = 6
// exit.spl
// usage: type exit = @import("exit.spl"); exit.ok();
#extern(vm) fn vm_exit(code: i32);
fn now(code: i32) {
vm_exit(code);
}
fn ok() {
vm_exit(0);
}
fn fail() {
vm_exit(1);
}
fn main(): void {
now(6);
}
+70
View File
@@ -0,0 +1,70 @@
#extern(vm) fn vm_fopen(path: *any, mode: *any): *any;
#extern(vm) fn vm_fclose(f: *any): i32;
#extern(vm) fn vm_fread(buf: *any, sz: usize, n: usize, f: *any): usize;
#extern(vm) fn vm_fwrite(buf: *any, sz: usize, n: usize, f: *any): usize;
#extern(vm) fn vm_fsize(f: *any): usize;
#extern(vm) fn vm_stdin(): *any;
#extern(vm) fn vm_stdout(): *any;
#extern(vm) fn vm_stderr(): *any;
#extern(vm) fn vm_putchar(c: i32): i32;
#extern(vm) fn vm_getchar(): i32;
handle: *any,
type File = @self;
fn stdin(): File { ret File { .handle = vm_stdin() }; }
fn stdout(): File { ret File { .handle = vm_stdout() }; }
fn stderr(): File { ret File { .handle = vm_stderr() }; }
fn open(path: *u8, mode: *u8): File {
ret File { .handle = vm_fopen(path as *any, mode as *any) };
}
fn valid(f: *File): bool {
ret f.handle != null;
}
fn write(f: *File, data: *u8, n: u64): u64 {
if f.handle == null { ret 0; }
ret vm_fwrite(data as *any, 1, n, f.handle);
}
fn write_byte(f: *File, b: u8): u64 {
var tmp: u8 = b;
ret write(f, &tmp, 1);
}
fn read(f: *File, buf: *u8, n: usize): usize {
if f.handle == null { ret 0; }
ret vm_fread(buf as *any, 1, n, f.handle);
}
fn size(f: *File): usize {
if f.handle == null { ret 0; }
ret vm_fsize(f.handle);
}
fn close(f: *File): i32 {
if f.handle == null { ret -1; }
var r = vm_fclose(f.handle);
f.handle = null;
ret r;
}
fn putchar(c: u8) {
vm_putchar(c as i32);
}
fn getchar(): i32 {
ret vm_getchar();
}
fn main(): void {
var out = stdout();
@assert(out.handle != null, "stdout");
var msg = "file: ok\n";
var n = write(&out, msg.ptr, msg.len);
@assert(n == msg.len, "write");
putchar(46 as u8);
putchar(10 as u8);
}
+79
View File
@@ -0,0 +1,79 @@
#extern(vm) fn vm_alloc(sz: usize): *any;
#extern(vm) fn vm_free(p: *any);
#extern(vm) fn vm_realloc(p: *any, sz: usize): *any;
#extern(vm) fn vm_memcpy(dst: *any, src: *any, n: usize): *any;
#extern(vm) fn vm_memcmp(a: *any, b: *any, n: usize): isize;
fn alloc(sz: usize): *any {
if sz == 0 { ret null; }
ret vm_alloc(sz);
}
fn free(p: *any) {
if p == null { ret; }
vm_free(p);
}
fn realloc(p: *any, sz: usize): *any {
if p == null { ret alloc(sz); }
if sz == 0 { free(p); ret null; }
ret vm_realloc(p, sz);
}
fn copy(dst: *any, src: *any, n: usize): *any {
if n == 0 { ret dst; }
ret vm_memcpy(dst, src, n);
}
fn cmp(a: *any, b: *any, n: usize): isize {
if n == 0 { ret 0; }
ret vm_memcmp(a, b, n);
}
fn set(dst: *any, byte: u8, n: usize): *any {
var d = dst as *u8;
var i: usize = 0;
while i < n {
d[i] = byte;
i = i + 1;
}
ret dst;
}
fn zero(dst: *any, n: usize): *any {
ret set(dst, 0 as u8, n);
}
fn main(): void {
var p = alloc(16);
@assert(p != null, "alloc");
var q = alloc(16);
@assert(p != q, "distinct");
@assert(alloc(0) == null, "alloc(0)");
var a: u8 = 42;
var b: u8 = 0;
copy(&b as *any, &a as *any, 1);
@assert(b == 42, "copy");
var x: u8 = 1;
var y: u8 = 2;
@assert(cmp(&x as *any, &y as *any, 1) < 0, "cmp lt");
@assert(cmp(&y as *any, &x as *any, 1) > 0, "cmp gt");
@assert(cmp(&x as *any, &x as *any, 1) == 0, "cmp eq");
var buf = alloc(8);
zero(buf, 8);
var bp = buf as *u8;
@assert(bp[0] == 0, "zero");
@assert(bp[7] == 0, "zero end");
set(buf, 0xAB as u8, 8);
@assert(bp[0] == 0xAB as u8, "set");
var r = realloc(p, 32);
@assert(r != null, "realloc");
free(r);
free(q);
free(buf);
free(null);
}
+49
View File
@@ -0,0 +1,49 @@
// 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 calls(): usize {
ret vm_timestamp(5);
}
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");
}
+20
View File
@@ -0,0 +1,20 @@
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, buf: *any, len: usize): i32;
#extern(vm) fn vm_push(vm: *any, val: spl_vm_val_t): void;
#extern(vm) fn vm_call(vm: *any, name: *u8, nargs: u64): i32;
#extern(vm) fn vm_run(vm: *any): i32;
#extern(vm) fn vm_ret_val(vm: *any): spl_vm_val_t;
#extern(vm) fn vm_mem_alloc(vm: *any, size: usize): spl_vm_val_t;
#extern(vm) fn vm_mem_read(vm: *any, off: spl_vm_val_t, dst: *any, n: usize): i32;
#extern(vm) fn vm_mem_write(vm: *any, off: spl_vm_val_t, src: *any, n: usize): i32;
#extern(vm) fn vm_stack_get(vm: *any, index: usize): spl_vm_val_t;
#extern(vm) fn vm_set_foreign(vm: *any, entry: []u8, ctx: u64): i32;
fn main() {}
+425
View File
@@ -0,0 +1,425 @@
// Expect retcode = 0
// 词法单元:TokenKind 为带载荷 ADT,关键词/算符各自独立 enum;整型/字符在词法期
// 解码,文本一律借用源切片。
type .{ .ByteView } = @import("utils/byte_view.spl");
type Keyword = enum {
Any, As, Bool, Break, Catch, Comptime, Const, Continue, Defer, Else, Enum,
Errdefer, False, Fn, For, If, Loop, Match, Null, Ret, Shape, Struct, True,
Try, Type, Undefined, Union, Var, While, Underscore,
}
type KeywordResult = enum {
Yes: Keyword,
No,
}
type Punct = enum {
Eq, Assign, AddAssign, Add, SubAssign, Sub, MulAssign, Mul, DivAssign, Div,
ModAssign, Mod, RightArrow, LeftArrow, ArrowBoth, FatArrowBoth, RightFatArrow,
AndAnd, AndAssign, And, OrOr, OrAssign, Or, XorAssign, Xor,
LShAssign, LSh, Le, Lt, RShAssign, RSh, Ge, Gt, Not, Neq, BitNot,
LBracket, RBracket, LParen, RParen, LBrace, RBrace,
Semicolon, Comma, Colon, ColonAssign, Dot, Range, Ellipsis, Cond, Sharp, At, Dollar,
}
type Radix = enum {
Dec,
Bin,
Oct,
Hex,
}
type TokenKind = enum {
Keyword: Keyword,
Punct: Punct,
Ident,
Int: Radix, // 仅记进制;数值由 parser 按任意宽度(mp)解析
Float,
Char: u8,
Str,
LineComment,
Endline,
Eof,
Unknown: u8,
}
type Token = @self;
kind: TokenKind,
text: ByteView,
line: usize,
col: usize,
offset: usize, // 源文件字节偏移(供 AST Span 使用)
fn make(kind: TokenKind, text: ByteView, line: usize, col: usize, offset: usize): Token {
ret Token { .kind = kind, .text = text, .line = line, .col = col, .offset = offset };
}
fn is_trivia(kind: TokenKind): bool {
match kind {
.Endline => { ret true; },
.LineComment => { ret true; },
_ => { ret false; },
}
}
// 先按长度与首字节分派,再做完整比较,避免对每个标识符做全表串比较。
fn keyword_of(text: ByteView): KeywordResult {
var n = text.len;
if n == 0 { ret .No; }
var c0 = text.ptr[0];
if n == 2 {
if c0 == 'a' && is(text, "as") { ret .Yes(.As); }
if c0 == 'f' && is(text, "fn") { ret .Yes(.Fn); }
if c0 == 'i' && is(text, "if") { ret .Yes(.If); }
ret .No;
}
if n == 3 {
if c0 == 'a' && is(text, "any") { ret .Yes(.Any); }
if c0 == 'f' && is(text, "for") { ret .Yes(.For); }
if c0 == 'r' && is(text, "ret") { ret .Yes(.Ret); }
if c0 == 't' && is(text, "try") { ret .Yes(.Try); }
if c0 == 'v' && is(text, "var") { ret .Yes(.Var); }
ret .No;
}
if n == 4 {
if c0 == 'b' && is(text, "bool") { ret .Yes(.Bool); }
if c0 == 'e' && is(text, "else") { ret .Yes(.Else); }
if c0 == 'e' && is(text, "enum") { ret .Yes(.Enum); }
if c0 == 'l' && is(text, "loop") { ret .Yes(.Loop); }
if c0 == 'n' && is(text, "null") { ret .Yes(.Null); }
if c0 == 't' && is(text, "true") { ret .Yes(.True); }
if c0 == 't' && is(text, "type") { ret .Yes(.Type); }
ret .No;
}
if n == 5 {
if c0 == 'b' && is(text, "break") { ret .Yes(.Break); }
if c0 == 'c' && is(text, "catch") { ret .Yes(.Catch); }
if c0 == 'c' && is(text, "const") { ret .Yes(.Const); }
if c0 == 'd' && is(text, "defer") { ret .Yes(.Defer); }
if c0 == 'f' && is(text, "false") { ret .Yes(.False); }
if c0 == 'm' && is(text, "match") { ret .Yes(.Match); }
if c0 == 's' && is(text, "shape") { ret .Yes(.Shape); }
if c0 == 'u' && is(text, "union") { ret .Yes(.Union); }
if c0 == 'w' && is(text, "while") { ret .Yes(.While); }
ret .No;
}
if n == 6 {
if c0 == 's' && is(text, "struct") { ret .Yes(.Struct); }
ret .No;
}
if n == 8 {
if c0 == 'c' && is(text, "comptime") { ret .Yes(.Comptime); }
if c0 == 'c' && is(text, "continue") { ret .Yes(.Continue); }
if c0 == 'e' && is(text, "errdefer") { ret .Yes(.Errdefer); }
ret .No;
}
if n == 9 {
if c0 == 'u' && is(text, "undefined") { ret .Yes(.Undefined); }
ret .No;
}
if n == 1 {
if c0 == '_' && is(text, "_") { ret .Yes(.Underscore); }
ret .No;
}
ret .No;
}
fn keyword_name(kw: Keyword): []u8 {
match kw {
.Any => { ret "KW_ANY"; },
.As => { ret "KW_AS"; },
.Bool => { ret "KW_BOOL"; },
.Break => { ret "KW_BREAK"; },
.Catch => { ret "KW_CATCH"; },
.Comptime => { ret "KW_COMPTIME"; },
.Const => { ret "KW_CONST"; },
.Continue => { ret "KW_CONTINUE"; },
.Defer => { ret "KW_DEFER"; },
.Else => { ret "KW_ELSE"; },
.Enum => { ret "KW_ENUM"; },
.Errdefer => { ret "KW_ERRDEFER"; },
.False => { ret "KW_FALSE"; },
.Fn => { ret "KW_FN"; },
.For => { ret "KW_FOR"; },
.If => { ret "KW_IF"; },
.Loop => { ret "KW_LOOP"; },
.Match => { ret "KW_MATCH"; },
.Null => { ret "KW_NULL"; },
.Ret => { ret "KW_RET"; },
.Shape => { ret "KW_SHAPE"; },
.Struct => { ret "KW_STRUCT"; },
.True => { ret "KW_TRUE"; },
.Try => { ret "KW_TRY"; },
.Type => { ret "KW_TYPE"; },
.Undefined => { ret "KW_UNDEFINED"; },
.Union => { ret "KW_UNION"; },
.Var => { ret "KW_VAR"; },
.While => { ret "KW_WHILE"; },
.Underscore => { ret "KW_UNDERSCORE"; },
}
ret "?";
}
fn punct_name(punct: Punct): []u8 {
match punct {
.Eq => { ret "TOK_EQ"; },
.Assign => { ret "TOK_ASSIGN"; },
.AddAssign => { ret "TOK_ASSIGN_ADD"; },
.Add => { ret "TOK_ADD"; },
.SubAssign => { ret "TOK_ASSIGN_SUB"; },
.Sub => { ret "TOK_SUB"; },
.MulAssign => { ret "TOK_ASSIGN_MUL"; },
.Mul => { ret "TOK_MUL"; },
.DivAssign => { ret "TOK_ASSIGN_DIV"; },
.Div => { ret "TOK_DIV"; },
.ModAssign => { ret "TOK_ASSIGN_MOD"; },
.Mod => { ret "TOK_MOD"; },
.RightArrow => { ret "TOK_RIGHT_ARROW"; },
.LeftArrow => { ret "TOK_LEFT_ARROW"; },
.ArrowBoth => { ret "TOK_ARROW_BOTH"; },
.FatArrowBoth => { ret "TOK_FAT_ARROW_BOTH"; },
.RightFatArrow => { ret "TOK_RIGHT_FAT_ARROW"; },
.AndAnd => { ret "TOK_AND_AND"; },
.AndAssign => { ret "TOK_ASSIGN_AND"; },
.And => { ret "TOK_AND"; },
.OrOr => { ret "TOK_OR_OR"; },
.OrAssign => { ret "TOK_ASSIGN_OR"; },
.Or => { ret "TOK_OR"; },
.XorAssign => { ret "TOK_ASSIGN_XOR"; },
.Xor => { ret "TOK_XOR"; },
.LShAssign => { ret "TOK_ASSIGN_L_SH"; },
.LSh => { ret "TOK_L_SH"; },
.Le => { ret "TOK_LE"; },
.Lt => { ret "TOK_LT"; },
.RShAssign => { ret "TOK_ASSIGN_R_SH"; },
.RSh => { ret "TOK_R_SH"; },
.Ge => { ret "TOK_GE"; },
.Gt => { ret "TOK_GT"; },
.Not => { ret "TOK_NOT"; },
.Neq => { ret "TOK_NEQ"; },
.BitNot => { ret "TOK_BIT_NOT"; },
.LBracket => { ret "TOK_L_BRACKET"; },
.RBracket => { ret "TOK_R_BRACKET"; },
.LParen => { ret "TOK_L_PAREN"; },
.RParen => { ret "TOK_R_PAREN"; },
.LBrace => { ret "TOK_L_BRACE"; },
.RBrace => { ret "TOK_R_BRACE"; },
.Semicolon => { ret "TOK_SEMICOLON"; },
.Comma => { ret "TOK_COMMA"; },
.Colon => { ret "TOK_COLON"; },
.ColonAssign => { ret "TOK_COLON_ASSIGN"; },
.Dot => { ret "TOK_DOT"; },
.Range => { ret "TOK_RANGE"; },
.Ellipsis => { ret "TOK_ELLIPSIS"; },
.Cond => { ret "TOK_COND"; },
.Sharp => { ret "TOK_SHARP"; },
.At => { ret "TOK_AT"; },
.Dollar => { ret "TOK_DOLLAR"; },
}
ret "?";
}
fn kind_name(kind: TokenKind): []u8 {
match kind {
.Keyword(kw) => { ret keyword_name(kw); },
.Punct(punct) => { ret punct_name(punct); },
.Ident => { ret "TOK_IDENT"; },
.Int(value) => { ret "TOK_INT_LITERAL"; },
.Float => { ret "TOK_FLOAT_LITERAL"; },
.Char(value) => { ret "TOK_CHAR_LITERAL"; },
.Str => { ret "TOK_STRING_LITERAL"; },
.LineComment => { ret "TOK_LINE_COMMENT"; },
.Endline => { ret "TOK_ENDLINE"; },
.Eof => { ret "TOK_EOF"; },
.Unknown(value) => { ret "TOK_UNKNOWN"; },
}
ret "?";
}
fn view(text: []u8): ByteView {
ret ByteView.from_slice(text);
}
fn is(text: ByteView, literal: []u8): bool {
ret ByteView.eq(text, view(literal));
}
fn expect_keyword(result: KeywordResult, expected: Keyword) {
match result {
.Yes(kw) => { @assert(@enum2int(kw) == @enum2int(expected), "keyword"); },
.No => { @assert(false, "expected keyword"); },
}
}
fn test_keywords() {
expect_keyword(keyword_of(view("fn")), .Fn);
expect_keyword(keyword_of(view("while")), .While);
expect_keyword(keyword_of(view("_")), .Underscore);
match keyword_of(view("foo")) {
.Yes(kw) => { @assert(false, "foo is not a keyword"); },
.No => {},
}
match keyword_of(view("")) {
.Yes(kw) => { @assert(false, "empty is not a keyword"); },
.No => {},
}
}
fn test_adt_payloads() {
var t = make(.Int(.Hex), view("0xFF"), 3, 5, 0);
@assert(t.line == 3 && t.col == 5, "position");
match t.kind {
.Int(radix) => {
var hex: Radix = .Hex;
@assert(@enum2int(radix) == @enum2int(hex), "int radix");
},
_ => { @assert(false, "expected int"); },
}
var c = make(.Char('a'), view("'a'"), 1, 1, 0);
match c.kind {
.Char(value) => { @assert(value == 'a', "char value"); },
_ => { @assert(false, "expected char"); },
}
var p = make(.Punct(.ArrowBoth), view("<->"), 1, 1, 0);
match p.kind {
.Punct(punct) => {
var both: Punct = .ArrowBoth;
@assert(@enum2int(punct) == @enum2int(both), "punct");
},
_ => { @assert(false, "expected punct"); },
}
var k = make(.Keyword(.Ret), view("ret"), 1, 1, 0);
match k.kind {
.Keyword(kw) => {
var ret_kw: Keyword = .Ret;
@assert(@enum2int(kw) == @enum2int(ret_kw), "keyword kind");
},
_ => { @assert(false, "expected keyword"); },
}
}
fn test_trivia() {
@assert(is_trivia(.Endline), "endline trivia");
@assert(is_trivia(.LineComment), "comment trivia");
@assert(!is_trivia(.Ident), "ident not trivia");
@assert(!is_trivia(.Eof), "eof not trivia");
}
fn expect_kind_name(kind: TokenKind, expected: []u8) {
@assert(ByteView.eq(ByteView.from_slice(kind_name(kind)),
ByteView.from_slice(expected)),
"token kind name");
}
fn test_kind_names() {
expect_kind_name(.Keyword(.Any), "KW_ANY");
expect_kind_name(.Keyword(.As), "KW_AS");
expect_kind_name(.Keyword(.Bool), "KW_BOOL");
expect_kind_name(.Keyword(.Break), "KW_BREAK");
expect_kind_name(.Keyword(.Catch), "KW_CATCH");
expect_kind_name(.Keyword(.Comptime), "KW_COMPTIME");
expect_kind_name(.Keyword(.Const), "KW_CONST");
expect_kind_name(.Keyword(.Continue), "KW_CONTINUE");
expect_kind_name(.Keyword(.Defer), "KW_DEFER");
expect_kind_name(.Keyword(.Else), "KW_ELSE");
expect_kind_name(.Keyword(.Enum), "KW_ENUM");
expect_kind_name(.Keyword(.Errdefer), "KW_ERRDEFER");
expect_kind_name(.Keyword(.False), "KW_FALSE");
expect_kind_name(.Keyword(.Fn), "KW_FN");
expect_kind_name(.Keyword(.For), "KW_FOR");
expect_kind_name(.Keyword(.If), "KW_IF");
expect_kind_name(.Keyword(.Loop), "KW_LOOP");
expect_kind_name(.Keyword(.Match), "KW_MATCH");
expect_kind_name(.Keyword(.Null), "KW_NULL");
expect_kind_name(.Keyword(.Ret), "KW_RET");
expect_kind_name(.Keyword(.Shape), "KW_SHAPE");
expect_kind_name(.Keyword(.Struct), "KW_STRUCT");
expect_kind_name(.Keyword(.True), "KW_TRUE");
expect_kind_name(.Keyword(.Try), "KW_TRY");
expect_kind_name(.Keyword(.Type), "KW_TYPE");
expect_kind_name(.Keyword(.Undefined), "KW_UNDEFINED");
expect_kind_name(.Keyword(.Union), "KW_UNION");
expect_kind_name(.Keyword(.Var), "KW_VAR");
expect_kind_name(.Keyword(.While), "KW_WHILE");
expect_kind_name(.Keyword(.Underscore), "KW_UNDERSCORE");
expect_kind_name(.Punct(.Eq), "TOK_EQ");
expect_kind_name(.Punct(.Assign), "TOK_ASSIGN");
expect_kind_name(.Punct(.AddAssign), "TOK_ASSIGN_ADD");
expect_kind_name(.Punct(.Add), "TOK_ADD");
expect_kind_name(.Punct(.SubAssign), "TOK_ASSIGN_SUB");
expect_kind_name(.Punct(.Sub), "TOK_SUB");
expect_kind_name(.Punct(.MulAssign), "TOK_ASSIGN_MUL");
expect_kind_name(.Punct(.Mul), "TOK_MUL");
expect_kind_name(.Punct(.DivAssign), "TOK_ASSIGN_DIV");
expect_kind_name(.Punct(.Div), "TOK_DIV");
expect_kind_name(.Punct(.ModAssign), "TOK_ASSIGN_MOD");
expect_kind_name(.Punct(.Mod), "TOK_MOD");
expect_kind_name(.Punct(.RightArrow), "TOK_RIGHT_ARROW");
expect_kind_name(.Punct(.LeftArrow), "TOK_LEFT_ARROW");
expect_kind_name(.Punct(.ArrowBoth), "TOK_ARROW_BOTH");
expect_kind_name(.Punct(.FatArrowBoth), "TOK_FAT_ARROW_BOTH");
expect_kind_name(.Punct(.RightFatArrow), "TOK_RIGHT_FAT_ARROW");
expect_kind_name(.Punct(.AndAnd), "TOK_AND_AND");
expect_kind_name(.Punct(.AndAssign), "TOK_ASSIGN_AND");
expect_kind_name(.Punct(.And), "TOK_AND");
expect_kind_name(.Punct(.OrOr), "TOK_OR_OR");
expect_kind_name(.Punct(.OrAssign), "TOK_ASSIGN_OR");
expect_kind_name(.Punct(.Or), "TOK_OR");
expect_kind_name(.Punct(.XorAssign), "TOK_ASSIGN_XOR");
expect_kind_name(.Punct(.Xor), "TOK_XOR");
expect_kind_name(.Punct(.LShAssign), "TOK_ASSIGN_L_SH");
expect_kind_name(.Punct(.LSh), "TOK_L_SH");
expect_kind_name(.Punct(.Le), "TOK_LE");
expect_kind_name(.Punct(.Lt), "TOK_LT");
expect_kind_name(.Punct(.RShAssign), "TOK_ASSIGN_R_SH");
expect_kind_name(.Punct(.RSh), "TOK_R_SH");
expect_kind_name(.Punct(.Ge), "TOK_GE");
expect_kind_name(.Punct(.Gt), "TOK_GT");
expect_kind_name(.Punct(.Not), "TOK_NOT");
expect_kind_name(.Punct(.Neq), "TOK_NEQ");
expect_kind_name(.Punct(.BitNot), "TOK_BIT_NOT");
expect_kind_name(.Punct(.LBracket), "TOK_L_BRACKET");
expect_kind_name(.Punct(.RBracket), "TOK_R_BRACKET");
expect_kind_name(.Punct(.LParen), "TOK_L_PAREN");
expect_kind_name(.Punct(.RParen), "TOK_R_PAREN");
expect_kind_name(.Punct(.LBrace), "TOK_L_BRACE");
expect_kind_name(.Punct(.RBrace), "TOK_R_BRACE");
expect_kind_name(.Punct(.Semicolon), "TOK_SEMICOLON");
expect_kind_name(.Punct(.Comma), "TOK_COMMA");
expect_kind_name(.Punct(.Colon), "TOK_COLON");
expect_kind_name(.Punct(.ColonAssign), "TOK_COLON_ASSIGN");
expect_kind_name(.Punct(.Dot), "TOK_DOT");
expect_kind_name(.Punct(.Range), "TOK_RANGE");
expect_kind_name(.Punct(.Ellipsis), "TOK_ELLIPSIS");
expect_kind_name(.Punct(.Cond), "TOK_COND");
expect_kind_name(.Punct(.Sharp), "TOK_SHARP");
expect_kind_name(.Punct(.At), "TOK_AT");
expect_kind_name(.Punct(.Dollar), "TOK_DOLLAR");
expect_kind_name(.Ident, "TOK_IDENT");
expect_kind_name(.Int(.Dec), "TOK_INT_LITERAL");
expect_kind_name(.Float, "TOK_FLOAT_LITERAL");
expect_kind_name(.Char('a'), "TOK_CHAR_LITERAL");
expect_kind_name(.Str, "TOK_STRING_LITERAL");
expect_kind_name(.LineComment, "TOK_LINE_COMMENT");
expect_kind_name(.Endline, "TOK_ENDLINE");
expect_kind_name(.Eof, "TOK_EOF");
expect_kind_name(.Unknown(0), "TOK_UNKNOWN");
}
fn main() {
test_keywords();
test_adt_payloads();
test_trivia();
test_kind_names();
}
+889
View File
@@ -0,0 +1,889 @@
// Expect retcode = 0
// IR 类型表:按结构 hash-consing。id 0 为保留哨兵(Error)。成员/参数 Vec 由本表
// 拥有;hash 仅用于选桶,相等始终由结构比较决定。
type .{ .Error } = @import("utils/error.spl");
type .{ .Vec } = @import("utils/vec.spl");
type .{ .Map, .hash_kind_usize } = @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,
}
// 变体顺序即 kind 数值;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,
}
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 {
if i >= v.len { ret null; }
ret (v.data as *Member) + i;
}
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。
fn node(self: *TypeTable, id: usize): *TypeEntry {
if id == 0 || id >= self.table.len { ret null; }
ret (self.table.data as *TypeEntry) + id;
}
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_kind_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;
}
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;
Map.put(&self.fold, &hash as *u8, &id as *u8);
}
// 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;
}
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;
}
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;
Vec.pop(stack, &popped as *u8);
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;
Vec.pop(stack, &dropped as *u8);
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(&params, &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(&params2, &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(&params, &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();
}
+325
View File
@@ -0,0 +1,325 @@
// Expect retcode = 0
// 类型擦除的搜索与稳定归并排序。元素由定长字节表示,比较回调负责解释元素和键;
// context 只借用,模块不持有元素、键或上下文。所有尺寸错误都在写入前返回。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .checked_mul } = @import("memory.spl");
type .{ .alloc, .copy, .free } = @import("../sys/mem.spl");
type SearchResult = enum {
Found: usize,
NotFound,
Err: Error,
}
fn validate_range(data: *u8, len: usize, elem_size: usize): Status {
if elem_size == 0 { ret .Err(make(.InvalidArgument)); }
match checked_mul(len, elem_size) {
.Ok(bytes) => {},
.Err(error) => { ret .Err(error); },
}
if len != 0 && data == null { ret .Err(make(.InvalidArgument)); }
ret .Ok;
}
// 返回首个相等元素。空范围不读取 data、key 或调用 compare。
fn linear_search(data: *u8, len: usize, elem_size: usize, key: *u8,
context: *u8, compare: fn(*u8, *u8, *u8): i32): SearchResult {
match validate_range(data, len, elem_size) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
if len == 0 { ret .NotFound; }
if key == null { ret .Err(make(.InvalidArgument)); }
var i: usize = 0;
while i < len {
if compare(data + i * elem_size, key, context) == 0 {
ret .Found(i);
}
i = i + 1;
}
ret .NotFound;
}
// data 必须按 compare 升序排列。存在重复值时返回首个相等元素。
fn binary_search(data: *u8, len: usize, elem_size: usize, key: *u8,
context: *u8, compare: fn(*u8, *u8, *u8): i32): SearchResult {
match validate_range(data, len, elem_size) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
if len == 0 { ret .NotFound; }
if key == null { ret .Err(make(.InvalidArgument)); }
var low: usize = 0;
var high = len;
while low < high {
var mid = low + (high - low) / 2;
if compare(data + mid * elem_size, key, context) < 0 {
low = mid + 1;
} else {
high = mid;
}
}
if low < len && compare(data + low * elem_size, key, context) == 0 {
ret .Found(low);
}
ret .NotFound;
}
fn min_size(a: usize, b: usize): usize {
if a < b { ret a; }
ret b;
}
fn merge_run(dst: *u8, src: *u8, elem_size: usize,
begin: usize, middle: usize, end: usize,
context: *u8, compare: fn(*u8, *u8, *u8): i32) {
var left = begin;
var right = middle;
var output = begin;
while left < middle && right < end {
var left_ptr = src + left * elem_size;
var right_ptr = src + right * elem_size;
// 相等时先取左侧元素,由此保证稳定性。
if compare(left_ptr, right_ptr, context) <= 0 {
copy((dst + output * elem_size) as *any, left_ptr as *any, elem_size);
left = left + 1;
} else {
copy((dst + output * elem_size) as *any, right_ptr as *any, elem_size);
right = right + 1;
}
output = output + 1;
}
while left < middle {
copy((dst + output * elem_size) as *any,
(src + left * elem_size) as *any, elem_size);
left = left + 1;
output = output + 1;
}
while right < end {
copy((dst + output * elem_size) as *any,
(src + right * elem_size) as *any, elem_size);
right = right + 1;
output = output + 1;
}
}
// 自底向上的稳定归并排序。分配失败及参数错误不改变输入。
fn stable_sort(data: *u8, len: usize, elem_size: usize,
context: *u8, compare: fn(*u8, *u8, *u8): i32): Status {
var bytes: usize = 0;
match validate_range(data, len, elem_size) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
match checked_mul(len, elem_size) {
.Ok(value) => { bytes = value; },
.Err(error) => { ret .Err(error); },
}
if len < 2 { ret .Ok; }
var scratch = alloc(bytes) as *u8;
if scratch == null { ret .Err(make(.Overflow)); }
defer { free(scratch as *any); }
var source = data;
var destination = scratch;
var width: usize = 1;
while width < len {
var begin: usize = 0;
while begin < len {
var left_width = min_size(width, len - begin);
var middle = begin + left_width;
var remaining = len - middle;
var right_width = min_size(width, remaining);
var end = middle + right_width;
merge_run(destination, source, elem_size, begin, middle, end,
context, compare);
begin = end;
}
var swap = source;
source = destination;
destination = swap;
if width > len / 2 {
width = len;
} else {
width = width * 2;
}
}
if source != data {
copy(data as *any, source as *any, bytes);
}
ret .Ok;
}
type Item = struct {
key: i32,
sequence: i32,
}
fn compare_i32(a: *u8, b: *u8, context: *u8): i32 {
var av = (a as *i32).*;
var bv = (b as *i32).*;
if av < bv { ret -1; }
if av > bv { ret 1; }
ret 0;
}
fn compare_item(a: *u8, b: *u8, context: *u8): i32 {
var av = (a as *Item).*.key;
var bv = (b as *Item).*.key;
var direction: i32 = 1;
if context != null { direction = (context as *i32).*; }
if av < bv { ret -direction; }
if av > bv { ret direction; }
ret 0;
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected algorithm error"); },
}
}
fn expect_found(result: SearchResult, expected: usize) {
match result {
.Found(index) => { @assert(index == expected, "search index"); },
.NotFound => { @assert(false, "expected search match"); },
.Err(error) => { @assert(false, "unexpected search error"); },
}
}
fn expect_missing(result: SearchResult) {
match result {
.Found(index) => { @assert(false, "unexpected search match"); },
.NotFound => {},
.Err(error) => { @assert(false, "unexpected search error"); },
}
}
fn expect_search_error(result: SearchResult, overflow: bool) {
match result {
.Found(index) => { @assert(false, "expected search error"); },
.NotFound => { @assert(false, "expected search error"); },
.Err(error) => {
match error.kind {
.Overflow => { @assert(overflow); },
.InvalidArgument => { @assert(!overflow); },
_ => { @assert(false, "unexpected search error kind"); },
}
},
}
}
fn test_search() {
var values: [8]i32 = .[1, 2, 2, 2, 5, 8, 13, 21];
var two: i32 = 2;
var seven: i32 = 7;
expect_found(linear_search(&values[0] as *u8, 8, @sizeof(i32),
&two as *u8, null, compare_i32), 1);
expect_found(binary_search(&values[0] as *u8, 8, @sizeof(i32),
&two as *u8, null, compare_i32), 1);
expect_missing(linear_search(&values[0] as *u8, 8, @sizeof(i32),
&seven as *u8, null, compare_i32));
expect_missing(binary_search(&values[0] as *u8, 8, @sizeof(i32),
&seven as *u8, null, compare_i32));
expect_missing(linear_search(null, 0, @sizeof(i32), null, null, compare_i32));
expect_missing(binary_search(null, 0, @sizeof(i32), null, null, compare_i32));
expect_search_error(linear_search(&values[0] as *u8, 8, 0,
&two as *u8, null, compare_i32), false);
expect_search_error(binary_search(null, 1, @sizeof(i32),
&two as *u8, null, compare_i32), false);
var huge = (~(0 as usize)) / @sizeof(i32) + 1;
expect_search_error(linear_search(&values[0] as *u8, huge, @sizeof(i32),
&two as *u8, null, compare_i32), true);
}
fn test_stable_sort() {
var items: [8]Item = .[
.{ .key = 3, .sequence = 0 },
.{ .key = 1, .sequence = 1 },
.{ .key = 2, .sequence = 2 },
.{ .key = 1, .sequence = 3 },
.{ .key = 3, .sequence = 4 },
.{ .key = 2, .sequence = 5 },
.{ .key = 1, .sequence = 6 },
.{ .key = 2, .sequence = 7 },
];
var ascending: i32 = 1;
expect_ok(stable_sort(&items[0] as *u8, 8, @sizeof(Item),
&ascending as *u8, compare_item));
var expected_keys: [8]i32 = .[1, 1, 1, 2, 2, 2, 3, 3];
var expected_sequence: [8]i32 = .[1, 3, 6, 2, 5, 7, 0, 4];
var i: usize = 0;
while i < 8 {
@assert(items[i].key == expected_keys[i], "sorted key");
@assert(items[i].sequence == expected_sequence[i], "stable equal order");
i = i + 1;
}
var descending: i32 = -1;
expect_ok(stable_sort(&items[0] as *u8, 8, @sizeof(Item),
&descending as *u8, compare_item));
i = 1;
while i < 8 {
@assert(items[i - 1].key >= items[i].key, "context controls order");
if items[i - 1].key == items[i].key {
@assert(items[i - 1].sequence < items[i].sequence,
"second stable sort preserves group order");
}
i = i + 1;
}
expect_ok(stable_sort(null, 0, @sizeof(Item), null, compare_item));
expect_ok(stable_sort(&items[0] as *u8, 1, @sizeof(Item), null, compare_item));
// 非二次幂长度覆盖末尾不足一个完整归并段的路径。
var odd: [9]i32 = .[9, -1, 4, 4, 0, 8, 2, 7, 3];
var odd_expected: [9]i32 = .[-1, 0, 2, 3, 4, 4, 7, 8, 9];
expect_ok(stable_sort(&odd[0] as *u8, 9, @sizeof(i32), null, compare_i32));
i = 0;
while i < 9 {
@assert(odd[i] == odd_expected[i], "non-power-of-two length");
i = i + 1;
}
}
fn test_sort_failures_preserve_input() {
var values: [3]i32 = .[3, 1, 2];
match stable_sort(&values[0] as *u8, 3, 0, null, compare_i32) {
.Ok => { @assert(false, "zero element size"); },
.Err(error) => {
match error.kind {
.InvalidArgument => {},
_ => { @assert(false); },
}
},
}
@assert(values[0] == 3 && values[1] == 1 && values[2] == 2,
"validation failure preserves input");
var huge = (~(0 as usize)) / @sizeof(i32) + 1;
match stable_sort(&values[0] as *u8, huge, @sizeof(i32), null, compare_i32) {
.Ok => { @assert(false, "size overflow"); },
.Err(error) => {
match error.kind {
.Overflow => {},
_ => { @assert(false); },
}
},
}
@assert(values[0] == 3 && values[1] == 1 && values[2] == 2,
"overflow preserves input");
}
fn main() {
test_search();
test_stable_sort();
test_sort_failures_preserve_input();
}
+211
View File
@@ -0,0 +1,211 @@
// Expect retcode = 0
// 分块 bump 分配器:按块分配、块内顺序推进,只在 drop 时整体释放。
// 适合 AST/IR 这类生命周期一致的批量小对象;不支持单独 free。
// 分配失败通过 ArenaResult 区分原因:size==0/非法对齐/内存不足/地址溢出。
// 失败的 alloc 不改变已有分配。
type .{ .Vec } = @import("vec.spl");
type .{ .Error, .Kind, .Status } = @import("error.spl");
type .{ .checked_add, .align_up } = @import("memory.spl");
type Mem = @import("../sys/mem.spl");
type Chunk = struct {
base: *u8,
size: usize,
used: usize,
}
type ArenaResult = enum {
Ok: *u8,
Err: Error,
}
type Arena = @self;
chunk_size: usize,
chunks: Vec, // 元素类型 Chunk
current: usize,
fn new(chunk_size: usize): Arena {
var cs = chunk_size;
if cs == 0 { cs = 4096; }
ret Arena {
.chunk_size = cs,
.chunks = Vec.blank(@sizeof(Chunk), 8),
.current = 0,
};
}
fn chunk_at(self: *Arena, index: usize): *Chunk {
match Vec.get(&self.chunks, index) {
.Ok(ptr) => { ret ptr as *Chunk; },
// 越界/无效 Arena 统一返回 null;调用方负责判空,无资源需要释放。
.Err(error) => { ret null; },
}
ret null;
}
fn push_chunk(self: *Arena, size: usize): Status {
var base = Mem.alloc(size) as *u8;
if base == null { ret .Err(Error.make(.Overflow)); }
var c = Chunk { .base = base, .size = size, .used = 0 };
match Vec.push(&self.chunks, &c as *u8) {
.Ok => {},
.Err(error) => { Mem.free(base as *any); ret .Err(error); },
}
self.current = self.chunks.len - 1;
ret .Ok;
}
// 分配 size 字节,地址按 align(非零二次幂)对齐。失败返回 ArenaResult.Err。
fn alloc(self: *Arena, size: usize, align: usize): ArenaResult {
if size == 0 { ret .Err(Error.make(.InvalidArgument)); }
if align == 0 { align = 1; }
while true {
if self.current >= self.chunks.len {
var want = self.chunk_size;
var need: usize = 0;
match checked_add(size, align) {
.Ok(value) => { need = value; },
.Err(error) => { ret .Err(error); },
}
if need > want { want = need; }
match push_chunk(self, want) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
}
var c = chunk_at(self, self.current);
if c == null { ret .Err(Error.make(.InvalidArgument)); }
var addr = c.base as usize;
var used_addr: usize = 0;
match checked_add(addr, c.used) {
.Ok(value) => { used_addr = value; },
.Err(error) => { ret .Err(error); },
}
var aligned: usize = 0;
match align_up(used_addr, align) {
.Ok(value) => { aligned = value; },
.Err(error) => { ret .Err(error); }, // 非二次幂对齐或地址溢出
}
var offset = aligned - addr;
if offset <= c.size && size <= c.size - offset {
c.used = offset + size;
ret .Ok(aligned as *u8);
}
self.current = self.current + 1;
}
ret .Err(Error.make(.OutOfRange)); // 不可达:循环内所有路径均已返回
}
fn chunk_count(self: Arena): usize {
ret self.chunks.len;
}
// 复用已分配的块,不释放内存。
fn reset(self: *Arena) {
var i: usize = 0;
while i < self.chunks.len {
var c = chunk_at(self, i);
if c != null { c.used = 0; }
i = i + 1;
}
self.current = 0;
}
fn drop(self: *Arena) {
var i: usize = 0;
while i < self.chunks.len {
var c = chunk_at(self, i);
if c != null { Mem.free(c.base as *any); }
i = i + 1;
}
Vec.drop(&self.chunks);
self.current = 0;
}
fn is_aligned(p: *u8, align: usize): bool {
ret ((p as usize) & (align - 1)) == 0;
}
fn alloc_ok(a: *Arena, size: usize, align: usize): *u8 {
match alloc(a, size, align) {
.Ok(ptr) => { ret ptr; },
.Err(error) => { @assert(false, "unexpected arena alloc error"); },
}
ret null;
}
fn expect_alloc_error(result: ArenaResult, expected: Kind) {
match result {
.Ok(ptr) => { @assert(false, "expected arena alloc error"); },
.Err(error) => {
var want: Kind = expected;
@assert(@enum2int(error.kind) == @enum2int(want), "arena alloc error kind");
},
}
}
fn test_alloc_and_alignment() {
var a = new(64);
@assert(chunk_count(a) == 0, "lazy");
var p1 = alloc_ok(&a, 1, 1);
var p8 = alloc_ok(&a, 16, 8);
var p16 = alloc_ok(&a, 1, 16);
@assert(p1 != null && p8 != null && p16 != null, "alloc");
@assert(p1 != p8 && p8 != p16 && p1 != p16, "distinct");
@assert(is_aligned(p8, 8), "align 8");
@assert(is_aligned(p16, 16), "align 16");
p1[0] = 7;
@assert(p1[0] == 7, "writable");
@assert(p8[0] == p8[0], "no crash");
@assert(chunk_count(a) == 1, "one chunk");
reset(&a);
@assert(chunk_count(a) == 1, "reset keeps chunks");
var q = alloc_ok(&a, 1, 1);
@assert(q == p1, "reset reuses first chunk");
// drop 后还有断言,故保持手动释放,不改为 defer。
drop(&a);
@assert(chunk_count(a) == 0, "drop empties");
}
fn test_growth() {
var a = new(64);
defer { drop(&a); }
var i: i32 = 0;
while i < 100 {
var p = alloc_ok(&a, 16, 8);
@assert(p != null, "grow alloc");
@assert(is_aligned(p, 8), "grow align");
p[0] = i as u8;
i = i + 1;
}
@assert(chunk_count(a) > 1, "grew multiple chunks");
// 超过块大小的单次分配也能成功(新块按需放大)。
var big = alloc_ok(&a, 1024, 16);
@assert(big != null && is_aligned(big, 16), "oversized alloc");
}
fn test_failures() {
var a = new(64);
defer { drop(&a); }
expect_alloc_error(alloc(&a, 0, 1), .InvalidArgument);
var d = alloc_ok(&a, 8, 0);
@assert(d != null && is_aligned(d, 1), "align 0 defaults to 1");
expect_alloc_error(alloc(&a, 8, 3), .InvalidAlignment);
}
fn main() {
test_alloc_and_alignment();
test_growth();
test_failures();
}
+218
View File
@@ -0,0 +1,218 @@
// Expect retcode = 0
// ASCII 字符工具。输入和输出都是单个字节,不解释 UTF-8 或本地化规则。
type .{ .Error, .make } = @import("error.spl");
type DigitResult = enum {
Ok: u8,
Err: Error,
}
fn is_ascii(c: u8): bool {
ret c <= 127;
}
fn is_control(c: u8): bool {
ret c <= 31 || c == 127;
}
fn is_space(c: u8): bool {
ret c == ' ' || (c >= '\t' && c <= '\r');
}
fn is_blank(c: u8): bool {
ret c == ' ' || c == '\t';
}
fn is_digit(c: u8): bool {
ret c >= '0' && c <= '9';
}
fn is_lower(c: u8): bool {
ret c >= 'a' && c <= 'z';
}
fn is_upper(c: u8): bool {
ret c >= 'A' && c <= 'Z';
}
fn is_alpha(c: u8): bool {
ret is_lower(c) || is_upper(c);
}
fn is_alnum(c: u8): bool {
ret is_alpha(c) || is_digit(c);
}
fn is_hex_digit(c: u8): bool {
ret is_digit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}
fn is_print(c: u8): bool {
ret c >= ' ' && c <= '~';
}
fn is_graph(c: u8): bool {
ret c >= '!' && c <= '~';
}
fn is_punct(c: u8): bool {
ret is_graph(c) && !is_alnum(c);
}
fn to_lower(c: u8): u8 {
if is_upper(c) { ret c + ('a' - 'A'); }
ret c;
}
fn to_upper(c: u8): u8 {
if is_lower(c) { ret c - ('a' - 'A'); }
ret c;
}
// radix 必须位于 2..36。大小写字母均表示 10..35。
fn digit_value(c: u8, radix: u8): DigitResult {
if radix < 2 || radix > 36 {
ret .Err(make(.InvalidArgument));
}
var value: u8 = 0;
if is_digit(c) {
value = c - '0';
} else if is_upper(c) {
value = c - 'A' + 10;
} else if is_lower(c) {
value = c - 'a' + 10;
} else {
ret .Err(make(.InvalidDigit));
}
if value >= radix { ret .Err(make(.InvalidDigit)); }
ret .Ok(value);
}
// value 必须位于 0..35。uppercase 只影响 10..35 的字母形式。
fn digit_char(value: u8, uppercase: bool): DigitResult {
if value >= 36 { ret .Err(make(.OutOfRange)); }
if value < 10 { ret .Ok(value + '0'); }
if uppercase { ret .Ok(value - 10 + 'A'); }
ret .Ok(value - 10 + 'a');
}
fn expect_digit(result: DigitResult, expected: u8) {
match result {
.Ok(value) => { @assert(value == expected, "digit value"); },
.Err(error) => { @assert(false, "unexpected digit error"); },
}
}
fn expect_error(result: DigitResult, expected_invalid_argument: bool) {
match result {
.Ok(value) => { @assert(false, "expected digit error"); },
.Err(error) => {
match error.kind {
.InvalidArgument => {
@assert(expected_invalid_argument, "unexpected invalid radix");
},
.InvalidDigit => {
@assert(!expected_invalid_argument, "unexpected invalid digit");
},
_ => { @assert(false, "unexpected digit error kind"); },
}
},
}
}
fn expect_range_error(result: DigitResult) {
match result {
.Ok(value) => { @assert(false, "expected digit range error"); },
.Err(error) => {
match error.kind {
.OutOfRange => {},
_ => { @assert(false, "unexpected digit range error kind"); },
}
},
}
}
fn test_all_bytes() {
var i: usize = 0;
while i < 256 {
var c = i as u8;
@assert(is_ascii(c) == (i <= 127), "ASCII boundary");
@assert(is_control(c) == (i <= 31 || i == 127), "control boundary");
@assert(is_print(c) == (c >= ' ' && c <= '~'), "print boundary");
@assert(is_graph(c) == (c >= '!' && c <= '~'), "graph boundary");
@assert(is_blank(c) == (c == '\t' || c == ' '), "blank set");
@assert(is_space(c) == (c == '\t' || c == '\n' || c == '\x0B' || c == '\x0C' ||
c == '\r' || c == ' '),
"space set");
@assert(is_digit(c) == (c >= '0' && c <= '9'), "digit set");
@assert(is_upper(c) == (c >= 'A' && c <= 'Z'), "upper set");
@assert(is_lower(c) == (c >= 'a' && c <= 'z'), "lower set");
@assert(is_hex_digit(c) == ((c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'F') ||
(c >= 'a' && c <= 'f')),
"hex digit set");
@assert(is_alpha(c) == (is_upper(c) || is_lower(c)), "alpha relation");
@assert(is_alnum(c) == (is_alpha(c) || is_digit(c)), "alnum relation");
@assert(is_punct(c) == (is_graph(c) && !is_alnum(c)), "punct relation");
if is_upper(c) {
@assert(to_lower(c) == c + ('a' - 'A'), "upper to lower");
@assert(to_upper(to_lower(c)) == c, "case round trip");
} else if is_lower(c) {
@assert(to_upper(c) == c - ('a' - 'A'), "lower to upper");
@assert(to_lower(to_upper(c)) == c, "case round trip");
} else {
@assert(to_lower(c) == c, "lower preserves non-letter");
@assert(to_upper(c) == c, "upper preserves non-letter");
}
i = i + 1;
}
}
fn test_digits() {
var radix: usize = 2;
while radix <= 36 {
var value: usize = 0;
while value < radix {
var lower: u8 = 0;
match digit_char(value as u8, false) {
.Ok(c) => { lower = c; },
.Err(error) => { @assert(false, "lower digit generation"); },
}
expect_digit(digit_value(lower, radix as u8), value as u8);
var upper: u8 = 0;
match digit_char(value as u8, true) {
.Ok(c) => { upper = c; },
.Err(error) => { @assert(false, "upper digit generation"); },
}
expect_digit(digit_value(upper, radix as u8), value as u8);
if value < 10 {
@assert(lower == upper, "decimal digits ignore case");
} else {
@assert(lower == upper + ('a' - 'A'), "letter digit case distance");
}
value = value + 1;
}
radix = radix + 1;
}
expect_error(digit_value('0', 0), true);
expect_error(digit_value('0', 1), true);
expect_error(digit_value('0', 37), true);
expect_error(digit_value('/', 10), false);
expect_error(digit_value(':', 10), false);
expect_error(digit_value('2', 2), false);
expect_error(digit_value('g', 16), false);
expect_range_error(digit_char(36, false));
expect_range_error(digit_char(255, true));
}
fn main() {
test_all_bytes();
test_digits();
}
+323
View File
@@ -0,0 +1,323 @@
// Expect retcode = 0
// 拥有字节存储的动态缓冲区。普通赋值会复制所有权,转移必须使用 take。
// 借用切片和 z_ptr 返回的指针在下一次可能扩容的操作或 drop 后失效。
type .{ .Error, .Status } = @import("error.spl");
type .{ .checked_add, .move } = @import("memory.spl");
type .{ .Vec } = @import("vec.spl");
type ByteBuffer = @self;
storage: Vec,
type PtrResult = enum {
Ok: *u8,
Err: Error,
}
fn new(): ByteBuffer {
match Vec.new(1, 1) {
.Ok(value) => { ret ByteBuffer { .storage = value }; },
.Err(error) => { @assert(false, "fixed byte layout is valid"); },
}
ret ByteBuffer { .storage = Vec.blank(1, 1) };
}
fn with_capacity(cap: usize): ByteBuffer {
var b = new();
reserve(&b, cap);
ret b;
}
fn size(self: ByteBuffer): usize {
ret self.storage.len;
}
fn capacity(self: ByteBuffer): usize {
ret self.storage.cap;
}
fn is_empty(self: ByteBuffer): bool {
ret self.storage.len == 0;
}
fn as_slice(self: ByteBuffer): []u8 {
ret .{ .ptr = self.storage.data, .len = self.storage.len };
}
fn reserve(self: *ByteBuffer, capacity_needed: usize): Status {
ret Vec.reserve(&self.storage, capacity_needed);
}
fn append(self: *ByteBuffer, bytes: []u8): Status {
ret Vec.append(&self.storage, bytes.ptr, bytes.len);
}
fn append_byte(self: *ByteBuffer, byte: u8): Status {
ret Vec.push(&self.storage, &byte);
}
fn append_repeat(self: *ByteBuffer, byte: u8, count: usize): Status {
if count == 0 { ret .Ok; }
var new_len: usize = 0;
match checked_add(self.storage.len, count) {
.Ok(value) => { new_len = value; },
.Err(error) => { ret .Err(error); },
}
match reserve(self, new_len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
var i = self.storage.len;
while i < new_len {
self.storage.data[i] = byte;
i = i + 1;
}
self.storage.len = new_len;
ret .Ok;
}
// 扩展时用 fill 初始化新增字节;缩短只改变逻辑长度。
fn resize(self: *ByteBuffer, new_len: usize, fill: u8): Status {
if new_len <= self.storage.len {
self.storage.len = new_len;
ret .Ok;
}
var count = new_len - self.storage.len;
ret append_repeat(self, fill, count);
}
fn clear(self: *ByteBuffer) {
Vec.clear(&self.storage);
}
// 保证 data[len] 为零并返回 data。终止零不属于逻辑内容。
// 缓冲区内容可以包含更早的零字节,调用方决定是否适合作为 C 字符串。
fn z_ptr(self: *ByteBuffer): PtrResult {
var needed: usize = 0;
match checked_add(self.storage.len, 1) {
.Ok(value) => { needed = value; },
.Err(error) => { ret .Err(error); },
}
match reserve(self, needed) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
self.storage.data[self.storage.len] = 0;
ret .Ok(self.storage.data);
}
// 成功时替换 out 的旧内容;失败保持 out 不变。允许 bytes 借用 out 自身。
fn assign(out: *ByteBuffer, bytes: []u8): Status {
if bytes.len == 0 {
clear(out);
ret .Ok;
}
if bytes.ptr == null { ret .Err(Error.make(.InvalidArgument)); }
var used = out.storage.len;
if used != 0 {
var base = out.storage.data as usize;
var source = bytes.ptr as usize;
if source >= base && source - base < used {
var offset = source - base;
if bytes.len > used - offset {
ret .Err(Error.make(.OutOfRange));
}
match move(out.storage.data[0..bytes.len], bytes) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
out.storage.len = bytes.len;
ret .Ok;
}
}
var fresh = new();
match append(&fresh, bytes) {
.Ok => {},
.Err(error) => {
drop(&fresh);
ret .Err(error);
},
}
drop(out);
out.storage = Vec.take(&fresh.storage);
ret .Ok;
}
// 深复制;允许 out 与 source 是同一对象。
fn clone(out: *ByteBuffer, source: ByteBuffer): Status {
ret Vec.clone(&out.storage, source.storage);
}
fn take(source: *ByteBuffer): ByteBuffer {
ret ByteBuffer { .storage = Vec.take(&source.storage) };
}
fn drop(self: *ByteBuffer) {
Vec.drop(&self.storage);
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => {
match error.kind {
.InvalidArgument => { @assert(false, "unexpected invalid argument"); },
.OutOfRange => { @assert(false, "unexpected out of range"); },
.Overflow => { @assert(false, "unexpected overflow"); },
_ => { @assert(false, "unexpected byte buffer error"); },
}
},
}
}
fn expect_overflow(result: Status) {
match result {
.Ok => { @assert(false, "expected byte buffer overflow"); },
.Err(error) => {
match error.kind {
.Overflow => {},
_ => { @assert(false, "unexpected byte buffer error kind"); },
}
},
}
}
fn expect_bytes(self: ByteBuffer, expected: []u8) {
@assert(self.storage.len == expected.len, "byte buffer length");
var i: usize = 0;
while i < expected.len {
@assert(self.storage.data[i] == expected.ptr[i], "byte buffer content");
i = i + 1;
}
}
fn expect_z(self: *ByteBuffer) {
var old_len = self.storage.len;
match z_ptr(self) {
.Ok(ptr) => {
@assert(ptr != null, "zero terminated pointer");
@assert(ptr[old_len] == 0, "zero terminator");
},
.Err(error) => { @assert(false, "zero termination"); },
}
@assert(self.storage.len == old_len, "terminator is outside logical content");
}
fn test_append_and_resize() {
var buffer = new();
defer { drop(&buffer); }
@assert(is_empty(buffer));
@assert(size(buffer) == 0 && capacity(buffer) == 0);
expect_z(&buffer);
@assert(capacity(buffer) >= 1);
expect_ok(append(&buffer, "abc"));
expect_ok(append_byte(&buffer, '\0'));
expect_ok(append(&buffer, "de"));
@assert(buffer.storage.len == 6, "append logical length");
var embedded: [6]u8 = .[97, 98, 99, 0, 100, 101];
expect_bytes(buffer, embedded[0..6]);
expect_z(&buffer);
expect_ok(resize(&buffer, 9, 255));
@assert(buffer.storage.len == 9, "grow logical length");
var grown: [9]u8 = .[97, 98, 99, 0, 100, 101, 255, 255, 255];
expect_bytes(buffer, grown[0..9]);
expect_ok(resize(&buffer, 2, 17));
@assert(buffer.storage.len == 2, "shrink logical length");
expect_bytes(buffer, "ab");
expect_ok(append_repeat(&buffer, 120, 4));
@assert(buffer.storage.len == 6, "repeat logical length");
expect_bytes(buffer, "abxxxx");
clear(&buffer);
@assert(is_empty(buffer) && capacity(buffer) != 0, "clear retains allocation");
expect_z(&buffer);
}
fn test_growth_and_self_append() {
var buffer = new();
defer { drop(&buffer); }
var i: usize = 0;
while i < 8 {
expect_ok(append_byte(&buffer, (65 + i) as u8));
i = i + 1;
}
@assert(buffer.storage.cap == 8, "prepare byte buffer growth boundary");
var own = as_slice(buffer);
expect_ok(append(&buffer, own));
@assert(buffer.storage.len == 16);
i = 0;
while i < 16 {
@assert(buffer.storage.data[i] == (65 + i % 8) as u8, "self append content");
i = i + 1;
}
expect_z(&buffer);
}
fn test_ownership_and_failures() {
var source = new();
defer { drop(&source); }
expect_ok(append(&source, "owned"));
var copy = new();
defer { drop(&copy); }
expect_ok(clone(&copy, source));
@assert(copy.storage.len == 5, "clone logical length");
source.storage.data[0] = 79;
expect_bytes(copy, "owned");
var before_self_clone = copy.storage.raw;
expect_ok(clone(&copy, copy));
@assert(copy.storage.raw != before_self_clone, "self clone replaces nested allocation");
expect_bytes(copy, "owned");
var before_external_assign = copy.storage.data;
expect_ok(assign(&copy, "xyz"));
@assert(copy.storage.len == 3, "external assign logical length");
@assert(copy.storage.data != before_external_assign, "external assign replaces data");
expect_bytes(copy, "xyz");
before_external_assign = copy.storage.data;
expect_ok(assign(&copy, "owned"));
@assert(copy.storage.len == 5, "second external assign logical length");
@assert(copy.storage.data != before_external_assign, "second external assign replaces data");
// assign 的输入借用目标自身,提交前必须保持旧分配有效。
var middle = as_slice(copy);
middle.ptr = middle.ptr + 1;
middle.len = 3;
match assign(&copy, middle) {
.Ok => {},
.Err(error) => { @assert(false, "self assign failed"); },
}
@assert(copy.storage.len == 3, "assign logical length");
expect_bytes(copy, "wne");
var moved = take(&copy);
defer { drop(&moved); }
@assert(copy.storage.raw == null && copy.storage.len == 0 && copy.storage.cap == 0);
expect_bytes(moved, "wne");
var old_raw = moved.storage.raw;
var old_len = moved.storage.len;
var old_cap = moved.storage.cap;
expect_overflow(append_repeat(&moved, 1, ~(0 as usize)));
@assert(moved.storage.raw == old_raw && moved.storage.len == old_len &&
moved.storage.cap == old_cap,
"overflow preserves byte buffer");
expect_bytes(moved, "wne");
}
fn main() {
test_append_and_resize();
test_growth_and_self_append();
test_ownership_and_failures();
}
+281
View File
@@ -0,0 +1,281 @@
// Expect retcode = 0
// 借用的字节字符串视图;不拥有、不分配也不释放 ptr 指向的存储。
// 非空视图要求 ptr 在整个借用期内可读。空视图统一表示为 null/0。
type .{ .Error, .make } = @import("error.spl");
#extern(vm) fn vm_bytes_eq(a: *any, alen: usize, b: *any, blen: usize): i32;
type ByteView = @self;
ptr: *u8,
len: usize,
type ByteResult = enum {
Ok: u8,
Err: Error,
}
type ViewResult = enum {
Ok: ByteView,
Err: Error,
}
type FindResult = enum {
Found: usize,
NotFound,
}
fn empty(): ByteView {
ret ByteView { .ptr = null, .len = 0 };
}
// 输入切片的存储仍由调用方拥有。零长度输入被规范化为空视图。
fn from_slice(bytes: []u8): ByteView {
if bytes.len == 0 { ret empty(); }
ret ByteView { .ptr = bytes.ptr, .len = bytes.len };
}
// 以 NUL 结尾的 C 字符串转为视图;不复制、不拥有。null 规范化为空视图。
fn from_cstr(text: *u8): ByteView {
if text == null { ret empty(); }
var len: usize = 0;
while text[len] != 0 { len = len + 1; }
ret from_slice(.{ .ptr = text, .len = len });
}
fn as_slice(self: ByteView): []u8 {
ret .{ .ptr = self.ptr, .len = self.len };
}
fn size(self: ByteView): usize {
ret self.len;
}
fn is_empty(self: ByteView): bool {
ret self.len == 0;
}
fn byte_at(self: ByteView, index: usize): ByteResult {
if index >= self.len { ret .Err(make(.OutOfRange)); }
if self.ptr == null { ret .Err(make(.InvalidArgument)); }
ret .Ok(self.ptr[index]);
}
fn eq(a: ByteView, b: ByteView): bool {
ret vm_bytes_eq(a.ptr as *any, a.len, b.ptr as *any, b.len) != 0;
}
// 按无符号字节进行字典序比较,结果只承诺小于、等于或大于零。
fn cmp(a: ByteView, b: ByteView): i32 {
var count = a.len;
if b.len < count { count = b.len; }
var i: usize = 0;
while i < count {
var av = a.ptr[i];
var bv = b.ptr[i];
if av < bv { ret -1; }
if av > bv { ret 1; }
i = i + 1;
}
if a.len < b.len { ret -1; }
if a.len > b.len { ret 1; }
ret 0;
}
// 返回半开区间 [start, end)。失败不会产生部分视图。
fn slice(self: ByteView, start: usize, end: usize): ViewResult {
if start > end || end > self.len {
ret .Err(make(.OutOfRange));
}
if start == end { ret .Ok(empty()); }
if self.ptr == null { ret .Err(make(.InvalidArgument)); }
var address = self.ptr as usize;
var limit: usize = ~(0 as usize);
if start > limit - address { ret .Err(make(.Overflow)); }
ret .Ok(ByteView { .ptr = (address + start) as *u8, .len = end - start });
}
fn find(haystack: ByteView, needle: ByteView): FindResult {
if needle.len == 0 { ret .Found(0); }
if needle.len > haystack.len { ret .NotFound; }
var last = haystack.len - needle.len;
var start: usize = 0;
while start <= last {
var i: usize = 0;
while i < needle.len && haystack.ptr[start + i] == needle.ptr[i] {
i = i + 1;
}
if i == needle.len { ret .Found(start); }
start = start + 1;
}
ret .NotFound;
}
fn contains(haystack: ByteView, needle: ByteView): bool {
match find(haystack, needle) {
.Found(index) => { ret true; },
.NotFound => { ret false; },
}
}
fn starts_with(self: ByteView, prefix: ByteView): bool {
if prefix.len > self.len { ret false; }
var i: usize = 0;
while i < prefix.len {
if self.ptr[i] != prefix.ptr[i] { ret false; }
i = i + 1;
}
ret true;
}
fn ends_with(self: ByteView, suffix: ByteView): bool {
if suffix.len > self.len { ret false; }
var start = self.len - suffix.len;
var i: usize = 0;
while i < suffix.len {
if self.ptr[start + i] != suffix.ptr[i] { ret false; }
i = i + 1;
}
ret true;
}
fn expect_byte(result: ByteResult, expected: u8) {
match result {
.Ok(value) => { @assert(value == expected, "byte result"); },
.Err(error) => { @assert(false, "unexpected byte error"); },
}
}
fn expect_byte_error(result: ByteResult, out_of_range: bool) {
match result {
.Ok(value) => { @assert(false, "expected byte error"); },
.Err(error) => {
match error.kind {
.OutOfRange => { @assert(out_of_range, "unexpected byte range error"); },
.InvalidArgument => { @assert(!out_of_range, "unexpected invalid view"); },
_ => { @assert(false, "unexpected byte error kind"); },
}
},
}
}
fn expect_slice(result: ViewResult, expected: ByteView) {
match result {
.Ok(value) => { @assert(eq(value, expected), "slice result"); },
.Err(error) => { @assert(false, "unexpected slice error"); },
}
}
fn expect_slice_error(result: ViewResult) {
match result {
.Ok(value) => { @assert(false, "expected slice error"); },
.Err(error) => {
match error.kind {
.OutOfRange => {},
_ => { @assert(false, "unexpected slice error kind"); },
}
},
}
}
fn expect_found(result: FindResult, expected: usize) {
match result {
.Found(index) => { @assert(index == expected, "find position"); },
.NotFound => { @assert(false, "expected match"); },
}
}
fn expect_not_found(result: FindResult) {
match result {
.Found(index) => { @assert(false, "unexpected match"); },
.NotFound => {},
}
}
fn test_construction_and_access() {
var canonical = empty();
@assert(canonical.ptr == null && canonical.len == 0, "canonical empty");
var data: [5]u8 = .[104, 101, 0, 108, 111];
var text = from_slice(data[0..5]);
@assert(size(text) == 5);
@assert(!is_empty(text));
expect_byte(byte_at(text, 0), 104);
expect_byte(byte_at(text, 2), 0);
expect_byte(byte_at(text, 4), 111);
expect_byte_error(byte_at(text, 5), true);
var normalized = from_slice(data[3..3]);
@assert(normalized.ptr == null && normalized.len == 0, "empty input normalized");
expect_byte_error(byte_at(ByteView { .ptr = null, .len = 1 }, 0), false);
var bytes = as_slice(text);
@assert(bytes.ptr == text.ptr && bytes.len == text.len, "slice conversion borrows");
var cstr_data: [4]u8 = .[104, 105, 0, 88];
var cstr = from_cstr(&cstr_data[0]);
@assert(cstr.len == 2 && cstr.ptr[0] == 104 && cstr.ptr[1] == 105,
"from_cstr stops at NUL");
@assert(from_cstr(&cstr_data[2]).len == 0, "from_cstr at NUL is empty");
var none = from_cstr(null);
@assert(none.ptr == null && none.len == 0, "from_cstr null normalized");
}
fn test_compare_and_slice() {
var hello = from_slice("hello");
var same = from_slice("hello");
var help = from_slice("help");
var high_data: [1]u8 = .[255];
var low_data: [1]u8 = .[127];
@assert(eq(hello, same));
@assert(!eq(hello, help));
@assert(eq(empty(), from_slice("")));
@assert(cmp(hello, same) == 0);
@assert(cmp(hello, help) < 0);
@assert(cmp(help, hello) > 0);
@assert(cmp(from_slice(low_data[0..1]), from_slice(high_data[0..1])) < 0,
"comparison uses unsigned bytes");
expect_slice(slice(hello, 0, 5), hello);
expect_slice(slice(hello, 1, 4), from_slice("ell"));
match slice(hello, 2, 2) {
.Ok(value) => {
@assert(value.ptr == null && value.len == 0, "empty subslice normalized");
},
.Err(error) => { @assert(false, "empty subslice"); },
}
expect_slice_error(slice(hello, 4, 3));
expect_slice_error(slice(hello, 0, 6));
}
fn test_search() {
var text = from_slice("ababa");
expect_found(find(text, from_slice("aba")), 0);
expect_found(find(text, from_slice("bab")), 1);
expect_found(find(text, from_slice("ba")), 1);
expect_found(find(text, from_slice("")), 0);
expect_not_found(find(text, from_slice("abb")));
expect_not_found(find(text, from_slice("ababaa")));
@assert(contains(text, from_slice("aba")));
@assert(!contains(text, from_slice("xyz")));
@assert(starts_with(text, from_slice("ab")));
@assert(starts_with(text, empty()));
@assert(!starts_with(text, from_slice("ba")));
@assert(ends_with(text, from_slice("ba")));
@assert(ends_with(text, empty()));
@assert(!ends_with(text, from_slice("ab")));
}
fn main() {
test_construction_and_access();
test_compare_and_slice();
test_search();
}
+83
View File
@@ -0,0 +1,83 @@
// Expect retcode = 0
// 公共错误值不持有资源,可按值复制;不分配错误消息字符串。
// position 是调用方输入中的从零开始的位置;仅在 has_position 时有效。
// 未定位错误的 position 规范为 0,不能用 position == 0 判断是否有位置。
// 数值解析位置按输入字节计数。可用内存耗尽仍由现有 VM 终止处理。
type Error = @self;
type Kind = enum {
InvalidArgument,
OutOfRange,
Overflow,
DivisionByZero,
InvalidDigit,
InvalidFormat,
UnexpectedEnd,
InvalidAlignment,
WidthMismatch,
Io,
}
kind: Kind,
has_position: bool,
position: usize,
// 无成功载荷的结果。带成功值的模块定义自己的 Ok: T / Err: Error 枚举。
type Status = enum {
Ok,
Err: Error,
}
fn make(kind: Kind): Error {
ret Error { .kind = kind, .has_position = false, .position = 0 };
}
fn at(kind: Kind, position: usize): Error {
ret Error { .kind = kind, .has_position = true, .position = position };
}
fn test_status(result: Status, expected_error: bool) {
match result {
.Ok => {
@assert(!expected_error, "success variant");
},
.Err(error) => {
@assert(expected_error, "error variant");
@assert(!error.has_position, "unlocated error");
@assert(error.position == 0, "canonical absent position");
match error.kind {
.Overflow => {},
_ => { @assert(false, "error kind preserved in result"); },
}
},
}
}
fn main() {
var success: Status = .Ok;
test_status(success, false);
var failure: Status = .Err(make(.Overflow));
test_status(failure, true);
var first = at(.InvalidDigit, 0);
@assert(first.has_position, "byte zero is a valid position");
@assert(first.position == 0);
match first.kind {
.InvalidDigit => {},
_ => { @assert(false, "positioned error kind"); },
}
var original = at(.UnexpectedEnd, 17);
var copy = original;
copy.position = 3;
@assert(original.position == 17, "error copy has independent fields");
@assert(copy.position == 3);
@assert(copy.has_position);
var max_position: usize = ~0 as usize;
var last = at(.OutOfRange, max_position);
@assert(last.has_position, "no reserved position sentinel");
@assert(last.position == max_position);
}
+509
View File
@@ -0,0 +1,509 @@
// Expect retcode = 0
// 数值/布尔格式化,写入 ByteBuffer。radix 取值 2..36;uppercase 只影响 10..35 的字母形式。
// 整数转换统一复用 MPA。先在临时缓冲区完成转换,再一次性追加,失败不产生部分输出。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .Value, .int, .uint, .char, .str, .addr } = @import("value.spl");
type .{ .BigInt, .Bits } = @import("mp.spl");
// radix 边界直接用字面量:顶层 const 不参与运行时常量折叠。
fn valid_radix(radix: u8): bool {
ret radix >= 2 && radix <= 36;
}
fn append_big_int(out: *ByteBuffer, value: BigInt,
radix: u8, uppercase: bool): Status {
if !valid_radix(radix) { ret .Err(make(.InvalidArgument)); }
var text = ByteBuffer.new();
defer { ByteBuffer.drop(&text); }
match BigInt.format(&text, value, radix, uppercase) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret ByteBuffer.append(out, ByteBuffer.as_slice(text));
}
fn append_bits(out: *ByteBuffer, value: Bits, signed: bool,
radix: u8, uppercase: bool): Status {
var integer = BigInt.zero();
defer { BigInt.drop(&integer); }
match Bits.to_big(&integer, value, signed) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret append_big_int(out, integer, radix, uppercase);
}
fn append_u64(out: *ByteBuffer, value: u64, radix: u8, uppercase: bool): Status {
var integer = BigInt.zero();
defer { BigInt.drop(&integer); }
match BigInt.from_u64(&integer, value) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret append_big_int(out, integer, radix, uppercase);
}
fn append_i64(out: *ByteBuffer, value: i64, radix: u8, uppercase: bool): Status {
var integer = BigInt.zero();
defer { BigInt.drop(&integer); }
match BigInt.from_i64(&integer, value) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret append_big_int(out, integer, radix, uppercase);
}
fn append_usize(out: *ByteBuffer, value: usize, radix: u8, uppercase: bool): Status {
ret append_u64(out, value as u64, radix, uppercase);
}
fn append_isize(out: *ByteBuffer, value: isize, radix: u8, uppercase: bool): Status {
ret append_i64(out, value as i64, radix, uppercase);
}
fn append_bool(out: *ByteBuffer, value: bool): Status {
if value { ret ByteBuffer.append(out, "true"); }
ret ByteBuffer.append(out, "false");
}
// Int/Uint 落到整数;signed 仅对 Int 的十进制生效,其余按无符号位模式输出。
fn append_value_int(out: *ByteBuffer, value: Value, radix: u8, uppercase: bool,
signed: bool): Status {
match value {
.Int(v) => {
if signed && radix == 10 { ret append_i64(out, v, radix, uppercase); }
ret append_u64(out, v as u64, radix, uppercase);
},
.Uint(v) => { ret append_u64(out, v, radix, uppercase); },
_ => { ret .Err(make(.InvalidFormat)); },
}
}
fn append_value_char(out: *ByteBuffer, value: Value): Status {
match value {
.Char(c) => { ret ByteBuffer.append_byte(out, c); },
_ => { ret .Err(make(.InvalidFormat)); },
}
}
fn append_value_str(out: *ByteBuffer, value: Value): Status {
match value {
.Str(view) => { ret ByteBuffer.append(out, ByteView.as_slice(view)); },
_ => { ret .Err(make(.InvalidFormat)); },
}
}
fn append_value_ptr(out: *ByteBuffer, value: Value): Status {
match value {
.Ptr(p) => {
match ByteBuffer.append(out, "0x") {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret append_u64(out, p as usize, 16, false);
},
_ => { ret .Err(make(.InvalidFormat)); },
}
}
// {} 自动格式化:按 Value 变体选择默认表示。
fn append_value_auto(out: *ByteBuffer, value: Value): Status {
match value {
.Int(v) => { ret append_i64(out, v, 10, false); },
.Uint(v) => { ret append_u64(out, v, 10, false); },
.Bool(v) => { ret append_bool(out, v); },
.Char(c) => { ret ByteBuffer.append_byte(out, c); },
.Str(view) => { ret ByteBuffer.append(out, ByteView.as_slice(view)); },
.Ptr(p) => {
match ByteBuffer.append(out, "0x") {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret append_u64(out, p as usize, 16, false);
},
}
ret .Ok;
}
// {} 风格:{} 自动;{:d} {:i} {:u} {:x} {:X} {:o} {:b} {:c} {:s} {:p} 指定。
// {{ 与 }} 输出字面量括号。参数按序从 args 消费;数量不足、类型不符、未知转换
// 或括号不配返回 Err(.InvalidFormat)。失败不产生部分输出:先在临时缓冲完成。
fn format(out: *ByteBuffer, fmt: []u8, args: []Value): Status {
var temp = ByteBuffer.new();
defer { ByteBuffer.drop(&temp); }
var argi: usize = 0;
var i: usize = 0;
while i < fmt.len {
var c = fmt.ptr[i];
if c == '{' {
if i + 1 < fmt.len && fmt.ptr[i + 1] == '{' {
match ByteBuffer.append_byte(&temp, '{') {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
i = i + 2;
continue;
}
var j = i + 1;
if j < fmt.len && fmt.ptr[j] == ':' { j = j + 1; }
var has_spec = false;
var spec: u8 = 0;
if j < fmt.len && fmt.ptr[j] != '}' {
has_spec = true;
spec = fmt.ptr[j];
j = j + 1;
}
if j >= fmt.len || fmt.ptr[j] != '}' { ret .Err(make(.InvalidFormat)); }
i = j + 1;
if argi >= args.len { ret .Err(make(.InvalidFormat)); }
var value = args.ptr[argi];
argi = argi + 1;
var step: Status = .Ok;
if !has_spec {
step = append_value_auto(&temp, value);
} else if spec == 'd' || spec == 'i' {
step = append_value_int(&temp, value, 10, false, true);
} else if spec == 'u' {
step = append_value_int(&temp, value, 10, false, false);
} else if spec == 'x' {
step = append_value_int(&temp, value, 16, false, false);
} else if spec == 'X' {
step = append_value_int(&temp, value, 16, true, false);
} else if spec == 'o' {
step = append_value_int(&temp, value, 8, false, false);
} else if spec == 'b' {
step = append_value_int(&temp, value, 2, false, false);
} else if spec == 'c' {
step = append_value_char(&temp, value);
} else if spec == 's' {
step = append_value_str(&temp, value);
} else if spec == 'p' {
step = append_value_ptr(&temp, value);
} else {
ret .Err(make(.InvalidFormat));
}
match step {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
continue;
}
if c == '}' {
if i + 1 < fmt.len && fmt.ptr[i + 1] == '}' {
match ByteBuffer.append_byte(&temp, '}') {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
i = i + 2;
continue;
}
ret .Err(make(.InvalidFormat));
}
match ByteBuffer.append_byte(&temp, c) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
i = i + 1;
}
ret ByteBuffer.append(out, ByteBuffer.as_slice(temp));
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected fmt error"); },
}
}
fn expect_invalid(result: Status) {
match result {
.Ok => { @assert(false, "expected fmt error"); },
.Err(error) => {
match error.kind {
.InvalidArgument => {},
_ => { @assert(false, "unexpected fmt error kind"); },
}
},
}
}
fn expect_text(buffer: *ByteBuffer, expected: []u8) {
var actual = ByteBuffer.as_slice(buffer.*);
@assert(actual.len == expected.len, "formatted length");
var i: usize = 0;
while i < expected.len {
@assert(actual.ptr[i] == expected.ptr[i], "formatted byte");
i = i + 1;
}
}
fn test_unsigned() {
var b = ByteBuffer.new();
defer { ByteBuffer.drop(&b); }
expect_ok(append_u64(&b, 0, 10, false));
expect_text(&b, "0");
ByteBuffer.clear(&b);
expect_ok(append_u64(&b, 42, 10, false));
expect_text(&b, "42");
ByteBuffer.clear(&b);
expect_ok(append_u64(&b, 1234567890, 10, false));
expect_text(&b, "1234567890");
ByteBuffer.clear(&b);
expect_ok(append_u64(&b, 18446744073709551615, 10, false));
expect_text(&b, "18446744073709551615");
ByteBuffer.clear(&b);
expect_ok(append_u64(&b, 255, 16, false));
expect_text(&b, "ff");
ByteBuffer.clear(&b);
expect_ok(append_u64(&b, 255, 16, true));
expect_text(&b, "FF");
ByteBuffer.clear(&b);
expect_ok(append_u64(&b, 4096, 16, false));
expect_text(&b, "1000");
ByteBuffer.clear(&b);
expect_ok(append_u64(&b, 170, 2, false));
expect_text(&b, "10101010");
ByteBuffer.clear(&b);
expect_ok(append_u64(&b, 35, 36, false));
expect_text(&b, "z");
ByteBuffer.clear(&b);
expect_ok(append_u64(&b, 18446744073709551615, 2, false));
expect_text(&b, "1111111111111111111111111111111111111111111111111111111111111111");
ByteBuffer.clear(&b);
}
fn test_signed() {
var b = ByteBuffer.new();
defer { ByteBuffer.drop(&b); }
expect_ok(append_i64(&b, 0, 10, false));
expect_text(&b, "0");
ByteBuffer.clear(&b);
expect_ok(append_i64(&b, -123, 10, false));
expect_text(&b, "-123");
ByteBuffer.clear(&b);
expect_ok(append_i64(&b, 9223372036854775807, 10, false));
expect_text(&b, "9223372036854775807");
ByteBuffer.clear(&b);
expect_ok(append_i64(&b, -9223372036854775808, 10, false));
expect_text(&b, "-9223372036854775808");
ByteBuffer.clear(&b);
expect_ok(append_isize(&b, -16, 16, false));
expect_text(&b, "-10");
ByteBuffer.clear(&b);
expect_ok(append_u64(&b, 48879, 16, true));
expect_text(&b, "BEEF");
ByteBuffer.clear(&b);
}
fn test_usize_bool_and_errors() {
var b = ByteBuffer.new();
defer { ByteBuffer.drop(&b); }
expect_ok(append_usize(&b, 1000 as usize, 10, false));
expect_text(&b, "1000");
ByteBuffer.clear(&b);
expect_ok(append_bool(&b, true));
expect_text(&b, "true");
ByteBuffer.clear(&b);
expect_ok(append_bool(&b, false));
expect_text(&b, "false");
ByteBuffer.clear(&b);
expect_invalid(append_u64(&b, 1, 0, false));
expect_invalid(append_u64(&b, 1, 1, false));
expect_invalid(append_u64(&b, 1, 37, false));
expect_invalid(append_i64(&b, -1, 0, false));
@assert(ByteBuffer.size(b) == 0, "invalid radix writes nothing");
expect_ok(ByteBuffer.append(&b, "prefix"));
expect_invalid(append_i64(&b, -123, 1, false));
expect_text(&b, "prefix");
}
fn test_mpa_integer_formatting() {
var b = ByteBuffer.new();
defer { ByteBuffer.drop(&b); }
var huge = BigInt.zero();
defer { BigInt.drop(&huge); }
expect_ok(BigInt.parse(&huge, "1234567890123456789012345678901234567890", 10));
expect_ok(append_big_int(&b, huge, 10, false));
expect_text(&b, "1234567890123456789012345678901234567890");
ByteBuffer.clear(&b);
expect_ok(BigInt.parse(&huge, "-ffffffffffffffffffffffffffffffff", 16));
expect_ok(append_big_int(&b, huge, 16, true));
expect_text(&b, "-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
ByteBuffer.clear(&b);
var raw = BigInt.zero();
defer { BigInt.drop(&raw); }
expect_ok(BigInt.from_u64(&raw, 255));
var bits: Bits = undefined;
match Bits.new(8) {
.Ok(value) => { bits = value; },
.Err(error) => { @assert(false, "Bits construction"); },
}
defer { Bits.drop(&bits); }
expect_ok(Bits.from_big_checked(&bits, raw, 8, false));
expect_ok(append_bits(&b, bits, false, 10, false));
expect_text(&b, "255");
ByteBuffer.clear(&b);
expect_ok(append_bits(&b, bits, true, 10, false));
expect_text(&b, "-1");
}
fn test_composition_and_growth() {
var b = ByteBuffer.new();
defer { ByteBuffer.drop(&b); }
expect_ok(ByteBuffer.append(&b, "x="));
expect_ok(append_i64(&b, -42, 10, false));
expect_ok(ByteBuffer.append(&b, " hex="));
expect_ok(append_u64(&b, 255, 16, false));
expect_ok(ByteBuffer.append(&b, " ok="));
expect_ok(append_bool(&b, true));
expect_text(&b, "x=-42 hex=ff ok=true");
ByteBuffer.clear(&b);
// 反复写入触发 ByteBuffer 扩容,校验累积内容。
var i: i32 = 0;
while i < 200 {
expect_ok(append_i64(&b, i as i64, 10, false));
expect_ok(ByteBuffer.append_byte(&b, ',')); // ','
i = i + 1;
}
expect_ok(append_i64(&b, 200, 10, false));
@assert(ByteBuffer.size(b) > 0, "growth content");
var s = ByteBuffer.as_slice(b);
@assert(s.ptr[0] == '0', "starts with 0");
@assert(s.ptr[1] == ',', "0 then comma");
}
fn expect_format(fmt: []u8, args: []Value, expected: []u8) {
var b = ByteBuffer.new();
defer { ByteBuffer.drop(&b); }
expect_ok(format(&b, fmt, args));
expect_text(&b, expected);
}
fn expect_invalid_format(fmt: []u8, args: []Value) {
var b = ByteBuffer.new();
defer { ByteBuffer.drop(&b); }
match format(&b, fmt, args) {
.Ok => { @assert(false, "expected invalid format"); },
.Err(error) => {
match error.kind {
.InvalidFormat => {},
_ => { @assert(false, "expected InvalidFormat kind"); },
}
},
}
}
fn test_format_specs() {
var one: [1]Value = .[int(-5)];
expect_format("{}", one[0..1], "-5");
expect_format("{d}", one[0..1], "-5");
expect_format("{i}", one[0..1], "-5");
var u: [1]Value = .[uint(7)];
expect_format("{}", u[0..1], "7");
expect_format("{u}", u[0..1], "7");
expect_format("{x}", u[0..1], "7");
expect_format("{o}", u[0..1], "7");
expect_format("{b}", u[0..1], "111");
var hex: [1]Value = .[uint(255)];
expect_format("{x}", hex[0..1], "ff");
expect_format("{X}", hex[0..1], "FF");
var oct: [1]Value = .[uint(8)];
expect_format("{o}", oct[0..1], "10");
var bits: [1]Value = .[uint(5)];
expect_format("{b}", bits[0..1], "101");
var ch: [1]Value = .[char('A')];
expect_format("{}", ch[0..1], "A");
expect_format("{c}", ch[0..1], "A");
var text: [1]Value = .[str("hi")];
expect_format("{}", text[0..1], "hi");
expect_format("{s}", text[0..1], "hi");
var nul: [1]Value = .[addr(null)];
expect_format("{p}", nul[0..1], "0x0");
var none: [1]Value = .[int(0)];
expect_format("100%", none[0..0], "100%");
expect_format("{{}}", none[0..0], "{}");
expect_format("plain", none[0..0], "plain");
}
fn test_format_mixed_and_failure() {
var args: [3]Value = .[int(42), str("x"), char('!')];
expect_format("n={} s={} c={}", args[0..3], "n=42 s=x c=!");
expect_format("n={} s={s} c={c}", args[0..3], "n=42 s=x c=!");
// 失败不改动输出,且不产生部分结果:前两个转换成功、第三个参数不足。
var b = ByteBuffer.new();
defer { ByteBuffer.drop(&b); }
expect_ok(ByteBuffer.append(&b, "prefix"));
var two: [2]Value = .[int(1), int(2)];
expect_invalid_format("a={} b={} c={}", two[0..2]);
expect_text(&b, "prefix");
var only_int: [1]Value = .[int(1)];
expect_invalid_format("{} {}", only_int[0..1]);
expect_invalid_format("{}", only_int[0..0]);
expect_invalid_format("trailing {", only_int[0..1]);
expect_invalid_format("dangling }", only_int[0..1]);
expect_invalid_format("{q}", only_int[0..1]);
expect_invalid_format("{s}", only_int[0..1]);
expect_invalid_format("{c}", only_int[0..1]);
}
fn main() {
test_unsigned();
test_signed();
test_usize_bool_and_errors();
test_mpa_integer_formatting();
test_composition_and_growth();
test_format_specs();
test_format_mixed_and_failure();
}
+89
View File
@@ -0,0 +1,89 @@
// Expect retcode = 0
// 哈希工具:FNV-1a 字节哈希 + 整型混合;并给出可直接交给 map 的回调
// (map 的 hash/eq 回调接收指向键字节的指针,这里按 usize / ByteView 解释)。
// 仅提供确定性哈希与等值,不承诺密码学性质。
type .{ .ByteView } = @import("byte_view.spl");
// FNV-1a 64 位,逐字节;空输入返回 offset basis。
fn hash_bytes(data: *u8, len: usize): usize {
var h: usize = 14695981039346656037; // FNV-1a 64 位 offset basis
var prime: usize = 1099511628211; // FNV 质数
var i: usize = 0;
while i < len {
h = (h ^ (data[i] as usize)) * prime;
i = i + 1;
}
ret h;
}
fn hash_byte_view(view: ByteView): usize {
ret hash_bytes(view.ptr, view.len);
}
// 整型混合(splitmix 风格的移位-乘法),保证非零与良好扩散。
fn hash_usize_value(value: usize): usize {
var prime: usize = 1099511628211; // splitmix 风格的混合乘子
var h = value;
h = (h ^ (h >> 30)) * prime;
h = h ^ (h >> 27);
h = h * prime;
h = h ^ (h >> 31);
if h == 0 { ret prime; } // 0 映射到非零
ret h;
}
// 键按 usize 解释。
fn hash_usize(key: *u8): usize {
ret hash_usize_value((key as *usize).*);
}
fn eq_usize(a: *u8, b: *u8): bool {
ret (a as *usize).* == (b as *usize).*;
}
// 键按 ByteView 解释,哈希其指向的字节。
fn hash_view(key: *u8): usize {
var view = (key as *ByteView).*;
ret hash_byte_view(view);
}
fn eq_view(a: *u8, b: *u8): bool {
ret ByteView.eq((a as *ByteView).*, (b as *ByteView).*);
}
fn expect_hash(view: []u8, expected: usize) {
var got = hash_bytes(view.ptr, view.len);
@assert(got == expected, "hash value");
}
fn test_fnv_vectors() {
expect_hash("", 14695981039346656037);
expect_hash("a", 12638187200555641996);
expect_hash("foobar", 9625390261332436968);
expect_hash("hello", 11831194018420276491);
// 同一输入稳定;不同输入不同。
var s1: []u8 = "abc";
var s2: []u8 = "abd";
@assert(hash_bytes(s1.ptr, 3) == hash_bytes(s1.ptr, 3), "deterministic");
@assert(hash_bytes(s1.ptr, 3) != hash_bytes(s2.ptr, 3), "distinct");
// ByteView 与 bytes 一致。
var v = ByteView.from_slice("foobar");
@assert(hash_byte_view(v) == hash_bytes(v.ptr, v.len), "view == bytes");
// 整型混合:0 不保持 0,且稳定/区分。
@assert(hash_usize_value(0) != 0, "zero mixed");
@assert(hash_usize_value(7) == hash_usize_value(7), "int stable");
@assert(hash_usize_value(7) != hash_usize_value(8), "int distinct");
}
// map 回调(hash_usize/eq_usize/hash_view/eq_view)由 map.spl 在自己的测试里验证。
fn main() {
test_fnv_vectors();
}
+239
View File
@@ -0,0 +1,239 @@
// Expect retcode = 0
// sys/file 之上的 I/O 便利层:C 字符串路径 open、精确读取/完整写入、整读、stdout/stderr 打印。
// 短读/短写零进展与关闭失败返回 Err(.Io);写入失败不承诺回滚。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type File = @import("../sys/file.spl");
type Mem = @import("../sys/mem.spl");
// 把 view 复制到以 NUL 结尾的临时缓冲,返回其 data(归属 tmp,调用后勿再用)。
fn cstr(tmp: *ByteBuffer, view: ByteView): *u8 {
ByteBuffer.clear(tmp);
if view.len != 0 {
match ByteBuffer.append(tmp, ByteView.as_slice(view)) {
.Ok => {},
.Err(error) => { ret null; },
}
}
match ByteBuffer.append_byte(tmp, '\0') {
.Ok => {},
.Err(error) => { ret null; },
}
ret tmp.storage.data;
}
fn open_cstr(path: ByteView, mode: ByteView): File {
var pbuf = ByteBuffer.new();
var mbuf = ByteBuffer.new();
var p = cstr(&pbuf, path);
var m = cstr(&mbuf, mode);
var f = File { .handle = null };
if p != null && m != null { f = File.open(p, m); }
ByteBuffer.drop(&pbuf);
ByteBuffer.drop(&mbuf);
ret f;
}
// 反复写入直到完成。发生错误时文件可能已经包含前缀,不承诺回滚。
fn write_all(f: *File, bytes: []u8): Status {
if f == null || !File.valid(f) { ret .Err(make(.Io)); }
if bytes.len != 0 && bytes.ptr == null { ret .Err(make(.InvalidArgument)); }
var total: usize = 0;
while total < bytes.len {
var written = File.write(f, bytes.ptr + total, (bytes.len - total) as u64);
if written == 0 || written > (bytes.len - total) as u64 {
ret .Err(make(.Io));
}
total = total + written as usize;
}
ret .Ok;
}
fn print(bytes: []u8): Status {
var out = File.stdout();
ret write_all(&out, bytes);
}
fn println(bytes: []u8): Status {
match print(bytes) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret print("\n");
}
fn eprint(bytes: []u8): Status {
var err = File.stderr();
ret write_all(&err, bytes);
}
// 仅供已经隔离的临时存储使用;失败时可能写入目标前缀。
fn read_fully(f: *File, data: *u8, len: usize): Status {
var total: usize = 0;
while total < len {
var got = File.read(f, data + total, len - total);
if got == 0 || got > len - total { ret .Err(make(.Io)); }
total = total + got;
}
ret .Ok;
}
// 精确读取 bytes.len 字节。短读返回 Err(.Io),失败保持 bytes 原内容不变。
fn read_exact(f: *File, bytes: []u8): Status {
if f == null || !File.valid(f) { ret .Err(make(.Io)); }
if bytes.len == 0 { ret .Ok; }
if bytes.ptr == null { ret .Err(make(.InvalidArgument)); }
var temporary = ByteBuffer.new();
defer { ByteBuffer.drop(&temporary); }
match ByteBuffer.reserve(&temporary, bytes.len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
match read_fully(f, temporary.storage.data, bytes.len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
Mem.copy(bytes.ptr as *any, temporary.storage.data as *any, bytes.len);
ret .Ok;
}
// 读取整个普通文件到 out。打开、短读或关闭失败时保持 out 不变。
fn read_all(path: ByteView, out: *ByteBuffer): Status {
if out == null { ret .Err(make(.InvalidArgument)); }
var f = open_cstr(path, ByteView.from_slice("rb"));
if !File.valid(&f) { ret .Err(make(.Io)); }
var sz = File.size(&f);
var fresh = ByteBuffer.new();
if sz != 0 {
match ByteBuffer.reserve(&fresh, sz) {
.Ok => {},
.Err(error) => { File.close(&f); ByteBuffer.drop(&fresh); ret .Err(error); },
}
match read_fully(&f, fresh.storage.data, sz) {
.Ok => {},
.Err(error) => { File.close(&f); ByteBuffer.drop(&fresh); ret .Err(error); },
}
fresh.storage.len = sz;
}
if File.close(&f) != 0 { ByteBuffer.drop(&fresh); ret .Err(make(.Io)); }
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
fn view(text: []u8): ByteView {
ret ByteView.from_slice(text);
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected io error"); },
}
}
fn expect_io(result: Status) {
match result {
.Ok => { @assert(false, "expected io error"); },
.Err(error) => {
match error.kind {
.Io => {},
_ => { @assert(false, "expected Io kind"); },
}
},
}
}
fn test_roundtrip() {
var path = view("build/_io_roundtrip.tmp");
var f = open_cstr(path, view("wb"));
@assert(File.valid(&f), "open wb");
expect_ok(write_all(&f, "hello\nworld"));
@assert(File.close(&f) == 0, "close writer");
var out = ByteBuffer.new();
match read_all(path, &out) {
.Ok => {},
.Err(error) => { @assert(false, "read_all"); },
}
var got = ByteBuffer.as_slice(out);
@assert(got.len == 11, "roundtrip length");
@assert(got.ptr[0] == 'h' && got.ptr[5] == '\n' && got.ptr[10] == 'd', "roundtrip bytes");
ByteBuffer.drop(&out);
}
fn test_append_and_missing() {
var path = view("build/_io_append.tmp");
var f = open_cstr(path, view("wb"));
@assert(File.valid(&f), "open wb");
expect_ok(write_all(&f, "ab"));
@assert(File.close(&f) == 0, "close initial writer");
f = open_cstr(path, view("ab"));
@assert(File.valid(&f), "open ab");
expect_ok(write_all(&f, "cd"));
@assert(File.close(&f) == 0, "close append writer");
var out = ByteBuffer.new();
match read_all(path, &out) {
.Ok => {},
.Err(error) => { @assert(false, "read append"); },
}
var got = ByteBuffer.as_slice(out);
@assert(got.len == 4 && got.ptr[2] == 'c' && got.ptr[3] == 'd', "append content");
// 不存在的文件
match read_all(view("build/_io_no_such_file.tmp"), &out) {
.Ok => { @assert(false, "missing should fail"); },
.Err(error) => {
match error.kind { .Io => {}, _ => { @assert(false, "error kind"); } }
},
}
got = ByteBuffer.as_slice(out);
@assert(got.len == 4 && got.ptr[0] == 'a' && got.ptr[3] == 'd',
"failed read_all preserves output");
ByteBuffer.drop(&out);
}
fn test_read_exact_and_errors() {
var path = view("build/_io_exact.tmp");
var f = open_cstr(path, view("wb"));
@assert(File.valid(&f));
expect_ok(write_all(&f, "xyz"));
@assert(File.close(&f) == 0);
f = open_cstr(path, view("rb"));
@assert(File.valid(&f));
var too_long: [4]u8 = .[9, 8, 7, 6];
expect_io(read_exact(&f, too_long[0..4]));
@assert(too_long[0] == 9 && too_long[1] == 8 &&
too_long[2] == 7 && too_long[3] == 6,
"short read preserves destination");
@assert(File.close(&f) == 0);
f = open_cstr(path, view("rb"));
@assert(File.valid(&f));
var exact: [3]u8 = .[0, 0, 0];
expect_ok(read_exact(&f, exact[0..3]));
@assert(exact[0] == 'x' && exact[1] == 'y' && exact[2] == 'z');
@assert(File.close(&f) == 0);
var invalid = File { .handle = null };
expect_io(write_all(&invalid, "x"));
expect_io(read_exact(&invalid, exact[0..3]));
}
fn main() {
test_roundtrip();
test_append_and_missing();
test_read_exact_and_errors();
expect_ok(println("io: ok"));
}
+831
View File
@@ -0,0 +1,831 @@
// Expect retcode = 0
// 轻量 JSON 解析/输出器:输入按源切片借用、不解码(round-trip 保真),数字不做数值化。
// 节点/子项存 Vec 保留插入顺序;输出写 ByteBuffer。递归/输出深度上限见 max_depth()。
type .{ .Error, .Status, .make, .at } = @import("error.spl");
type .{ .ByteView, .ViewResult, .FindResult } = @import("byte_view.spl");
type .{ .Vec } = @import("vec.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type .{ .is_digit, .is_hex_digit, .is_space } = @import("ascii.spl");
type .{ .checked_mul } = @import("memory.spl");
type .{ .append_i64, .append_u64 } = @import("fmt.spl");
type JsonKind = enum {
Null,
True,
False,
Number,
String,
Array,
Object,
}
// 数组元素或对象成员。数组时 key 为空视图;对象时 key 为源中的原始键文本。
type JsonChild = struct {
key: ByteView,
val: usize,
}
// 节点:kind 决定有效载荷字段,其余字段保持空。访问方必须按 kind 读取:
// Null/True/False -> text 空、children 空;
// Number/String -> text 为源切片、children 空;
// Array/Object -> children 保存子项、text 空。
type JsonNode = struct {
kind: JsonKind,
text: ByteView,
children: Vec,
}
// 解析结果文档:nodes 拥有全部节点;root 为根节点下标。
type Json = struct {
nodes: Vec,
root: usize,
}
type JsonResult = enum {
Ok: Json,
Err: Error,
}
type NodeResult = enum {
Ok: usize,
Err: Error,
}
type Parser = struct {
src: ByteView,
pos: usize,
nodes: Vec,
}
// 递归/输出深度上限:防止病态输入耗尽调用栈。256 层足够覆盖正常 JSON。
fn max_depth(): usize {
ret 256;
}
fn blank_node(kind: JsonKind): JsonNode {
ret JsonNode {
.kind = kind,
.text = ByteView.empty(),
.children = Vec.blank(@sizeof(JsonChild), 8),
};
}
fn intern_node(p: *Parser, node: JsonNode): NodeResult {
var index = p.nodes.len;
var n = node;
match Vec.push(&p.nodes, &n as *u8) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret .Ok(index);
}
fn node_at(doc: *Json, index: usize): *JsonNode {
match Vec.get(&doc.nodes, index) {
.Ok(ptr) => { ret ptr as *JsonNode; },
.Err(error) => { ret null; }, // 越界/无效池统一返回 null,调用方判空
}
ret null;
}
fn skip_ws(p: *Parser) {
while p.pos < p.src.len && is_space(p.src.ptr[p.pos]) {
p.pos = p.pos + 1;
}
}
// -1 表示 EOF。
fn peek(p: *Parser): i32 {
if p.pos >= p.src.len { ret -1; }
ret p.src.ptr[p.pos] as i32;
}
// 当前字符是否等于 ch(EOF 时 false)。
fn at_char(p: *Parser, ch: u8): bool {
var c = peek(p);
if c < 0 { ret false; }
ret (c as u8) == ch;
}
fn parse_string_raw(p: *Parser): ViewResult {
if !at_char(p, '"') { ret .Err(at(.InvalidFormat, p.pos)); }
p.pos = p.pos + 1;
var start = p.pos;
while p.pos < p.src.len {
var c = p.src.ptr[p.pos];
if c == '"' { break; }
if c == '\\' { // '\\'
p.pos = p.pos + 1;
if p.pos >= p.src.len { ret .Err(at(.UnexpectedEnd, p.pos)); }
if p.src.ptr[p.pos] == 'u' { // 'u'
var k: usize = 0;
while k < 4 {
p.pos = p.pos + 1;
if p.pos >= p.src.len { ret .Err(at(.UnexpectedEnd, p.pos)); }
if !is_hex_digit(p.src.ptr[p.pos]) { ret .Err(at(.InvalidDigit, p.pos)); }
k = k + 1;
}
}
} else if c < 32 {
// JSON 只禁止未转义的 U+0000..U+001F;0x7F 合法,不能用 ascii.is_control。
ret .Err(at(.InvalidFormat, p.pos));
}
p.pos = p.pos + 1;
}
if p.pos >= p.src.len { ret .Err(at(.UnexpectedEnd, p.pos)); }
var end = p.pos;
p.pos = p.pos + 1; // 收尾引号
ret ByteView.slice(p.src, start, end);
}
fn parse_literal(p: *Parser, word: []u8, kind: JsonKind): NodeResult {
var i: usize = 0;
while i < word.len {
if p.pos + i >= p.src.len { ret .Err(at(.UnexpectedEnd, p.src.len)); }
if p.src.ptr[p.pos + i] != word.ptr[i] {
ret .Err(at(.InvalidFormat, p.pos + i));
}
i = i + 1;
}
p.pos = p.pos + word.len;
var node = blank_node(kind);
ret intern_node(p, node);
}
fn parse_number(p: *Parser): NodeResult {
var start = p.pos;
if at_char(p, '-') { p.pos = p.pos + 1; } // '-'
if p.pos >= p.src.len || !is_digit(p.src.ptr[p.pos]) {
ret .Err(at(.InvalidDigit, p.pos));
}
if p.src.ptr[p.pos] == '0' { // 前导 0 后面不能接数字
p.pos = p.pos + 1;
} else {
while p.pos < p.src.len && is_digit(p.src.ptr[p.pos]) {
p.pos = p.pos + 1;
}
}
if p.pos < p.src.len && p.src.ptr[p.pos] == '.' { // '.'
p.pos = p.pos + 1;
if p.pos >= p.src.len || !is_digit(p.src.ptr[p.pos]) {
ret .Err(at(.InvalidDigit, p.pos));
}
while p.pos < p.src.len && is_digit(p.src.ptr[p.pos]) {
p.pos = p.pos + 1;
}
}
if p.pos < p.src.len && (p.src.ptr[p.pos] == 'e' || p.src.ptr[p.pos] == 'E') { // e/E
p.pos = p.pos + 1;
if p.pos < p.src.len && (p.src.ptr[p.pos] == '+' || p.src.ptr[p.pos] == '-') {
p.pos = p.pos + 1;
}
if p.pos >= p.src.len || !is_digit(p.src.ptr[p.pos]) {
ret .Err(at(.InvalidDigit, p.pos));
}
while p.pos < p.src.len && is_digit(p.src.ptr[p.pos]) {
p.pos = p.pos + 1;
}
}
var text = ByteView.slice(p.src, start, p.pos);
match text {
.Ok(v) => {
var node = blank_node(.Number);
node.text = v;
ret intern_node(p, node);
},
.Err(error) => { ret .Err(error); },
}
}
fn parse_array(p: *Parser, depth: usize): NodeResult {
if depth >= max_depth() { ret .Err(at(.OutOfRange, p.pos)); }
p.pos = p.pos + 1; // '['
var kids = Vec.blank(@sizeof(JsonChild), 8);
skip_ws(p);
if at_char(p, ']') { // ']'
p.pos = p.pos + 1;
var empty = blank_node(.Array);
empty.children = kids;
match intern_node(p, empty) {
.Ok(index) => { ret .Ok(index); },
.Err(error) => { Vec.drop(&empty.children); ret .Err(error); },
}
}
while true {
var element = parse_value(p, depth + 1);
var index: usize = 0;
match element {
.Ok(v) => { index = v; },
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
}
var child = JsonChild { .key = ByteView.empty(), .val = index };
match Vec.push(&kids, &child as *u8) {
.Ok => {},
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
}
skip_ws(p);
if at_char(p, ',') { // ','
p.pos = p.pos + 1;
continue;
}
if at_char(p, ']') { // ']'
p.pos = p.pos + 1;
break;
}
Vec.drop(&kids);
ret .Err(at(.InvalidFormat, p.pos));
}
var node = blank_node(.Array);
node.children = kids;
match intern_node(p, node) {
.Ok(index) => { ret .Ok(index); },
.Err(error) => { Vec.drop(&node.children); ret .Err(error); },
}
}
fn parse_object(p: *Parser, depth: usize): NodeResult {
if depth >= max_depth() { ret .Err(at(.OutOfRange, p.pos)); }
p.pos = p.pos + 1; // '{'
var kids = Vec.blank(@sizeof(JsonChild), 8);
skip_ws(p);
if at_char(p, '}') { // '}'
p.pos = p.pos + 1;
var empty = blank_node(.Object);
empty.children = kids;
match intern_node(p, empty) {
.Ok(index) => { ret .Ok(index); },
.Err(error) => { Vec.drop(&empty.children); ret .Err(error); },
}
}
while true {
skip_ws(p);
var key_view = ByteView.empty();
match parse_string_raw(p) {
.Ok(v) => { key_view = v; },
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
}
skip_ws(p);
if !at_char(p, ':') { // ':'
Vec.drop(&kids);
ret .Err(at(.InvalidFormat, p.pos));
}
p.pos = p.pos + 1;
var value = parse_value(p, depth + 1);
var index: usize = 0;
match value {
.Ok(v) => { index = v; },
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
}
var child = JsonChild { .key = key_view, .val = index };
match Vec.push(&kids, &child as *u8) {
.Ok => {},
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
}
skip_ws(p);
if at_char(p, ',') { // ','
p.pos = p.pos + 1;
continue;
}
if at_char(p, '}') { // '}'
p.pos = p.pos + 1;
break;
}
Vec.drop(&kids);
ret .Err(at(.InvalidFormat, p.pos));
}
var node = blank_node(.Object);
node.children = kids;
match intern_node(p, node) {
.Ok(index) => { ret .Ok(index); },
.Err(error) => { Vec.drop(&node.children); ret .Err(error); },
}
}
fn parse_value(p: *Parser, depth: usize): NodeResult {
skip_ws(p);
var c = peek(p);
if c < 0 { ret .Err(at(.UnexpectedEnd, p.pos)); }
var ch = c as u8;
if ch == '"' { // '"'
var view = ByteView.empty();
match parse_string_raw(p) {
.Ok(v) => { view = v; },
.Err(error) => { ret .Err(error); },
}
var node = blank_node(.String);
node.text = view;
ret intern_node(p, node);
}
if ch == '{' { ret parse_object(p, depth); } // '{'
if ch == '[' { ret parse_array(p, depth); } // '['
if ch == 't' { ret parse_literal(p, "true", .True); }
if ch == 'f' { ret parse_literal(p, "false", .False); }
if ch == 'n' { ret parse_literal(p, "null", .Null); }
ret parse_number(p);
}
fn drop_node_pool(pool: *Vec) {
var i: usize = 0;
while i < pool.*.len {
match Vec.get(pool, i) {
.Ok(ptr) => {
var n = ptr as *JsonNode;
Vec.drop(&n.children);
},
// i < pool.len 且池有效,get 不会失败;失败时无额外资源可释放。
.Err(error) => {},
}
i = i + 1;
}
Vec.drop(pool);
}
fn parse(src: ByteView): JsonResult {
var p = Parser {
.src = src,
.pos = 0,
.nodes = Vec.blank(@sizeof(JsonNode), 8),
};
skip_ws(&p);
var root: usize = 0;
var result = parse_value(&p, 0);
match result {
.Ok(index) => { root = index; },
.Err(error) => {
drop_node_pool(&p.nodes);
ret .Err(error);
},
}
skip_ws(&p);
if p.pos != src.len {
drop_node_pool(&p.nodes);
ret .Err(at(.InvalidFormat, p.pos));
}
ret .Ok(Json { .nodes = p.nodes, .root = root });
}
fn drop(doc: *Json) {
drop_node_pool(&doc.nodes);
}
fn root_index(doc: *Json): usize {
ret doc.root;
}
// 枚举 tag 取整:枚举 == 不可靠,用 @enum2int 比较。
fn kind_code(kind: JsonKind): usize {
ret @enum2int(kind) as usize;
}
fn text_of(doc: *Json, index: usize): ByteView {
var n = node_at(doc, index);
if n == null { ret ByteView.empty(); }
ret n.text;
}
fn array_count(doc: *Json, index: usize): usize {
var n = node_at(doc, index);
if n == null { ret 0; }
match n.kind {
.Array => { ret n.children.len; },
_ => { ret 0; },
}
ret 0;
}
fn array_at(doc: *Json, index: usize, i: usize): usize {
var n = node_at(doc, index);
if n == null { ret ~(0 as usize); }
match n.kind {
.Array => {
if i >= n.children.len { ret ~(0 as usize); }
match Vec.get(&n.children, i) {
.Ok(ptr) => { ret (ptr as *JsonChild).val; },
.Err(error) => { ret ~(0 as usize); },
}
},
_ => { ret ~(0 as usize); },
}
ret ~(0 as usize);
}
// 对象按键查找(线性,保持插入顺序)。找到返回节点下标。
fn object_find(doc: *Json, index: usize, key: ByteView): FindResult {
var n = node_at(doc, index);
if n == null { ret .NotFound; }
match n.kind {
.Object => {
var i: usize = 0;
var count = n.children.len;
while i < count {
match Vec.get(&n.children, i) {
.Ok(ptr) => {
var child = ptr as *JsonChild;
if ByteView.eq(child.key, key) { ret .Found(child.val); }
},
// i < count == children.len,get 不会失败。
.Err(error) => {},
}
i = i + 1;
}
ret .NotFound;
},
_ => { ret .NotFound; },
}
ret .NotFound;
}
fn emit_quoted(out: *ByteBuffer, text: ByteView): Status {
match ByteBuffer.append_byte(out, '"') { // '"'
.Ok => {},
.Err(error) => { ret .Err(error); },
}
match ByteBuffer.append(out, ByteView.as_slice(text)) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret ByteBuffer.append_byte(out, '"');
}
fn emit_indent(out: *ByteBuffer, indent: usize, depth: usize): Status {
match ByteBuffer.append_byte(out, '\n') { // '\n'
.Ok => {},
.Err(error) => { ret .Err(error); },
}
var spaces: usize = 0;
match checked_mul(depth, indent) {
.Ok(value) => { spaces = value; },
.Err(error) => { ret .Err(error); },
}
ret ByteBuffer.append_repeat(out, 32, spaces);
}
fn emit(out: *ByteBuffer, doc: *Json, index: usize, indent: usize, depth: usize): Status {
if depth >= max_depth() { ret .Err(make(.OutOfRange)); }
var n = node_at(doc, index);
if n == null { ret .Err(make(.OutOfRange)); }
match n.kind {
.Null => { ret ByteBuffer.append(out, "null"); },
.True => { ret ByteBuffer.append(out, "true"); },
.False => { ret ByteBuffer.append(out, "false"); },
.Number => { ret ByteBuffer.append(out, ByteView.as_slice(n.text)); },
.String => { ret emit_quoted(out, n.text); },
.Array => { ret emit_children(out, doc, n, false, indent, depth); },
.Object => { ret emit_children(out, doc, n, true, indent, depth); },
}
ret .Ok;
}
fn emit_children(out: *ByteBuffer, doc: *Json, n: *JsonNode, is_object: bool, indent: usize,
depth: usize): Status {
var open_byte: u8 = '['; // '['
var close_byte: u8 = ']'; // ']'
if is_object {
open_byte = '{'; // '{'
close_byte = '}'; // '}'
}
match ByteBuffer.append_byte(out, open_byte) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
var count = n.children.len;
if count == 0 {
ret ByteBuffer.append_byte(out, close_byte);
}
var i: usize = 0;
while i < count {
if i != 0 {
match ByteBuffer.append_byte(out, ',') { // ','
.Ok => {},
.Err(error) => { ret .Err(error); },
}
}
if indent != 0 {
match emit_indent(out, indent, depth + 1) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
}
match Vec.get(&n.children, i) {
.Ok(ptr) => {
var child = ptr as *JsonChild;
if is_object {
match emit_quoted(out, child.key) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
match ByteBuffer.append_byte(out, ':') { // ':'
.Ok => {},
.Err(error) => { ret .Err(error); },
}
if indent != 0 {
match ByteBuffer.append_byte(out, ' ') { // ' '
.Ok => {},
.Err(error) => { ret .Err(error); },
}
}
}
match emit(out, doc, child.val, indent, depth + 1) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
},
.Err(error) => { ret .Err(error); },
}
i = i + 1;
}
if indent != 0 {
match emit_indent(out, indent, depth) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
}
ret ByteBuffer.append_byte(out, close_byte);
}
fn emit_compact(out: *ByteBuffer, doc: *Json): Status {
ret emit(out, doc, doc.root, 0, 0);
}
fn emit_pretty(out: *ByteBuffer, doc: *Json, indent: usize): Status {
ret emit(out, doc, doc.root, indent, 0);
}
fn write_int(out: *ByteBuffer, value: i64): Status {
ret append_i64(out, value, 10, false);
}
fn write_u64(out: *ByteBuffer, value: u64): Status {
ret append_u64(out, value, 10, false);
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected json error"); },
}
}
fn expect_bytes(out: *ByteBuffer, expected: []u8) {
var actual = ByteBuffer.as_slice(out.*);
@assert(actual.len == expected.len, "emitted length");
var i: usize = 0;
while i < expected.len {
@assert(actual.ptr[i] == expected.ptr[i], "emitted byte");
i = i + 1;
}
}
fn expect_parse_error(result: JsonResult, pos: usize) {
match result {
.Ok(doc) => { @assert(false, "expected parse error"); },
.Err(error) => {
@assert(error.has_position, "positioned error");
@assert(error.position == pos, "error position");
},
}
}
fn expect_kind(doc: *Json, index: usize, kind: JsonKind) {
var n = node_at(doc, index);
@assert(n != null, "node exists");
@assert(kind_code(n.kind) == kind_code(kind), "node kind");
}
fn expect_text(doc: *Json, index: usize, expected: []u8) {
var got = text_of(doc, index);
var want = ByteView.from_slice(expected);
@assert(ByteView.eq(got, want), "node text");
}
fn test_scalars() {
var r = parse(ByteView.from_slice("null"));
match r {
.Ok(doc) => { expect_kind(&doc, root_index(&doc), .Null); drop(&doc); },
.Err(error) => { @assert(false, "parse null"); },
}
r = parse(ByteView.from_slice("true"));
match r {
.Ok(doc) => { expect_kind(&doc, root_index(&doc), .True); drop(&doc); },
.Err(error) => { @assert(false, "parse true"); },
}
r = parse(ByteView.from_slice("false"));
match r {
.Ok(doc) => { expect_kind(&doc, root_index(&doc), .False); drop(&doc); },
.Err(error) => { @assert(false, "parse false"); },
}
r = parse(ByteView.from_slice("-12.5e2"));
match r {
.Ok(doc) => {
var idx = root_index(&doc);
expect_kind(&doc, idx, .Number);
expect_text(&doc, idx, "-12.5e2");
drop(&doc);
},
.Err(error) => { @assert(false, "parse number"); },
}
r = parse(ByteView.from_slice("\"hi\\n\""));
match r {
.Ok(doc) => {
var idx = root_index(&doc);
expect_kind(&doc, idx, .String);
expect_text(&doc, idx, "hi\\n"); // 原始切片,未解码
drop(&doc);
},
.Err(error) => { @assert(false, "parse string"); },
}
}
fn test_containers_and_lookup() {
var text = "{\"a\":1,\"b\":[true,null,\"x\"],\"c\":{\"d\":2}}";
var r = parse(ByteView.from_slice(text));
match r {
.Ok(doc) => {
var root = root_index(&doc);
expect_kind(&doc, root, .Object);
var a = ByteView.from_slice("a");
match object_find(&doc, root, a) {
.Found(idx) => {
expect_kind(&doc, idx, .Number);
expect_text(&doc, idx, "1");
},
.NotFound => { @assert(false, "find a"); },
}
var b = ByteView.from_slice("b");
var bidx: usize = 0;
match object_find(&doc, root, b) {
.Found(idx) => { bidx = idx; },
.NotFound => { @assert(false, "find b"); },
}
expect_kind(&doc, bidx, .Array);
@assert(array_count(&doc, bidx) == 3, "array count");
expect_kind(&doc, array_at(&doc, bidx, 0), .True);
expect_kind(&doc, array_at(&doc, bidx, 1), .Null);
expect_text(&doc, array_at(&doc, bidx, 2), "x");
var c = ByteView.from_slice("c");
var cidx: usize = 0;
match object_find(&doc, root, c) {
.Found(idx) => { cidx = idx; },
.NotFound => { @assert(false, "find c"); },
}
var d = ByteView.from_slice("d");
match object_find(&doc, cidx, d) {
.Found(idx) => { expect_text(&doc, idx, "2"); },
.NotFound => { @assert(false, "find c.d"); },
}
var missing = ByteView.from_slice("zzz");
match object_find(&doc, root, missing) {
.Found(idx) => { @assert(false, "unexpected find"); },
.NotFound => {},
}
drop(&doc);
},
.Err(error) => { @assert(false, "parse object"); },
}
}
fn test_roundtrip_compact() {
var text = "{\"a\":1,\"b\":[true,null,\"x\\n\"],\"c\":{},\"d\":[],\"e\":-3.5e-2}";
var r = parse(ByteView.from_slice(text));
match r {
.Ok(doc) => {
var out = ByteBuffer.new();
expect_ok(emit_compact(&out, &doc));
expect_bytes(&out, text);
ByteBuffer.drop(&out);
drop(&doc);
},
.Err(error) => { @assert(false, "parse for roundtrip"); },
}
}
fn test_pretty() {
var text = "{\"a\":1,\"b\":[1,2]}";
var r = parse(ByteView.from_slice(text));
match r {
.Ok(doc) => {
var out = ByteBuffer.new();
expect_ok(emit_pretty(&out, &doc, 2));
var expected = "{\n \"a\": 1,\n \"b\": [\n 1,\n 2\n ]\n}";
expect_bytes(&out, expected);
ByteBuffer.drop(&out);
drop(&doc);
},
.Err(error) => { @assert(false, "parse pretty"); },
}
}
fn test_errors() {
expect_parse_error(parse(ByteView.from_slice("")), 0);
expect_parse_error(parse(ByteView.from_slice("\"abc")), 4);
expect_parse_error(parse(ByteView.from_slice("tru")), 3);
expect_parse_error(parse(ByteView.from_slice("[1,]")), 3);
expect_parse_error(parse(ByteView.from_slice("{\"a\":}")), 5);
expect_parse_error(parse(ByteView.from_slice("1 2")), 2);
expect_parse_error(parse(ByteView.from_slice("01")), 1);
}
fn test_depth_limit() {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
var i: usize = 0;
while i < max_depth() + 8 {
expect_ok(ByteBuffer.append_byte(&out, '['));
i = i + 1;
}
i = 0;
while i < max_depth() + 8 {
expect_ok(ByteBuffer.append_byte(&out, ']'));
i = i + 1;
}
match parse(ByteView.from_slice(ByteBuffer.as_slice(out))) {
.Ok(doc) => { drop(&doc); @assert(false, "deep input should be rejected"); },
.Err(error) => {
match error.kind {
.OutOfRange => {},
_ => { @assert(false, "depth limit error kind"); },
}
},
}
}
fn test_construction_helpers() {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
expect_ok(ByteBuffer.append(&out, "{\"id\":"));
expect_ok(write_u64(&out, 18446744073709551615));
expect_ok(ByteBuffer.append(&out, ",\"n\":"));
expect_ok(write_int(&out, -7));
expect_ok(ByteBuffer.append(&out, "}"));
expect_bytes(&out, "{\"id\":18446744073709551615,\"n\":-7}");
}
fn test_large_array() {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
expect_ok(ByteBuffer.append_byte(&out, '[')); // '['
var i: usize = 0;
while i < 1000 {
if i != 0 { expect_ok(ByteBuffer.append_byte(&out, ',')); }
expect_ok(ByteBuffer.append_byte(&out, '1')); // '1'
i = i + 1;
}
expect_ok(ByteBuffer.append_byte(&out, ']')); // ']'
var text = ByteBuffer.as_slice(out);
var r = parse(ByteView.from_slice(text));
match r {
.Ok(doc) => {
expect_kind(&doc, root_index(&doc), .Array);
@assert(array_count(&doc, root_index(&doc)) == 1000, "large array count");
expect_kind(&doc, array_at(&doc, root_index(&doc), 999), .Number);
drop(&doc);
},
.Err(error) => { @assert(false, "parse large array"); },
}
}
fn main() {
test_scalars();
test_containers_and_lookup();
test_roundtrip_compact();
test_pretty();
test_errors();
test_depth_limit();
test_construction_helpers();
test_large_array();
}
+203
View File
@@ -0,0 +1,203 @@
// Expect retcode = 0
// 编译器自身日志(区别于 diag:diag 负责带源码位置的诊断)。
// 输出到 stderr,格式稳定:[LEVEL] name: message\n。
// 消息为借用;Logger 不拥有 name。fatal 打印后终止进程(退出码 1)。
// write 返回 Status;OOM 归类为 .Overflow(无独立内存耗尽错误码)。
type .{ .Status, .make } = @import("error.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type .{ .format } = @import("fmt.spl");
type .{ .Value, .int, .str } = @import("value.spl");
type .{ .eprint } = @import("io.spl");
type Exit = @import("../sys/exit.spl");
type Level = enum { Debug, Info, Warn, Error, Fatal }
fn level_name(level: Level): []u8 {
match level {
.Debug => { ret "DEBUG"; },
.Info => { ret "INFO "; },
.Warn => { ret "WARN "; },
.Error => { ret "ERROR"; },
.Fatal => { ret "FATAL"; },
}
ret "NOTSET";
}
type Logger = @self;
name: ByteView,
min_level: Level,
fn new(name: ByteView): Logger {
ret Logger { .name = name, .min_level = .Debug };
}
fn set_level(self: *Logger, level: Level) {
self.min_level = level;
}
fn enabled(self: Logger, level: Level): bool {
ret @enum2int(level) >= @enum2int(self.min_level);
}
// 追加成功返回 true;失败(仅 OOM)返回 false。
fn push(line: *ByteBuffer, text: []u8): bool {
match ByteBuffer.append(line, text) {
.Ok => { ret true; },
.Err(error) => { ret false; },
}
ret false;
}
// 低于 min_level 的日志不写出,直接成功。
fn write(self: *Logger, level: Level, message: []u8): Status {
if !enabled(self.*, level) { ret .Ok; }
var line = ByteBuffer.new();
defer { ByteBuffer.drop(&line); }
if !push(&line, "[") { ret .Err(make(.Overflow)); }
if !push(&line, level_name(level)) { ret .Err(make(.Overflow)); }
if !push(&line, "] ") { ret .Err(make(.Overflow)); }
if !push(&line, ByteView.as_slice(self.name)) { ret .Err(make(.Overflow)); }
if !push(&line, ": ") { ret .Err(make(.Overflow)); }
if !push(&line, message) { ret .Err(make(.Overflow)); }
if !push(&line, "\n") { ret .Err(make(.Overflow)); }
ret eprint(ByteBuffer.as_slice(line));
}
fn debug(self: *Logger, message: []u8): Status {
ret write(self, .Debug, message);
}
fn info(self: *Logger, message: []u8): Status {
ret write(self, .Info, message);
}
fn warn(self: *Logger, message: []u8): Status {
ret write(self, .Warn, message);
}
fn error(self: *Logger, message: []u8): Status {
ret write(self, .Error, message);
}
// 尽力写出后终止;写失败无法挽回,直接退出。
fn fatal(self: *Logger, message: []u8) {
write(self, .Fatal, message);
Exit.fail();
}
// 先格式化到临时缓冲;格式错误在写出前返回 Err(.InvalidFormat)。
fn logf(self: *Logger, level: Level, fmt: []u8, args: []Value): Status {
if !enabled(self.*, level) { ret .Ok; }
var message = ByteBuffer.new();
defer { ByteBuffer.drop(&message); }
match format(&message, fmt, args) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret write(self, level, ByteBuffer.as_slice(message));
}
fn debugf(self: *Logger, fmt: []u8, args: []Value): Status {
ret logf(self, .Debug, fmt, args);
}
fn infof(self: *Logger, fmt: []u8, args: []Value): Status {
ret logf(self, .Info, fmt, args);
}
fn warnf(self: *Logger, fmt: []u8, args: []Value): Status {
ret logf(self, .Warn, fmt, args);
}
fn errorf(self: *Logger, fmt: []u8, args: []Value): Status {
ret logf(self, .Error, fmt, args);
}
// 格式化后终止;格式化失败也要退出(避免吞掉致命错误)。
fn fatalf(self: *Logger, fmt: []u8, args: []Value) {
logf(self, .Fatal, fmt, args);
Exit.fail();
}
fn expect_name(level: Level, expected: []u8) {
@assert(ByteView.eq(ByteView.from_slice(level_name(level)),
ByteView.from_slice(expected)),
"level name");
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected log error"); },
}
}
fn test_level_names() {
expect_name(.Debug, "DEBUG");
expect_name(.Info, "INFO ");
expect_name(.Warn, "WARN ");
expect_name(.Error, "ERROR");
expect_name(.Fatal, "FATAL");
var debug: Level = .Debug;
var info: Level = .Info;
var warn: Level = .Warn;
var err: Level = .Error;
var fatal: Level = .Fatal;
@assert(@enum2int(fatal) > @enum2int(err), "fatal above error");
@assert(@enum2int(err) > @enum2int(warn), "error above warn");
@assert(@enum2int(warn) > @enum2int(info), "warn above info");
@assert(@enum2int(info) > @enum2int(debug), "info above debug");
}
fn test_filter_and_write() {
var log = new(ByteView.from_slice("splc1"));
@assert(enabled(log, .Debug), "default shows debug");
@assert(enabled(log, .Fatal), "default shows fatal");
set_level(&log, .Warn);
@assert(!enabled(log, .Debug), "filtered debug");
@assert(!enabled(log, .Info), "filtered info");
@assert(enabled(log, .Warn), "warn passes");
@assert(enabled(log, .Error), "error passes");
// 被过滤的写入不产生输出,且返回成功。
expect_ok(debug(&log, "hidden"));
expect_ok(write(&log, .Info, "hidden too"));
expect_ok(error(&log, "visible"));
}
fn test_logf() {
var log = new(ByteView.from_slice("splc1"));
set_level(&log, .Error);
var args: [2]Value = .[int(3), str("x")];
// 被过滤的级别不写出,直接成功。
expect_ok(infof(&log, "n={} s={}", args[0..2]));
// 通过级别正常写出。
expect_ok(errorf(&log, "n={} s={}", args[0..2]));
// 格式化失败向上传播,且不产出消息。
match errorf(&log, "{} {}", args[0..1]) {
.Ok => { @assert(false, "expected invalid format"); },
.Err(error) => {
match error.kind {
.InvalidFormat => {},
_ => { @assert(false, "expected InvalidFormat"); },
}
},
}
}
fn main() {
test_level_names();
test_filter_and_write();
test_logf();
}
+196
View File
@@ -0,0 +1,196 @@
// Expect retcode = 0
// LSP 传输帧:Content-Length 头 + 空行 + 正文。
// 这里只做纯逻辑(写入/解析 ByteBuffer/ByteView),真正的 stdin/stdout I/O 由上层负责。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type .{ .append_usize } = @import("fmt.spl");
type .{ .checked_add, .checked_mul } = @import("memory.spl");
type .{ .to_lower } = @import("ascii.spl");
type FrameResult = enum {
Ok: usize, // 消耗的字节数
Incomplete,
Err: Error,
}
// data[j..] 是否以 word 开头(ASCII 大小写不敏感)。
fn ci_prefix(data: *u8, len: usize, j: usize, word: []u8): bool {
if j + word.len > len { ret false; }
var i: usize = 0;
while i < word.len {
if to_lower(data[j + i]) != to_lower(word.ptr[i]) { ret false; }
i = i + 1;
}
ret true;
}
fn write_frame(out: *ByteBuffer, body: []u8): Status {
match ByteBuffer.append(out, "Content-Length: ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
match append_usize(out, body.len, 10, false) { .Ok => {}, .Err(e) => { ret .Err(e); } }
match ByteBuffer.append(out, "\r\n\r\n") { .Ok => {}, .Err(e) => { ret .Err(e); } }
ret ByteBuffer.append(out, body);
}
// 从 input 解析一个帧;正文复制到 body_out。缺数据返回 Incomplete。
fn parse_frame(input: ByteView, body_out: *ByteBuffer): FrameResult {
var data = ByteView.as_slice(input);
var len = data.len;
// 找头结束标记 \r\n\r\n
var term = ~(0 as usize);
var i: usize = 0;
while i + 3 < len {
if data[i] == '\r' && data[i + 1] == '\n' && data[i + 2] == '\r' && data[i + 3] == '\n' {
term = i;
break;
}
i = i + 1;
}
if term == ~(0 as usize) { ret .Incomplete; }
// 在头内找 Content-Length(大小写不敏感)
var content_len: usize = 0;
var found = false;
var j: usize = 0;
while j + 15 <= term {
if ci_prefix(data.ptr, term, j, "content-length:") {
var k = j + 15;
while k < term && (data[k] == ' ' || data[k] == '\t') { k = k + 1; }
var n: usize = 0;
var digits = 0;
while k < term && data[k] >= '0' && data[k] <= '9' {
var digit = (data[k] - '0') as usize;
var scaled: usize = 0;
match checked_mul(n, 10) {
.Ok(value) => { scaled = value; },
.Err(error) => { ret .Err(error); },
}
match checked_add(scaled, digit) {
.Ok(value) => { n = value; },
.Err(error) => { ret .Err(error); },
}
digits = digits + 1;
k = k + 1;
}
if digits == 0 { ret .Err(make(.InvalidDigit)); }
content_len = n;
found = true;
break;
}
j = j + 1;
}
if !found { ret .Err(make(.InvalidFormat)); }
var body_start = term + 4;
if len < body_start || content_len > len - body_start { ret .Incomplete; }
// 先写入临时缓冲区,成功后再替换 body_out,失败不产生部分输出。
var fresh = ByteBuffer.new();
var body = data[body_start..body_start + content_len];
match ByteBuffer.append(&fresh, body) {
.Ok => {},
.Err(e) => { ByteBuffer.drop(&fresh); ret .Err(e); },
}
ByteBuffer.drop(body_out);
body_out.* = ByteBuffer.take(&fresh);
ret .Ok(body_start + content_len);
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected frame error"); },
}
}
fn expect_body(out: *ByteBuffer, expected: []u8) {
var actual = ByteBuffer.as_slice(out.*);
@assert(actual.len == expected.len, "body length");
var i: usize = 0;
while i < expected.len {
@assert(actual.ptr[i] == expected.ptr[i], "body byte");
i = i + 1;
}
}
fn test_roundtrip() {
var wire = ByteBuffer.new();
defer { ByteBuffer.drop(&wire); }
expect_ok(write_frame(&wire, "{\"jsonrpc\":\"2.0\"}"));
var bytes = ByteBuffer.as_slice(wire);
@assert(bytes.len == 16 + 2 + 4 + 17, "wire length"); // "Content-Length: 17\r\n\r\n" + body
var body = ByteBuffer.new();
defer { ByteBuffer.drop(&body); }
match parse_frame(ByteView.from_slice(bytes), &body) {
.Ok(used) => {
@assert(used == bytes.len, "consumed all");
expect_body(&body, "{\"jsonrpc\":\"2.0\"}");
},
.Incomplete => { @assert(false, "unexpected incomplete"); },
.Err(error) => { @assert(false, "unexpected err"); },
}
}
fn test_multiple_and_leftover() {
var wire = ByteBuffer.new();
defer { ByteBuffer.drop(&wire); }
expect_ok(write_frame(&wire, "abcd"));
expect_ok(write_frame(&wire, "ef"));
var bytes = ByteBuffer.as_slice(wire);
var body = ByteBuffer.new();
defer { ByteBuffer.drop(&body); }
match parse_frame(ByteView.from_slice(bytes), &body) {
.Ok(used) => {
expect_body(&body, "abcd");
var rest = bytes[used..bytes.len];
match parse_frame(ByteView.from_slice(rest), &body) {
.Ok(used2) => { expect_body(&body, "ef"); @assert(used2 == rest.len, "second"); },
_ => { @assert(false, "second frame"); },
}
},
_ => { @assert(false, "first frame"); },
}
}
fn test_incomplete_and_errors() {
var body = ByteBuffer.new();
defer { ByteBuffer.drop(&body); }
// 头未结束
var h1 = "Content-Length: 3\r\n";
match parse_frame(ByteView.from_slice(h1), &body) {
.Incomplete => {},
_ => { @assert(false, "h1"); },
}
// 正文不足
var h2 = "Content-Length: 3\r\n\r\nab";
match parse_frame(ByteView.from_slice(h2), &body) {
.Incomplete => {},
_ => { @assert(false, "h2"); },
}
// 缺 Content-Length
var h3 = "X: 1\r\n\r\n";
match parse_frame(ByteView.from_slice(h3), &body) {
.Ok(used) => { @assert(false, "h3 ok"); },
.Incomplete => { @assert(false, "h3 incomplete"); },
.Err(error) => {},
}
// 大小写不敏感 + 额外头
var h4 = "X-Test: yes\r\ncontent-length: 2\r\n\r\nhi";
match parse_frame(ByteView.from_slice(h4), &body) {
.Ok(used) => { expect_body(&body, "hi"); },
_ => { @assert(false, "h4"); },
}
}
fn main() {
test_roundtrip();
test_multiple_and_leftover();
test_incomplete_and_errors();
}
+356
View File
@@ -0,0 +1,356 @@
// Expect retcode = 0
// 开放寻址 + tombstone 哈希表。键和值都按字节复制到表自己的存储。
// 键类型由 hash_fn / eq_fn 解释:回调收到指向键字节的指针(如 *usize / *ByteView)。
// 普通赋值只复制句柄(与 Vec 一样),所有权转移必须使用 take。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .Vec } = @import("vec.spl");
type .{ .ByteView } = @import("byte_view.spl");
#extern(vm) fn vm_map_valid(m: *any): i32;
#extern(vm) fn vm_map_reserve(m: *any, need: usize): i32;
#extern(vm) fn vm_map_put(m: *any, key: *any, val: *any): i32;
#extern(vm) fn vm_map_get(m: *any, key: *any, out: *any): i32;
#extern(vm) fn vm_map_del(m: *any, key: *any): i32;
#extern(vm) fn vm_map_clear(m: *any): void;
#extern(vm) fn vm_map_drop(m: *any): void;
// 槽位状态:Empty/Occupied/Deleted(墓碑,仅在扩容重哈希时清除)。
// 负载因子 70/128。
type SlotState = enum {
Empty,
Occupied,
Deleted,
}
// 查找/删除结果:显式区分命中与缺失,替代 bool 哨兵。
type LookupResult = enum {
Found,
NotFound,
}
type Map = @self;
keys: Vec,
vals: Vec,
states: Vec,
size: usize,
key_size: usize,
val_size: usize,
hash_kind: usize,
// 键解释:0 = usize;1 = ByteView(按内容哈希/比较)。
fn hash_kind_usize(): usize { ret 0; }
fn hash_kind_view(): usize { ret 1; }
fn hash_kind_constant(): usize { ret 2; }
fn blank(key_size: usize, key_align: usize, val_size: usize, val_align: usize,
hash_kind: usize): Map {
ret Map {
.keys = Vec.blank(key_size, key_align),
.vals = Vec.blank(val_size, val_align),
.states = Vec.blank(1, 1),
.size = 0,
.key_size = key_size,
.val_size = val_size,
.hash_kind = hash_kind,
};
}
fn new(key_size: usize, key_align: usize, val_size: usize, val_align: usize,
hash_kind: usize): Map {
ret blank(key_size, key_align, val_size, val_align, hash_kind);
}
fn status_from_code(code: i32): Status {
if code == 0 { ret .Ok; }
if code == 2 { ret .Err(make(.OutOfRange)); }
if code == 3 { ret .Err(make(.Overflow)); }
if code == 4 { ret .Err(make(.InvalidAlignment)); }
ret .Err(make(.InvalidArgument));
}
fn lookup_from_code(code: i32): LookupResult {
if code == 0 { ret .Found; }
ret .NotFound;
}
fn valid(self: *Map): bool {
ret vm_map_valid(self as *any) != 0;
}
fn size(self: Map): usize {
ret self.size;
}
fn capacity(self: Map): usize {
ret self.states.cap;
}
// 迭代支持:遍历 [0, slot_count),slot_state 为 1 时 key_at/val_at 有效。
fn slot_count(self: Map): usize {
ret self.states.len;
}
fn slot_state(self: Map, index: usize): SlotState {
var code = self.states.data[index];
if code == 1 { ret .Occupied; }
if code == 2 { ret .Deleted; }
ret .Empty;
}
fn slot_occupied(state: SlotState): bool {
match state {
.Occupied => { ret true; },
.Empty => { ret false; },
.Deleted => { ret false; },
}
ret false;
}
fn key_at(self: *Map, index: usize): *u8 {
ret self.keys.data + index * self.key_size;
}
fn val_at(self: *Map, index: usize): *u8 {
ret self.vals.data + index * self.val_size;
}
// 保证至少 need 个槽位。所有检查在旧表改动前完成,失败保持原表不变。
fn reserve(self: *Map, need: usize): Status {
ret status_from_code(vm_map_reserve(self as *any, need));
}
// 覆盖已存在的键;否则插入并可能扩容。
fn put(self: *Map, key: *u8, val: *u8): Status {
ret status_from_code(vm_map_put(self as *any, key as *any, val as *any));
}
// 找到键则把值复制到 out 并返回 Found;否则 out 不变并返回 NotFound。
fn get(self: *Map, key: *u8, out: *u8): LookupResult {
ret lookup_from_code(vm_map_get(self as *any, key as *any, out as *any));
}
fn del(self: *Map, key: *u8): LookupResult {
ret lookup_from_code(vm_map_del(self as *any, key as *any));
}
// 清空内容但保留容量。墓碑与负载计数一并复位。
fn clear(self: *Map) {
vm_map_clear(self as *any);
}
fn drop(self: *Map) {
vm_map_drop(self as *any);
}
fn take(source: *Map): Map {
var result = source.*;
source.keys = Vec.blank(source.key_size, source.keys.elem_align);
source.vals = Vec.blank(source.val_size, source.vals.elem_align);
source.states = Vec.blank(1, 1);
source.size = 0;
ret result;
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected Map error"); },
}
}
fn expect_invalid(result: Status) {
match result {
.Ok => { @assert(false, "expected Map error"); },
.Err(error) => {
match error.kind {
.InvalidArgument => {},
_ => { @assert(false, "unexpected Map error kind"); },
}
},
}
}
fn count_occupied(m: *Map): usize {
var n: usize = 0;
var i: usize = 0;
while i < slot_count(m.*) {
if slot_occupied(slot_state(m.*, i)) { n = n + 1; }
i = i + 1;
}
ret n;
}
fn found(m: *Map, key: *u8, out: *u8): bool {
match get(m, key, out) {
.Found => { ret true; },
.NotFound => { ret false; },
}
ret false;
}
fn deleted(m: *Map, key: *u8): bool {
match del(m, key) {
.Found => { ret true; },
.NotFound => { ret false; },
}
ret false;
}
fn make_int_map(): Map {
ret new(@sizeof(usize), @alignof(usize), @sizeof(i32), @alignof(i32), hash_kind_usize());
}
fn test_put_get_update() {
var m = make_int_map();
@assert(size(m) == 0 && capacity(m) == 0, "empty map");
@assert(valid(&m));
var key: usize = 0;
var value: i32 = 0;
var out: i32 = 0;
expect_invalid(put(&m, null, &value as *u8));
expect_invalid(put(&m, &key as *u8, null));
@assert(size(m) == 0, "failed put preserves map");
key = 7;
value = 70;
expect_ok(put(&m, &key as *u8, &value as *u8));
@assert(size(m) == 1 && valid(&m));
out = -1;
@assert(found(&m, &key as *u8, &out as *u8) && out == 70, "insert then get");
value = 71;
expect_ok(put(&m, &key as *u8, &value as *u8));
@assert(size(m) == 1, "update does not grow size");
out = -1;
@assert(found(&m, &key as *u8, &out as *u8) && out == 71, "update overwrites");
key = 8;
out = 123;
@assert(!found(&m, &key as *u8, &out as *u8) && out == 123, "missing key preserves out");
// drop 后还有断言,故保持手动释放,不改为 defer。
drop(&m);
@assert(size(m) == 0 && capacity(m) == 0, "drop resets map");
}
fn test_growth_and_delete() {
var m = make_int_map();
defer { drop(&m); }
var i: i32 = 0;
while i < 1000 {
var key: usize = i as usize;
var value: i32 = i * 3 - 7;
expect_ok(put(&m, &key as *u8, &value as *u8));
i = i + 1;
}
@assert(size(m) == 1000);
@assert(count_occupied(&m) == 1000, "all slots accounted");
i = 0;
while i < 1000 {
var key: usize = i as usize;
var out: i32 = 0;
@assert(found(&m, &key as *u8, &out as *u8), "get after growth");
@assert(out == i * 3 - 7, "value after growth");
i = i + 1;
}
i = 0;
while i < 500 {
var key: usize = i as usize;
@assert(deleted(&m, &key as *u8));
i = i + 1;
}
@assert(size(m) == 500 && count_occupied(&m) == 500, "deleted slots not occupied");
i = 0;
while i < 500 {
var key: usize = i as usize;
var out: i32 = 0;
@assert(!found(&m, &key as *u8, &out as *u8), "deleted key missing");
i = i + 1;
}
// 墓碑槽位可被再次插入复用,且不会重复计数。
var key: usize = 12345;
var value: i32 = 99;
expect_ok(put(&m, &key as *u8, &value as *u8));
@assert(size(m) == 501);
var out: i32 = 0;
@assert(found(&m, &key as *u8, &out as *u8) && out == 99, "reuse tombstone");
@assert(deleted(&m, &key as *u8), "delete key");
@assert(size(m) == 500);
@assert(!found(&m, &key as *u8, &out as *u8));
clear(&m);
@assert(size(m) == 0 && count_occupied(&m) == 0, "clear empties");
}
fn test_take_and_self_put() {
var m = make_int_map();
defer { drop(&m); }
var key: usize = 5;
var value: i32 = 55;
expect_ok(put(&m, &key as *u8, &value as *u8));
var moved = take(&m);
defer { drop(&moved); }
@assert(size(m) == 0 && capacity(m) == 0, "take leaves source empty");
var out: i32 = 0;
@assert(found(&moved, &key as *u8, &out as *u8) && out == 55, "moved keeps content");
// 值指针可以指向表内已有的值(按字节移动,允许重叠)。
var alias: i32 = 0;
@assert(found(&moved, &key as *u8, &alias as *u8));
expect_ok(put(&moved, &key as *u8, &alias as *u8));
@assert(found(&moved, &key as *u8, &out as *u8) && out == 55, "self value put");
}
fn test_byte_view_keys() {
var m = new(@sizeof(ByteView), @alignof(ByteView), @sizeof(i32), @alignof(i32),
hash_kind_view());
defer { drop(&m); }
var alpha = ByteView.from_slice("alpha");
var beta = ByteView.from_slice("beta");
var gamma = ByteView.from_slice("gamma");
var one: i32 = 1;
var two: i32 = 2;
var three: i32 = 3;
expect_ok(put(&m, &alpha as *u8, &one as *u8));
expect_ok(put(&m, &beta as *u8, &two as *u8));
expect_ok(put(&m, &gamma as *u8, &three as *u8));
@assert(size(m) == 3);
var out: i32 = 0;
@assert(found(&m, &alpha as *u8, &out as *u8) && out == 1, "alpha");
@assert(found(&m, &beta as *u8, &out as *u8) && out == 2, "beta");
@assert(found(&m, &gamma as *u8, &out as *u8) && out == 3, "gamma");
// 内容相同的不同 ByteView 视为同一个键。
var alpha2 = ByteView.from_slice("alpha");
out = 0;
@assert(found(&m, &alpha2 as *u8, &out as *u8) && out == 1, "equal content key");
@assert(size(m) == 3, "equal content does not insert");
@assert(deleted(&m, &beta as *u8), "delete beta");
@assert(size(m) == 2);
@assert(!found(&m, &beta as *u8, &out as *u8));
}
fn main() {
test_put_get_update();
test_growth_and_delete();
test_take_and_self_put();
test_byte_view_keys();
}
+264
View File
@@ -0,0 +1,264 @@
// Expect retcode = 0
// 尺寸计算、字节移动与内存分配统一门面(分配/释放转发自 sys/mem)。
// 本模块不改变任何资源的所有权。
type .{ .Error, .Status, .make } = @import("error.spl");
type SysMem = @import("../sys/mem.spl");
// 分配/释放/字节操作转发(core 只经本模块使用内存,不直接依赖 sys)。
fn alloc(sz: usize): *any { ret SysMem.alloc(sz); }
fn free(p: *any) { SysMem.free(p); }
fn realloc(p: *any, sz: usize): *any { ret SysMem.realloc(p, sz); }
fn copy(dst: *any, src: *any, n: usize): *any { ret SysMem.copy(dst, src, n); }
fn cmp(a: *any, b: *any, n: usize): isize { ret SysMem.cmp(a, b, n); }
fn set(dst: *any, byte: u8, n: usize): *any { ret SysMem.set(dst, byte, n); }
fn zero(dst: *any, n: usize): *any { ret SysMem.zero(dst, n); }
#extern(vm) fn vm_memcpy(dst: *any, src: *any, n: usize): *any;
#extern(vm) fn vm_memmove(dst: *any, src: *any, n: usize): *any;
type SizeResult = enum {
Ok: usize,
Err: Error,
}
fn checked_add(a: usize, b: usize): SizeResult {
var limit: usize = ~(0 as usize);
if b > limit - a { ret .Err(make(.Overflow)); }
ret .Ok(a + b);
}
fn checked_mul(a: usize, b: usize): SizeResult {
if a == 0 { ret .Ok(0); }
var limit: usize = ~(0 as usize);
if b > limit / a { ret .Err(make(.Overflow)); }
ret .Ok(a * b);
}
// alignment 必须是非零的二次幂。已对齐的值保持不变。
fn align_up(size: usize, alignment: usize): SizeResult {
if alignment == 0 { ret .Err(make(.InvalidAlignment)); }
var mask = alignment - 1;
if (alignment & mask) != 0 { ret .Err(make(.InvalidAlignment)); }
var limit: usize = ~(0 as usize);
if size > limit - mask { ret .Err(make(.Overflow)); }
ret .Ok((size + mask) & ~mask);
}
// 计算至少 need 的容量:从 current 起(不低于 min_cap)按倍增增长;
// 无法再倍增(会溢出)时直接取 need。need 可表示时不溢出。
fn grow_capacity(current: usize, min_cap: usize, need: usize): usize {
var cap = current;
if cap < min_cap { cap = min_cap; }
var limit: usize = ~(0 as usize);
while cap < need {
if cap > limit / 2 {
cap = need;
break;
}
cap = cap * 2;
}
ret cap;
}
// 将 src.len 个字节复制到 dst 的开头,允许两个切片任意重叠。
// 零长度源不访问内存,允许空指针。所有可恢复错误均在首次写入前返回。
// 非空源要求可读,目标前 src.len 字节要求可写,生命周期由调用方保证。
// 长度和地址计算检查不能证明底层分配大小,也不能识别悬空指针。
fn move(dst: []u8, src: []u8): Status {
var count = src.len;
if count == 0 { ret .Ok; }
if dst.len < count { ret .Err(make(.OutOfRange)); }
if dst.ptr == null || src.ptr == null {
ret .Err(make(.InvalidArgument));
}
var dst_addr = dst.ptr as usize;
var src_addr = src.ptr as usize;
var limit: usize = ~(0 as usize);
var last_offset = count - 1;
if last_offset > limit - dst_addr || last_offset > limit - src_addr {
ret .Err(make(.Overflow));
}
if dst_addr == src_addr { ret .Ok; }
// 重叠安全,统一走原生 memmove。
vm_memmove(dst.ptr as *any, src.ptr as *any, count);
ret .Ok;
}
fn expect_size(result: SizeResult, expected: usize) {
match result {
.Ok(value) => { @assert(value == expected, "checked size result"); },
.Err(error) => { @assert(false, "unexpected size error"); },
}
}
fn expect_overflow(result: SizeResult) {
match result {
.Ok(value) => { @assert(false, "expected overflow"); },
.Err(error) => {
match error.kind {
.Overflow => {},
_ => { @assert(false, "expected overflow kind"); },
}
},
}
}
fn expect_alignment_error(result: SizeResult) {
match result {
.Ok(value) => { @assert(false, "expected invalid alignment"); },
.Err(error) => {
match error.kind {
.InvalidAlignment => {},
_ => { @assert(false, "expected invalid alignment kind"); },
}
},
}
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected move error"); },
}
}
fn expect_invalid_move(result: Status) {
match result {
.Ok => { @assert(false, "expected invalid pointer"); },
.Err(error) => {
match error.kind {
.InvalidArgument => {},
_ => { @assert(false, "expected invalid argument kind"); },
}
},
}
}
fn expect_move_overflow(result: Status) {
match result {
.Ok => { @assert(false, "expected address overflow"); },
.Err(error) => {
match error.kind {
.Overflow => {},
_ => { @assert(false, "expected address overflow kind"); },
}
},
}
}
fn test_sizes() {
var limit: usize = ~(0 as usize);
expect_size(checked_add(0, 0), 0);
expect_size(checked_add(17, 25), 42);
expect_size(checked_add(limit, 0), limit);
expect_size(checked_add(limit - 1, 1), limit);
expect_overflow(checked_add(limit, 1));
expect_overflow(checked_add(1, limit));
expect_size(checked_mul(6, 7), 42);
expect_size(checked_mul(0, limit), 0);
expect_size(checked_mul(limit, 0), 0);
expect_size(checked_mul(limit, 1), limit);
expect_size(checked_mul(limit / 2, 2), limit - 1);
expect_overflow(checked_mul(limit / 2 + 1, 2));
expect_overflow(checked_mul(limit, limit));
expect_size(align_up(0, 8), 0);
expect_size(align_up(1, 8), 8);
expect_size(align_up(8, 8), 8);
expect_size(align_up(9, 8), 16);
expect_size(align_up(limit, 1), limit);
expect_size(align_up(limit - 7, 8), limit - 7);
expect_overflow(align_up(limit - 6, 8));
expect_overflow(align_up(limit, 2));
expect_alignment_error(align_up(0, 0));
expect_alignment_error(align_up(0, 3));
expect_alignment_error(align_up(7, 6));
var highest_power = limit / 2 + 1;
expect_size(align_up(0, highest_power), 0);
expect_size(align_up(1, highest_power), highest_power);
expect_size(align_up(highest_power, highest_power), highest_power);
expect_overflow(align_up(highest_power + 1, highest_power));
@assert(grow_capacity(0, 8, 0) == 8, "grow respects minimum");
@assert(grow_capacity(0, 8, 5) == 8, "grow minimum covers need");
@assert(grow_capacity(8, 8, 9) == 16, "grow doubles");
@assert(grow_capacity(8, 8, 100) == 128, "grow repeats");
@assert(grow_capacity(limit / 2, 8, limit) == limit, "grow exact to limit");
@assert(grow_capacity(limit / 2 + 1, 8, limit) == limit, "grow clamps to need");
}
// 穷举同一 8 字节数组内的所有合法源、目标和长度组合。
// expected 从原始字节值建立快照,不使用被测 move 构造期望值。
fn test_overlap() {
var src_start: usize = 0;
while src_start <= 8 {
var dst_start: usize = 0;
while dst_start <= 8 {
var count: usize = 0;
while count <= 8 - src_start && count <= 8 - dst_start {
var data: [8]u8 = .[0, 1, 2, 3, 4, 5, 6, 7];
var expected: [8]u8 = .[0, 1, 2, 3, 4, 5, 6, 7];
var i: usize = 0;
while i < count {
expected[dst_start + i] = (src_start + i) as u8;
i = i + 1;
}
expect_ok(move(data[dst_start..8], data[src_start..src_start + count]));
i = 0;
while i < 8 {
@assert(data[i] == expected[i], "overlap and surrounding bytes");
i = i + 1;
}
count = count + 1;
}
dst_start = dst_start + 1;
}
src_start = src_start + 1;
}
}
fn test_move_errors() {
var data: [4]u8 = .[10, 20, 30, 40];
var empty: []u8 = .{ .ptr = null, .len = 0 };
expect_ok(move(empty, empty));
expect_ok(move(data[0..4], empty));
match move(data[0..1], data[2..4]) {
.Ok => { @assert(false, "expected short destination"); },
.Err(error) => {
match error.kind {
.OutOfRange => {},
_ => { @assert(false, "expected out of range kind"); },
}
},
}
var invalid: []u8 = .{ .ptr = null, .len = 2 };
expect_invalid_move(move(data[0..4], invalid));
expect_invalid_move(move(invalid, data[0..2]));
// 伪造仅用于验证拒绝路径的地址,必须在任何解引用之前报告错误。
var limit: usize = ~(0 as usize);
var overflowing: []u8 = .{ .ptr = limit as *u8, .len = 2 };
expect_move_overflow(move(data[0..4], overflowing));
expect_move_overflow(move(overflowing, data[0..2]));
expect_move_overflow(move(overflowing, overflowing));
@assert(data[0] == 10, "failure preserves destination byte 0");
@assert(data[1] == 20, "failure preserves destination byte 1");
@assert(data[2] == 30, "failure preserves destination byte 2");
@assert(data[3] == 40, "failure preserves destination byte 3");
}
fn main() {
test_sizes();
test_overlap();
test_move_errors();
}
+1643
View File
File diff suppressed because it is too large. Load diff
+399
View File
@@ -0,0 +1,399 @@
// Expect retcode = 0
// 纯词法路径工具,不访问文件系统。调用方必须显式选择 POSIX 或 Windows 规则;
// 输入视图均为借用,拥有型结果写入 ByteBuffer,失败保持原输出不变。
type .{ .Status, .make } = @import("error.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type .{ .Vec } = @import("vec.spl");
type .{ .is_alpha } = @import("ascii.spl");
type PathStyle = enum { Posix, Windows }
fn is_windows(style: PathStyle): bool {
match style { .Posix => { ret false; }, .Windows => { ret true; }, }
ret false;
}
fn separator(style: PathStyle): u8 {
if is_windows(style) { ret '\\'; }
ret '/';
}
fn is_sep(style: PathStyle, byte: u8): bool {
if is_windows(style) { ret byte == '/' || byte == '\\'; }
ret byte == '/';
}
fn is_drive_prefix(path: ByteView): bool {
ret path.len >= 2 && is_alpha(path.ptr[0]) && path.ptr[1] == ':';
}
fn is_unc(path: ByteView, style: PathStyle): bool {
ret is_windows(style) && path.len >= 2 &&
is_sep(style, path.ptr[0]) && is_sep(style, path.ptr[1]);
}
fn is_absolute(path: ByteView, style: PathStyle): bool {
if path.len == 0 { ret false; }
if !is_windows(style) { ret path.ptr[0] == '/'; }
if is_unc(path, style) { ret true; }
if is_drive_prefix(path) {
ret path.len >= 3 && is_sep(style, path.ptr[2]);
}
// 根相对路径以当前盘符为根,本模块将其视为绝对。
ret is_sep(style, path.ptr[0]);
}
// UNC 根长度不含共享名后的分隔符;不完整时返回 0。
fn unc_root_len(path: ByteView, style: PathStyle): usize {
if !is_unc(path, style) { ret 0; }
var i: usize = 2;
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
var server = i;
while i < path.len && !is_sep(style, path.ptr[i]) { i = i + 1; }
if i == server { ret 0; }
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
var share = i;
while i < path.len && !is_sep(style, path.ptr[i]) { i = i + 1; }
if i == share { ret 0; }
ret i;
}
fn root_len(path: ByteView, style: PathStyle): usize {
if path.len == 0 { ret 0; }
if !is_windows(style) {
if path.ptr[0] == '/' { ret 1; }
ret 0;
}
var unc = unc_root_len(path, style);
if unc != 0 { ret unc; }
if is_drive_prefix(path) {
if path.len >= 3 && is_sep(style, path.ptr[2]) { ret 3; }
ret 2;
}
if is_sep(style, path.ptr[0]) { ret 1; }
ret 0;
}
fn last_sep(path: ByteView, style: PathStyle): usize {
var i = path.len;
while i > 0 {
i = i - 1;
if is_sep(style, path.ptr[i]) { ret i; }
}
ret ~(0 as usize);
}
fn slice_or_empty(path: ByteView, start: usize, end: usize): ByteView {
match ByteView.slice(path, start, end) {
.Ok(value) => { ret value; },
.Err(error) => { ret ByteView.empty(); },
}
ret ByteView.empty();
}
fn dirname(path: ByteView, style: PathStyle): ByteView {
var root = root_len(path, style);
if root != 0 && path.len == root { ret path; }
var index = last_sep(path, style);
if index == ~(0 as usize) {
if is_windows(style) && is_drive_prefix(path) {
ret slice_or_empty(path, 0, 2);
}
ret ByteView.empty();
}
if root != 0 && index <= root { ret slice_or_empty(path, 0, root); }
if index == 0 { ret slice_or_empty(path, 0, 1); }
ret slice_or_empty(path, 0, index);
}
fn basename(path: ByteView, style: PathStyle): ByteView {
var root = root_len(path, style);
if root != 0 && path.len == root { ret ByteView.empty(); }
var index = last_sep(path, style);
if index == ~(0 as usize) {
if is_windows(style) && is_drive_prefix(path) {
ret slice_or_empty(path, 2, path.len);
}
ret path;
}
ret slice_or_empty(path, index + 1, path.len);
}
fn extension(path: ByteView, style: PathStyle): ByteView {
var base = basename(path, style);
var i = base.len;
while i > 0 {
i = i - 1;
if base.ptr[i] == '.' {
if i == 0 { ret ByteView.empty(); }
ret slice_or_empty(base, i + 1, base.len);
}
}
ret ByteView.empty();
}
// Windows 盘符相对路径依赖进程的盘符工作目录,join 明确拒绝该歧义。
fn join_into(out: *ByteBuffer, dir: ByteView,
rel: ByteView, style: PathStyle): Status {
if out == null { ret .Err(make(.InvalidArgument)); }
var fresh = ByteBuffer.new();
if is_absolute(rel, style) {
match ByteBuffer.append(&fresh, ByteView.as_slice(rel)) {
.Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
}
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
if is_windows(style) && is_drive_prefix(rel) {
ByteBuffer.drop(&fresh);
ret .Err(make(.InvalidArgument));
}
match ByteBuffer.append(&fresh, ByteView.as_slice(dir)) {
.Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
}
var drive_relative_base = is_windows(style) && dir.len == 2 && is_drive_prefix(dir);
if dir.len != 0 && !is_sep(style, dir.ptr[dir.len - 1]) && rel.len != 0 &&
!drive_relative_base {
match ByteBuffer.append_byte(&fresh, separator(style)) {
.Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
}
}
match ByteBuffer.append(&fresh, ByteView.as_slice(rel)) {
.Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
}
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
fn segment_is_parent(buffer: ByteBuffer, rollback: usize, style: PathStyle): bool {
var start = rollback;
if start < buffer.storage.len && is_sep(style, buffer.storage.data[start]) {
start = start + 1;
}
ret buffer.storage.len - start == 2 &&
buffer.storage.data[start] == '.' && buffer.storage.data[start + 1] == '.';
}
fn append_segment(out: *ByteBuffer, starts: *Vec,
segment: ByteView, style: PathStyle): Status {
var rollback = out.storage.len;
if rollback != 0 && !is_sep(style, out.storage.data[rollback - 1]) &&
!(is_windows(style) && out.storage.data[rollback - 1] == ':') {
match ByteBuffer.append_byte(out, separator(style)) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
}
match Vec.push(starts, &rollback as *u8) {
.Ok => {},
.Err(error) => { out.storage.len = rollback; ret .Err(error); },
}
match ByteBuffer.append(out, ByteView.as_slice(segment)) {
.Ok => {},
.Err(error) => {
starts.len = starts.len - 1;
out.storage.len = rollback;
ret .Err(error);
},
}
ret .Ok;
}
// 规范化分隔符并折叠 '.'/'..'。相对路径无法消去的前导 '..' 会被保留;
// 绝对路径的 '..' 不会越过根,UNC 的 server/share 也不可被消去。
fn normalize_into(out: *ByteBuffer, path: ByteView, style: PathStyle): Status {
if out == null { ret .Err(make(.InvalidArgument)); }
var fresh = ByteBuffer.new();
var starts = Vec.blank(@sizeof(usize), @alignof(usize));
var i: usize = 0;
var absolute = false;
var protected: usize = 0;
if path.len != 0 {
if !is_windows(style) && path.ptr[0] == '/' {
absolute = true;
match ByteBuffer.append_byte(&fresh, '/') {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
} else if is_unc(path, style) {
absolute = true;
protected = 2;
match ByteBuffer.append(&fresh, "\\\\") {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
i = 2;
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
} else if is_windows(style) && is_drive_prefix(path) {
match ByteBuffer.append(&fresh, path.ptr[0..2]) {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
i = 2;
if i < path.len && is_sep(style, path.ptr[i]) {
absolute = true;
match ByteBuffer.append_byte(&fresh, '\\') {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
}
} else if is_windows(style) && is_sep(style, path.ptr[0]) {
absolute = true;
match ByteBuffer.append_byte(&fresh, '\\') {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
}
}
while i < path.len {
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
if i >= path.len { break; }
var begin = i;
while i < path.len && !is_sep(style, path.ptr[i]) { i = i + 1; }
var segment = slice_or_empty(path, begin, i);
if ByteView.eq(segment, ByteView.from_slice(".")) {
if protected != 0 && starts.len < protected {
Vec.drop(&starts); ByteBuffer.drop(&fresh);
ret .Err(make(.InvalidArgument));
}
continue;
}
if ByteView.eq(segment, ByteView.from_slice("..")) {
if protected != 0 && starts.len < protected {
Vec.drop(&starts); ByteBuffer.drop(&fresh);
ret .Err(make(.InvalidArgument));
}
if starts.len > protected {
var last: usize = 0;
match Vec.get(&starts, starts.len - 1) {
.Ok(ptr) => { last = (ptr as *usize).*; },
.Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
if !segment_is_parent(fresh, last, style) {
fresh.storage.len = last;
starts.len = starts.len - 1;
continue;
}
}
if absolute { continue; }
}
match append_segment(&fresh, &starts, segment, style) {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
}
if protected != 0 && starts.len < protected {
Vec.drop(&starts); ByteBuffer.drop(&fresh);
ret .Err(make(.InvalidArgument));
}
Vec.drop(&starts);
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
fn expect_ok(result: Status) {
match result { .Ok => {}, .Err(error) => { @assert(false, "unexpected path error"); }, }
}
fn expect_invalid(result: Status) {
match result {
.Ok => { @assert(false, "expected invalid path"); },
.Err(error) => {
match error.kind { .InvalidArgument => {}, _ => { @assert(false); }, }
},
}
}
fn view(text: []u8): ByteView { ret ByteView.from_slice(text); }
fn expect_view(got: ByteView, expected: []u8) {
@assert(ByteView.eq(got, view(expected)), "path view");
}
fn expect_out(out: *ByteBuffer, expected: []u8) {
var actual = ByteBuffer.as_slice(out.*);
@assert(actual.len == expected.len, "path output length");
var i: usize = 0;
while i < expected.len {
@assert(actual.ptr[i] == expected.ptr[i], "path output byte");
i = i + 1;
}
}
fn test_style_and_parts() {
@assert(is_absolute(view("/a"), .Posix));
@assert(!is_absolute(view("\\a"), .Posix));
@assert(is_absolute(view("C:\\a"), .Windows));
@assert(!is_absolute(view("C:a"), .Windows));
@assert(is_absolute(view("\\\\server\\share\\a"), .Windows));
@assert(!is_absolute(view("C:/a"), .Posix));
expect_view(dirname(view("/a/b"), .Posix), "/a");
expect_view(dirname(view("/a"), .Posix), "/");
expect_view(basename(view("/a/b"), .Posix), "b");
expect_view(extension(view("a/b.spl"), .Posix), "spl");
expect_view(extension(view("a/.hidden"), .Posix), "");
expect_view(dirname(view("C:\\a"), .Windows), "C:\\");
expect_view(basename(view("C:\\a"), .Windows), "a");
expect_view(dirname(view("C:relative"), .Windows), "C:");
expect_view(basename(view("C:relative"), .Windows), "relative");
expect_view(dirname(view("\\\\server\\share\\file"), .Windows),
"\\\\server\\share");
}
fn test_join() {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
expect_ok(join_into(&out, view("stage2/utils"), view("vec.spl"), .Posix));
expect_out(&out, "stage2/utils/vec.spl");
expect_ok(join_into(&out, view("C:\\base"), view("child\\x"), .Windows));
expect_out(&out, "C:\\base\\child\\x");
expect_ok(join_into(&out, view("C:"), view("child"), .Windows));
expect_out(&out, "C:child");
expect_ok(join_into(&out, view("ignored"), view("D:/abs"), .Windows));
expect_out(&out, "D:/abs");
expect_invalid(join_into(&out, view("C:\\base"), view("D:relative"), .Windows));
expect_out(&out, "D:/abs");
}
fn test_normalize_posix() {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
expect_ok(normalize_into(&out, view("a/b/../c"), .Posix));
expect_out(&out, "a/c");
expect_ok(normalize_into(&out, view("../../a/./b"), .Posix));
expect_out(&out, "../../a/b");
expect_ok(normalize_into(&out, view("/../../a//b"), .Posix));
expect_out(&out, "/a/b");
expect_ok(normalize_into(&out, view("a\\b/../c"), .Posix));
expect_out(&out, "c");
}
fn test_normalize_windows() {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
expect_ok(normalize_into(&out, view("C:/a\\b/../c"), .Windows));
expect_out(&out, "C:\\a\\c");
expect_ok(normalize_into(&out, view("C:foo\\..\\bar"), .Windows));
expect_out(&out, "C:bar");
expect_ok(normalize_into(&out, view("\\root\\..\\file"), .Windows));
expect_out(&out, "\\file");
expect_ok(normalize_into(&out,
view("\\\\server\\share\\a\\..\\file"), .Windows));
expect_out(&out, "\\\\server\\share\\file");
expect_ok(ByteBuffer.assign(&out, "preserved"));
expect_invalid(normalize_into(&out, view("\\\\server\\..\\file"), .Windows));
expect_out(&out, "preserved");
}
fn main() {
test_style_and_parts();
test_join();
test_normalize_posix();
test_normalize_windows();
}
+297
View File
@@ -0,0 +1,297 @@
// Expect retcode = 0
// 采样热点记录器:每个调用点聚合 count 与 self/total 的 steps/ns(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;
Vec.push(&self.stack, &token as *u8);
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;
Vec.pop(&self.stack, &popped as *u8);
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;
}
}
}
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 {
Vec.push(&idx, &i as *u8);
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 { v + 1; }
}
fn main() {
test_profile();
}
+234
View File
@@ -0,0 +1,234 @@
// Expect retcode = 0
// 开放寻址集合。键按字节复制到集合自己的存储中;集合只释放自身存储,
// 不释放键值内部引用的资源。哈希与相等回调的约定和 Map 一致。
type .{ .Status } = @import("error.spl");
type .{ .Map, .LookupResult, .hash_kind_usize, .hash_kind_view, .hash_kind_constant } =
@import("map.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .hash_usize, .eq_usize, .hash_view, .eq_view } = @import("hash.spl");
type Set = @self;
map: Map,
fn new(key_size: usize, key_align: usize, hash_kind: usize): Set {
ret Set {
.map = Map.new(key_size, key_align, 1, 1, hash_kind),
};
}
fn valid(self: Set): bool {
ret Map.valid(&self.map) && self.map.val_size == 1;
}
fn size(self: Set): usize {
ret self.map.size;
}
fn capacity(self: Set): usize {
ret self.map.states.cap;
}
fn reserve(self: *Set, need: usize): Status {
ret Map.reserve(&self.map, need);
}
// 重复插入保持集合大小不变。
fn insert(self: *Set, key: *u8): Status {
var marker: u8 = 1;
ret Map.put(&self.map, key, &marker);
}
fn find(self: *Set, key: *u8): LookupResult {
var marker: u8 = 0;
ret Map.get(&self.map, key, &marker);
}
fn remove(self: *Set, key: *u8): LookupResult {
ret Map.del(&self.map, key);
}
// 遍历 [0, slot_count);仅 slot_occupied 为真时 key_at 有效。
fn slot_count(self: Set): usize {
ret Map.slot_count(self.map);
}
fn slot_occupied(self: Set, index: usize): bool {
ret Map.slot_occupied(Map.slot_state(self.map, index));
}
fn key_at(self: *Set, index: usize): *u8 {
ret Map.key_at(&self.map, index);
}
fn clear(self: *Set) {
Map.clear(&self.map);
}
fn take(source: *Set): Set {
ret Set { .map = Map.take(&source.map) };
}
fn drop(self: *Set) {
Map.drop(&self.map);
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected Set error"); },
}
}
fn found(set: *Set, key: *u8): bool {
match find(set, key) {
.Found => { ret true; },
.NotFound => { ret false; },
}
ret false;
}
fn removed(set: *Set, key: *u8): bool {
match remove(set, key) {
.Found => { ret true; },
.NotFound => { ret false; },
}
ret false;
}
fn constant_hash(key: *u8): usize {
ret 3;
}
fn test_insert_find_remove() {
var set = new(@sizeof(usize), @alignof(usize), hash_kind_usize());
defer { drop(&set); }
@assert(valid(set));
@assert(size(set) == 0 && capacity(set) == 0);
var i: usize = 0;
while i < 1000 {
expect_ok(insert(&set, &i as *u8));
i = i + 1;
}
@assert(size(set) == 1000, "all unique keys inserted");
i = 0;
while i < 1000 {
@assert(found(&set, &i as *u8), "key survives growth");
i = i + 1;
}
// 重复键不增加 size。
i = 0;
while i < 1000 {
expect_ok(insert(&set, &i as *u8));
i = i + 1;
}
@assert(size(set) == 1000, "duplicate insertion is idempotent");
i = 0;
while i < 1000 {
if i % 2 == 0 { @assert(removed(&set, &i as *u8)); }
i = i + 1;
}
@assert(size(set) == 500);
i = 0;
while i < 1000 {
@assert(found(&set, &i as *u8) == (i % 2 != 0), "removed membership");
i = i + 1;
}
i = 0;
while i < 1000 {
if i % 2 == 0 { expect_ok(insert(&set, &i as *u8)); }
i = i + 1;
}
@assert(size(set) == 1000, "removed keys can be reinserted");
var occupied: usize = 0;
i = 0;
while i < slot_count(set) {
if slot_occupied(set, i) {
var key = (key_at(&set, i) as *usize).*;
@assert(key < 1000, "iteration yields stored key");
occupied = occupied + 1;
}
i = i + 1;
}
@assert(occupied == size(set), "iteration visits each member once");
var missing: usize = 2000;
@assert(!removed(&set, &missing as *u8));
clear(&set);
@assert(size(set) == 0 && capacity(set) != 0, "clear retains allocation");
}
fn test_collisions_and_take() {
var set = new(@sizeof(usize), @alignof(usize), hash_kind_constant());
defer { drop(&set); }
var i: usize = 0;
while i < 64 {
expect_ok(insert(&set, &i as *u8));
i = i + 1;
}
i = 0;
while i < 64 {
@assert(found(&set, &i as *u8), "collision chain lookup");
i = i + 1;
}
// 删除整条碰撞链后重新插入,验证墓碑会被复用且探测会终止。
i = 0;
while i < 64 {
@assert(removed(&set, &i as *u8));
i = i + 1;
}
@assert(size(set) == 0);
i = 64;
while i < 128 {
expect_ok(insert(&set, &i as *u8));
i = i + 1;
}
i = 64;
while i < 128 {
@assert(found(&set, &i as *u8), "tombstone collision chain reuse");
i = i + 1;
}
var moved = take(&set);
defer { drop(&moved); }
@assert(size(set) == 0 && capacity(set) == 0, "take clears source");
@assert(size(moved) == 64, "take preserves members");
i = 64;
while i < 128 {
@assert(found(&moved, &i as *u8));
i = i + 1;
}
}
fn test_byte_view_keys() {
var set = new(@sizeof(ByteView), @alignof(ByteView), hash_kind_view());
defer { drop(&set); }
var alpha = ByteView.from_slice("alpha");
var alpha_copy = ByteView.from_slice("alpha");
var beta = ByteView.from_slice("beta");
expect_ok(insert(&set, &alpha as *u8));
expect_ok(insert(&set, &alpha_copy as *u8));
expect_ok(insert(&set, &beta as *u8));
@assert(size(set) == 2, "ByteView keys compare by content");
@assert(found(&set, &alpha_copy as *u8));
@assert(removed(&set, &alpha_copy as *u8));
@assert(!found(&set, &alpha as *u8));
}
fn main() {
test_insert_find_remove();
test_collisions_and_take();
test_byte_view_keys();
}
+259
View File
@@ -0,0 +1,259 @@
// Expect retcode = 0
// 源文件与位置映射:fname + text 借用调用方存储;行起始表由 Source 拥有。
// 提供字节偏移 <-> (行,列)(行列均为 1-based、按字节计),以及取整行文本。
// Sources 是按 fname 索引的多文件集合。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .ByteView, .FindResult } = @import("byte_view.spl");
type .{ .Vec } = @import("vec.spl");
type .{ .Map, .hash_kind_view } = @import("map.spl");
type .{ .hash_view, .eq_view } = @import("hash.spl");
type Position = struct {
line: usize, // 1-based
col: usize, // 1-based,按字节
}
type Source = @self;
fname: ByteView, // 借用
text: ByteView, // 借用
line_starts: Vec, // usize:每行起始字节偏移(含首行 0)
type SourceResult = enum {
Ok: Source,
Err: Error,
}
type AddResult = enum {
Ok: usize,
Err: Error,
}
fn blank(): Source {
ret Source {
.fname = ByteView.empty(),
.text = ByteView.empty(),
.line_starts = Vec.blank(@sizeof(usize), 8),
};
}
fn new(fname: ByteView, text: ByteView): SourceResult {
var starts = Vec.blank(@sizeof(usize), 8);
var zero: usize = 0;
match Vec.push(&starts, &zero as *u8) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
var i: usize = 0;
while i < text.len {
if text.ptr[i] == '\n' {
var next = i + 1;
match Vec.push(&starts, &next as *u8) {
.Ok => {},
.Err(error) => { Vec.drop(&starts); ret .Err(error); },
}
}
i = i + 1;
}
ret .Ok(Source { .fname = fname, .text = text, .line_starts = starts });
}
fn line_count(self: Source): usize {
ret self.line_starts.len;
}
fn line_start_offset(self: Source, line: usize): usize {
if line == 0 || line > self.line_starts.len { ret ~(0 as usize); }
match Vec.get(&self.line_starts, line - 1) {
.Ok(ptr) => { ret (ptr as *usize).*; },
.Err(error) => { ret ~(0 as usize); },
}
ret ~(0 as usize);
}
// offset 所在行(0-based);超出范围夹到末行。
fn line_index_at(self: Source, offset: usize): usize {
var off = offset;
if off > self.text.len { off = self.text.len; }
var lo: usize = 0;
var hi: usize = self.line_starts.len;
while lo + 1 < hi {
var mid = (lo + hi) / 2;
var start = line_start_offset(self, mid + 1); // 1-based -> 内部
if start != ~(0 as usize) && start <= off { lo = mid; } else { hi = mid; }
}
ret lo;
}
fn line_col(self: Source, offset: usize): Position {
var off = offset;
if off > self.text.len { off = self.text.len; }
var line0 = line_index_at(self, off);
var start = line_start_offset(self, line0 + 1); // 1-based
if start == ~(0 as usize) { start = 0; }
ret Position { .line = line0 + 1, .col = off - start + 1 };
}
// line 为 1-based;返回整行(去掉行尾 '\n' 与 '\r')。
fn line_text(self: Source, line: usize): ByteView {
var n = self.line_starts.len;
if line == 0 || line > n { ret ByteView.empty(); }
var start = line_start_offset(self, line);
if start == ~(0 as usize) { ret ByteView.empty(); }
var end = self.text.len;
if line < n {
var next = line_start_offset(self, line + 1);
if next != ~(0 as usize) { end = next; }
}
while end > start && (self.text.ptr[end - 1] == '\n' || self.text.ptr[end - 1] == '\r') {
end = end - 1;
}
var r = ByteView.slice(self.text, start, end);
match r {
.Ok(v) => { ret v; },
.Err(error) => { ret ByteView.empty(); },
}
ret ByteView.empty();
}
fn drop(self: *Source) {
Vec.drop(&self.line_starts);
}
type Sources = struct {
items: Vec, // Source
by_name: Map, // ByteView(fname) -> usize
}
fn set_new(): Sources {
ret Sources {
.items = Vec.blank(@sizeof(Source), 8),
.by_name = Map.blank(@sizeof(ByteView), @alignof(ByteView), @sizeof(usize),
@alignof(usize), hash_kind_view()),
};
}
fn set_add(set: *Sources, fname: ByteView, text: ByteView): AddResult {
var src = new(fname, text);
var s = blank();
match src {
.Ok(v) => { s = v; },
.Err(error) => { ret .Err(error); },
}
var index = set.items.len;
match Vec.push(&set.items, &s as *u8) {
.Ok => {},
.Err(error) => { drop(&s); ret .Err(error); },
}
match Map.put(&set.by_name, &fname as *u8, &index as *u8) {
.Ok => {},
.Err(error) => {
// 回滚最后一项
set.items.len = index;
drop(&s);
ret .Err(error);
},
}
ret .Ok(index);
}
fn set_at(set: *Sources, index: usize): *Source {
match Vec.get(&set.items, index) {
.Ok(ptr) => { ret ptr as *Source; },
.Err(error) => { ret null; },
}
ret null;
}
fn set_find(set: *Sources, fname: ByteView): FindResult {
var out: usize = 0;
match Map.get(&set.by_name, &fname as *u8, &out as *u8) {
.Found => { ret .Found(out); },
.NotFound => {},
}
ret .NotFound;
}
fn set_drop(set: *Sources) {
var i: usize = 0;
while i < set.items.len {
var s = set_at(set, i);
if s != null { drop(s); }
i = i + 1;
}
Vec.drop(&set.items);
Map.drop(&set.by_name);
}
fn view(text: []u8): ByteView {
ret ByteView.from_slice(text);
}
fn test_single() {
var text = "line1\nline2\n\nline4";
var r = new(view("a.spl"), view(text));
var src = blank();
match r {
.Ok(v) => { src = v; },
.Err(error) => { @assert(false, "source new"); },
}
@assert(line_count(src) == 4, "line count");
var p0 = line_col(src, 0);
@assert(p0.line == 1 && p0.col == 1, "start");
var p6 = line_col(src, 6);
@assert(p6.line == 2 && p6.col == 1, "line2 start");
var p8 = line_col(src, 8);
@assert(p8.line == 2 && p8.col == 3, "line2 col3");
var p12 = line_col(src, 12);
@assert(p12.line == 3 && p12.col == 1, "empty line3");
var p13 = line_col(src, 13);
@assert(p13.line == 4 && p13.col == 1, "line4 start");
var p18 = line_col(src, 18);
@assert(p18.line == 4 && p18.col == 6, "past end clamps");
var l1 = line_text(src, 1);
var l3 = line_text(src, 3);
var l4 = line_text(src, 4);
@assert(ByteView.eq(l1, view("line1")), "line1");
@assert(ByteView.eq(l3, view("")), "line3 empty");
@assert(ByteView.eq(l4, view("line4")), "line4");
drop(&src);
}
fn test_sources() {
var set = set_new();
var r1 = set_add(&set, view("a.spl"), view("x\ny"));
var r2 = set_add(&set, view("b.spl"), view("z"));
var a: usize = 0;
var b: usize = 0;
match r1 { .Ok(v) => { a = v; }, .Err(e) => { @assert(false, "add a"); } }
match r2 { .Ok(v) => { b = v; }, .Err(e) => { @assert(false, "add b"); } }
@assert(a == 0 && b == 1, "indices");
match set_find(&set, view("b.spl")) {
.Found(i) => { @assert(i == 1, "find b"); },
.NotFound => { @assert(false, "b missing"); },
}
match set_find(&set, view("c.spl")) {
.Found(i) => { @assert(false, "c unexpected"); },
.NotFound => {},
}
var sb = set_at(&set, b);
@assert(sb != null && line_count(sb.*) == 1, "at b");
set_drop(&set);
}
fn main() {
test_single();
test_sources();
}
+235
View File
@@ -0,0 +1,235 @@
// Expect retcode = 0
// 拥有型字节字符串:在 ByteBuffer 之上提供字符串常用操作。
// 普通赋值复制句柄(共享存储),所有权转移用 take;失败不产生部分状态。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .ByteView, .FindResult } = @import("byte_view.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type Str = @self;
storage: ByteBuffer,
type StrResult = enum {
Ok: Str,
Err: Error,
}
fn new(): Str {
ret Str { .storage = ByteBuffer.new() };
}
// 复制 view 指向的字节。
fn from_view(view: ByteView): StrResult {
var s = new();
match append_view(&s, view) {
.Ok => {},
.Err(error) => { drop(&s); ret .Err(error); },
}
ret .Ok(s);
}
fn from_slice(bytes: []u8): StrResult {
ret from_view(ByteView.from_slice(bytes));
}
fn size(self: Str): usize {
ret ByteBuffer.size(self.storage);
}
fn is_empty(self: Str): bool {
ret ByteBuffer.is_empty(self.storage);
}
fn as_view(self: *Str): ByteView {
ret ByteView.from_slice(ByteBuffer.as_slice(self.storage));
}
fn as_slice(self: *Str): []u8 {
ret ByteBuffer.as_slice(self.storage);
}
fn reserve(self: *Str, capacity: usize): Status {
ret ByteBuffer.reserve(&self.storage, capacity);
}
fn append_view(self: *Str, view: ByteView): Status {
ret ByteBuffer.append(&self.storage, ByteView.as_slice(view));
}
fn append(self: *Str, bytes: []u8): Status {
ret ByteBuffer.append(&self.storage, bytes);
}
fn append_str(self: *Str, other: Str): Status {
var o = other;
ret append_view(self, as_view(&o));
}
fn append_byte(self: *Str, byte: u8): Status {
ret ByteBuffer.append_byte(&self.storage, byte);
}
fn clear(self: *Str) {
ByteBuffer.clear(&self.storage);
}
fn eq(a: Str, b: Str): bool {
var x = a;
var y = b;
ret ByteView.eq(as_view(&x), as_view(&y));
}
fn eq_slice(a: Str, bytes: []u8): bool {
var x = a;
ret ByteView.eq(as_view(&x), ByteView.from_slice(bytes));
}
fn cmp(a: Str, b: Str): i32 {
var x = a;
var y = b;
ret ByteView.cmp(as_view(&x), as_view(&y));
}
fn starts_with(self: Str, prefix: Str): bool {
var x = self;
var p = prefix;
ret ByteView.starts_with(as_view(&x), as_view(&p));
}
fn ends_with(self: Str, suffix: Str): bool {
var x = self;
var s = suffix;
ret ByteView.ends_with(as_view(&x), as_view(&s));
}
fn find(self: Str, needle: Str): FindResult {
var x = self;
var n = needle;
ret ByteView.find(as_view(&x), as_view(&n));
}
// 深复制;允许 out 与 source 为同一对象。
fn clone(out: *Str, source: Str): Status {
ret ByteBuffer.clone(&out.storage, source.storage);
}
fn take(source: *Str): Str {
ret Str { .storage = ByteBuffer.take(&source.storage) };
}
fn drop(self: *Str) {
ByteBuffer.drop(&self.storage);
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected str error"); },
}
}
fn expect_text(s: *Str, expected: []u8) {
var view = as_view(s);
@assert(ByteView.eq(view, ByteView.from_slice(expected)), "str text");
}
fn test_construction() {
var s = new();
defer { drop(&s); }
@assert(is_empty(s) && size(s) == 0, "empty new");
expect_ok(append(&s, "hello"));
expect_ok(append_byte(&s, ' '));
expect_ok(append(&s, "world"));
expect_text(&s, "hello world");
@assert(size(s) == 11, "size");
var r = from_slice("copy");
match r {
.Ok(copy) => {
expect_text(&copy, "copy");
@assert(eq(copy, copy), "self eq");
drop(&copy);
},
.Err(error) => { @assert(false, "from_slice"); },
}
}
fn test_compare_and_search() {
var hello_r = from_slice("hello");
var hello: Str = new();
defer { drop(&hello); }
match hello_r {
.Ok(v) => { hello = v; },
.Err(error) => { @assert(false, "hello"); },
}
var help_r = from_slice("help");
var help: Str = new();
defer { drop(&help); }
match help_r {
.Ok(v) => { help = v; },
.Err(error) => { @assert(false, "help"); },
}
@assert(eq(hello, hello), "eq");
@assert(!eq(hello, help), "ne");
@assert(eq_slice(hello, "hello"), "eq_slice");
@assert(cmp(hello, help) < 0, "cmp lt");
@assert(cmp(help, hello) > 0, "cmp gt");
@assert(starts_with(hello, hello), "starts_with");
@assert(ends_with(hello, hello), "ends_with");
var pre_r = from_slice("he");
var pre: Str = new();
defer { drop(&pre); }
match pre_r { .Ok(v) => { pre = v; }, .Err(e) => { @assert(false, "pre"); } }
@assert(starts_with(hello, pre), "starts he");
@assert(!ends_with(hello, pre), "!ends he");
var needle_r = from_slice("ll");
var needle: Str = new();
defer { drop(&needle); }
match needle_r { .Ok(v) => { needle = v; }, .Err(e) => { @assert(false, "needle"); } }
match find(hello, needle) {
.Found(index) => { @assert(index == 2, "find ll"); },
.NotFound => { @assert(false, "ll not found"); },
}
match find(hello, pre) {
.Found(index) => { @assert(index == 0, "find he"); },
.NotFound => { @assert(false, "he not found"); },
}
}
fn test_clone_take_and_failures() {
var source = new();
defer { drop(&source); }
expect_ok(append(&source, "owned"));
var copy = new();
defer { drop(&copy); }
expect_ok(clone(&copy, source));
expect_text(&copy, "owned");
expect_ok(append_byte(&source, '!')); // '!'
expect_text(&copy, "owned"); // 独立
var before = ByteBuffer.as_slice(copy.storage).ptr;
expect_ok(clone(&copy, copy));
@assert(ByteBuffer.as_slice(copy.storage).ptr != before, "self clone replaces allocation");
expect_text(&copy, "owned");
var moved = take(&copy);
defer { drop(&moved); }
@assert(ByteBuffer.size(copy.storage) == 0 && ByteBuffer.capacity(copy.storage) == 0,
"take empties source");
expect_text(&moved, "owned");
clear(&moved);
@assert(is_empty(moved) && ByteBuffer.capacity(moved.storage) != 0, "clear keeps capacity");
}
fn main() {
test_construction();
test_compare_and_search();
test_clone_take_and_failures();
}
Loaded 100 of 173 files, more files were not shown because too many files have changed in this diff. Show more