stage2 提供近乎完整的功能但出现严重性能问题 添加stage0的原生性能记录 stage1添加更多内置函数 nob重构代码使其更加健壮

This commit is contained in:
zzy committed 2026-09-19 18:08:43 +08:00
1 parent b5a7a811b3
commit e02b0c633a
35 files changed
+14018 -216

No files matched your search

+578 -12
View File
@@ -1,8 +1,8 @@
// Expect retcode = 0
// splc1 (stage2 自举编译器入口)。
// 当前仅实现 `--dump lex <file> [out]`:读取源文件,词法分析,按与 stage1
// splc0 完全一致的格式打印 token 列表,供 diff 测试逐字节比对。
// 当前实现 `--dump <lex|ast|sema|ir|vm> <file> [out]`:读取源文件,按与 stage1
// splc0 完全一致的格式打印各阶段产物,供 diff 测试逐字节比对。
type .{ .ByteView } = @import("./utils/byte_view.spl");
type .{ .ByteBuffer } = @import("./utils/byte_buffer.spl");
@@ -11,11 +11,21 @@ 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 } = @import("./utils/io.spl");
type .{ .set_new, .set_add, .set_drop } = @import("./utils/source.spl");
type Diag = @import("./diag.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 .{ .alloc, .free } = @import("./sys/mem.spl");
fn token_at(tokens: *Vec, index: usize): Token {
match Vec.get(tokens, index) {
@@ -209,31 +219,587 @@ fn dump_ast(log: *Logger, path: ByteView): i32 {
ret 0;
}
// ---- @import 模块加载器(driver 侧)----
type ModEntry = struct {
path: ByteView,
ast: *Ast,
ok: bool,
}
type LoaderCtx = struct {
sources: *Sources,
mods: Vec, // ModEntry
buffers: Vec, // ByteBuffer(路径/文本稳定存储)
diag: *Diag,
}
fn ignore_bool(b: bool) {}
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 {
ignore_bool(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] == '.' {
ignore_bool(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] == '\\') {
ignore_bool(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] != '/' {
ignore_bool(append_byte(out, '/'));
}
ignore_bool(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 {
ignore_bool(append_bytes(&out, sub(from, 0, (last as usize) + 1)));
}
ignore_bool(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); }
match Diag.render_human(&diag, &rendered) {
.Ok => {},
.Err(error) => {},
}
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); }
match Diag.render_human(&diag, &rendered) {
.Ok => {},
.Err(error) => {},
}
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, // 只逐步打日志(诊断“卡在哪一步”)
}
fn step(mode: PipeMode, s: []u8) {
if @enum2int(mode) == @enum2int(PipeMode.State) {
match eprint(s) { .Ok => {}, .Err(error) => {} }
}
}
// 完整前端流水线: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); }
match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} }
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); }
match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} }
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;
match Vec.push(&asts, &ap as *u8) { .Ok => {}, .Err(error) => {} }
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;
match Vec.push(&asts, &mp as *u8) { .Ok => {}, .Err(error) => {} }
}
mi = mi + 1;
}
Ast2Ir.run(&a2ir, asts);
if Diag.has_error(diag) {
var rendered = ByteBuffer.new();
defer { ByteBuffer.drop(&rendered); }
match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} }
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");
var ir2vm: Ir2Vm = undefined;
Ir2VmM.init(&ir2vm, &builder.ir, &sema.types.ir, &layout, &sema.attr);
defer { Ir2VmM.drop(&ir2vm); }
ignore_i32(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 ignore_i32(v: i32) {}
fn main(argc: i32, argv: **u8): i32 {
// 无参数:自测运行(`nob test stage2`)视为成功。
if argc <= 1 { ret 0; }
var log = Logger.new(ByteView.from_slice("splc1"));
if argc < 4 {
Logger.fatal(&log, "usage: splc1 --dump <lex|ast> <file> [out]");
ret 1;
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);
}
var arg1 = ByteView.from_cstr(argv[1]);
if argc < 4 {
Logger.fatal(&log, "usage: splc1 --dump <lex|ast|sema|ir|vm> <file> [out]");
ret 1;
}
var mode = ByteView.from_cstr(argv[2]);
var path = ByteView.from_cstr(argv[3]);
if !ByteView.eq(arg1, ByteView.from_slice("--dump")) {
Logger.fatal(&log, "usage: splc1 --dump <lex|ast> <file> [out]");
ret 1;
}
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());
}
Logger.fatal(&log, "dump mode not implemented");
ret 1;
}