From e02b0c633a7dcedf160c4e922ebc937c52971dd9 Mon Sep 17 00:00:00 2001 From: zzy <2450266535@qq.com> Date: Sat, 19 Sep 2026 18:08:43 +0800 Subject: [PATCH] =?UTF-8?q?stage2=20=E6=8F=90=E4=BE=9B=E8=BF=91=E4=B9=8E?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E7=9A=84=E5=8A=9F=E8=83=BD=E4=BD=86=E5=87=BA?= =?UTF-8?q?=E7=8E=B0=E4=B8=A5=E9=87=8D=E6=80=A7=E8=83=BD=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0stage0=E7=9A=84=E5=8E=9F=E7=94=9F=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E8=AE=B0=E5=BD=95=20stage1=E6=B7=BB=E5=8A=A0=E6=9B=B4?= =?UTF-8?q?=E5=A4=9A=E5=86=85=E7=BD=AE=E5=87=BD=E6=95=B0=20nob=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=BB=A3=E7=A0=81=E4=BD=BF=E5=85=B6=E6=9B=B4=E5=8A=A0?= =?UTF-8?q?=E5=81=A5=E5=A3=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nob.c | 406 +++-- stage0/spl_cli.c | 32 + stage0/spl_mcode.c | 2 + stage0/spl_syscall.c | 38 + stage0/spl_vm.c | 141 ++ stage0/spl_vm.h | 27 +- stage1/spl_ast2ir.c | 72 + stage1/spl_intrin.c | 8 + stage1/spl_intrin.h | 5 + stage1/spl_ir.c | 5 +- stage1/spl_sema.c | 36 +- stage1/splc0.c | 35 +- stage2/ast.spl | 31 + stage2/ast2ir.spl | 2142 +++++++++++++++++++++++ stage2/attr.spl | 426 +++++ stage2/def.spl | 316 ++++ stage2/eval.spl | 139 ++ stage2/intrin.spl | 331 ++++ stage2/ir.spl | 605 +++++++ stage2/ir2vm.spl | 1368 +++++++++++++++ stage2/ir_builder.spl | 497 ++++++ stage2/lang_type.spl | 1192 +++++++++++++ stage2/layout.spl | 358 ++++ stage2/mcode.spl | 723 ++++++++ stage2/parser.spl | 46 +- stage2/scope.spl | 224 +++ stage2/sema.spl | 3088 ++++++++++++++++++++++++++++++++++ stage2/splc1.spl | 590 ++++++- stage2/sys/time.spl | 45 + stage2/sys/vm.spl | 13 + stage2/type_table.spl | 917 ++++++++++ stage2/utils/byte_buffer.spl | 6 + stage2/utils/memory.spl | 16 + stage2/utils/profile.spl | 303 ++++ stage2/utils/vec.spl | 51 +- 35 files changed, 14018 insertions(+), 216 deletions(-) create mode 100644 stage2/ast2ir.spl create mode 100644 stage2/attr.spl create mode 100644 stage2/def.spl create mode 100644 stage2/eval.spl create mode 100644 stage2/intrin.spl create mode 100644 stage2/ir.spl create mode 100644 stage2/ir2vm.spl create mode 100644 stage2/ir_builder.spl create mode 100644 stage2/lang_type.spl create mode 100644 stage2/layout.spl create mode 100644 stage2/mcode.spl create mode 100644 stage2/scope.spl create mode 100644 stage2/sema.spl create mode 100644 stage2/sys/time.spl create mode 100644 stage2/sys/vm.spl create mode 100644 stage2/type_table.spl create mode 100644 stage2/utils/profile.spl diff --git a/nob.c b/nob.c index 9352bab..f7cffef 100644 --- a/nob.c +++ b/nob.c @@ -1,5 +1,7 @@ -#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 @@ -20,7 +22,7 @@ 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 *vm_test_srcs[] = {"stage0/test_spl_vm.c", VM_SRCS}; static const char *splc0_srcs[] = { "stage1/splc0.c", "stage1/spl_diag.c", @@ -44,27 +46,6 @@ static const char *splc0_srcs[] = { }; /* 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; -} - static const char *obj_path(const char *src) { // "stage0/spl_vm.c" -> "build/stage0_spl_vm.o" size_t prefix_len = strlen(BUILD_FOLDER); @@ -89,6 +70,10 @@ static const char *exe_path(const char *name) { #endif } +static const char *sir_path(const char *name) { + return nob_temp_sprintf(BUILD_FOLDER "%s.sir", name); +} + static bool sync_cflags_stamp(void) { Nob_String_View cflags = nob_sv_from_cstr(CFLAGS_STR); Nob_String_Builder old = {0}; @@ -106,138 +91,234 @@ static bool sync_cflags_stamp(void) { return true; } -// 头文件依赖:收集 stage0/stage1 下的所有 .h,作为每个 object 的输入。 -// nob 不解析 #include,这里用“任一头文件变化则重建全部 object”的保守策略, -// 避免头文件改动后各 .o 结构体布局/常量不一致导致的隐蔽错误。 -static Nob_Files g_headers = {0}; - -static bool collect_header_entry(Nob_Walk_Entry entry) { - if (entry.type != NOB_FILE_REGULAR) - return true; - const char *dot = strrchr(entry.path, '.'); - if (!dot || strcmp(dot, ".h") != 0) - return true; - if (strstr(entry.path, ".old") != NULL) - return true; - da_append(&g_headers, nob_temp_strdup(entry.path)); - return true; -} - -static bool collect_headers(void) { - if (g_headers.count > 0) - return true; - if (!nob_walk_dir("stage0", collect_header_entry)) - return false; - if (!nob_walk_dir("stage1", collect_header_entry)) - return false; - return true; -} - -// stage2 的所有 .spl:作为 splc1.sir 的依赖(不解析 @import,保守全量重建)。 -static Nob_Files g_spl2 = {0}; - -static bool collect_spl2_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; - da_append(&g_spl2, nob_temp_strdup(entry.path)); - return true; -} - -static bool collect_spl2(void) { - if (g_spl2.count > 0) - return true; - return nob_walk_dir("stage2", collect_spl2_entry); -} - static bool prepare_build(void) { if (!nob_mkdir_if_not_exists(BUILD_FOLDER)) return false; if (!sync_cflags_stamp()) return false; - if (!collect_headers()) - return false; - return collect_spl2(); + 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 build { + const char *name; + enum { + BUILD_EXE, + BUILD_EXE_WITH_OPTIMIZATION, + BUILD_SIR, + } 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; + } sir; + }; +} build_t; - // 依赖:源文件 + 所有头文件 + cflags 戳。 - size_t ninputs = g_headers.count + 2; - const char **inputs = (const char **)nob_temp_alloc(sizeof(const char *) * ninputs); - inputs[0] = src; - for (size_t i = 0; i < g_headers.count; ++i) { - inputs[i + 1] = g_headers.items[i]; - } - inputs[ninputs - 1] = CFLAGS_STAMP; +typedef struct { + const char **items; + size_t count; + size_t capacity; +} str_da_t; - int needs = nob_needs_rebuild(obj, inputs, ninputs); - if (needs < 0) - return false; - if (needs == 0) - return true; +typedef struct run { + const char *name; + const char *interpreter; +} run_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}; - 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); -} + 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,下一轮循环重新构建 + } -static bool link_exe(const target_t *target) { const char *exe = exe_path(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); + 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); - 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); +static build_t targets[] = { + { + "spl_cli", + BUILD_EXE_WITH_OPTIMIZATION, + .exe = {.srcs = spl_cli_srcs, + .srcs_count = NOB_ARRAY_LEN(spl_cli_srcs), + .headers = NULL, + .headers_count = 0}, + }, + { + "spl_cli_g", + BUILD_EXE, + .exe = {.srcs = spl_cli_srcs, + .srcs_count = NOB_ARRAY_LEN(spl_cli_srcs), + .headers = NULL, + .headers_count = 0}, + }, + { + "vm_test", + BUILD_EXE, + .exe = {.srcs = vm_test_srcs, + .srcs_count = NOB_ARRAY_LEN(vm_test_srcs), + .headers = NULL, + .headers_count = 0}, + }, + { + "splc0", + BUILD_EXE, + .exe = {.srcs = splc0_srcs, + .srcs_count = NOB_ARRAY_LEN(splc0_srcs), + .headers = NULL, + .headers_count = 0}, + }, + { + "splc1", + BUILD_SIR, + .sir = {.src = "stage2/splc1.spl", .compiler = "splc0"}, + }, + { + "splc1s", + BUILD_SIR, + .sir = {.src = "stage2/splc1.spl", .compiler = "splc1"}, + }, + { + "splc1r", + BUILD_SIR, + .sir = {.src = "stage2/splc1.spl", .compiler = "splc1s"}, + }, +}; + +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; + } + default: + NOB_UNREACHABLE("build_t.kind"); + } + } + nob_log(NOB_ERROR, "unknown build target `%s`", name); + return NULL; +} + +static run_t runs[] = { + {"spl_cli", NULL}, {"vm_test", NULL}, {"splc0", NULL}, + {"splc1", "spl_cli"}, {"splc1r", "spl_cli"}, {"splc1s", "spl_cli"}, +}; + +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) { @@ -341,30 +422,15 @@ static bool test_once_with_dump(const char *fname, void *user_data) { return nob_cmd_run(&cmd); } -// 用 splc0 编译 stage2/splc1.spl -> build/splc1.sir(依赖 stage2 全部 .spl)。 -static bool build_splc1(void) { - if (!build_target("splc0")) - return false; - - const char *src = "stage2/splc1.spl"; - const char *sir = BUILD_FOLDER "splc1.sir"; - - size_t ninputs = g_spl2.count + 2; - const char **inputs = (const char **)nob_temp_alloc(sizeof(const char *) * ninputs); - inputs[0] = src; - for (size_t i = 0; i < g_spl2.count; ++i) - inputs[i + 1] = g_spl2.items[i]; - inputs[ninputs - 1] = CFLAGS_STAMP; - - int needs = nob_needs_rebuild(sir, inputs, ninputs); - if (needs < 0) - return false; - if (needs == 0) - return true; - - Nob_Cmd cmd = {0}; - nob_cmd_append(&cmd, exe_path("splc0"), src, sir); - return nob_cmd_run(&cmd); +// 由源路径派生构建产物路径:build/.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); } static bool run_capture(Nob_Cmd *cmd, Nob_Cmd_Result *result) { @@ -388,7 +454,7 @@ static bool test_once_diff(const char *fname, void *user_data) { return false; if (!build_target("spl_cli")) return false; - if (!build_splc1()) + if (!build_target("splc1")) return false; Nob_Cmd c_cmd = {0}; @@ -519,20 +585,16 @@ 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 [args] build and run a target"); - nob_log(NOB_INFO, " test build and run VM unit tests"); + nob_log(NOB_INFO, " test 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"); set_log_handler(nob_cancer_log_handler); @@ -553,7 +615,7 @@ int main(int argc, char **argv) { return 1; } } else { - if (!build_all()) + if (!build_target(NULL)) return 1; } return 0; @@ -565,16 +627,11 @@ 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, exe_path(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) { @@ -629,11 +686,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; } diff --git a/stage0/spl_cli.c b/stage0/spl_cli.c index 9d69cf4..1a04cdf 100644 --- a/stage0/spl_cli.c +++ b/stage0/spl_cli.c @@ -30,6 +30,9 @@ typedef struct { int debug_cli; int trace; int disasm; + int profile; + int profile_interval; + const char *profile_out; const char *entry; const char *path; /* file.sir */ const char **args; /* spl 程序参数(file.sir 之后) */ @@ -43,6 +46,8 @@ static void usage(void) { " --disasm disasm dump sir\n" " --entry entry function (default: main)\n" " --trace print every instruction\n" + " --profile[=N] native sampling profiler (every N insns, default 10000)\n" + " --profile-out

profile output prefix (default build/profile)\n" " -h, --help show help\n"); } @@ -67,6 +72,17 @@ 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, "--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"); @@ -651,6 +667,11 @@ int main(int argc, const char **argv) { 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)); @@ -689,6 +710,17 @@ int main(int argc, const char **argv) { fprintf(stderr, "spl_cli: hit breakpoint (run with -g to debug)\n"); } + 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); diff --git a/stage0/spl_mcode.c b/stage0/spl_mcode.c index a4afbfd..1b2378a 100644 --- a/stage0/spl_mcode.c +++ b/stage0/spl_mcode.c @@ -283,6 +283,8 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) { return 0; } +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) { unsigned char *buf, *p; spl_vm_val_t i; diff --git a/stage0/spl_syscall.c b/stage0/spl_syscall.c index 2b26fee..ef6d0a3 100644 --- a/stage0/spl_syscall.c +++ b/stage0/spl_syscall.c @@ -16,6 +16,7 @@ #include #include #include +#include /* ================================================================= * Macro templates @@ -322,6 +323,41 @@ static spl_vm_val_t vm_printf(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { return (spl_vm_val_t)nargs; } +/* 探测接口(syscall 风格,非必选,逐步扩展)。 + * 不假设一定可用:不支持时统一返回 0。 + * case 0 -> 是否存在 time:1 = 存在(则 case 1/2 有效),0 = 不存在 + * case 1 -> 当前秒(time_t) + * case 2 -> 当前纳秒部分(tv_nsec) + * case 3 -> VM 已执行指令数(参考量;解码/执行恒定时可换算时间) + * case 4 -> 完整纳秒时间戳(单次调用,避免 sec/ns 跨秒竞态; + * 纪元纳秒约 1.8e18 < u64 上限,usize 足够,无需 i128) + * 新增探测点先加 case,再在 SPL 侧逐步验证。 */ +static spl_vm_val_t vm_timestamp(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { + CHECK_NARGS("vm_timestamp", 1); + spl_vm_val_t arg = args[0]; + struct timespec ts; + int has_time = (timespec_get(&ts, TIME_UTC) == TIME_UTC); + switch (arg) { + case 0: + return has_time ? 1 : 0; + case 1: + return has_time ? (spl_vm_val_t)ts.tv_sec : 0; + case 2: + return has_time ? (spl_vm_val_t)ts.tv_nsec : 0; + case 3: + if (!_vm) + return 0; + return (spl_vm_val_t)_vm->steps; + case 4: + return has_time ? (spl_vm_val_t)((unsigned long long)ts.tv_sec * 1000000000ull + + (unsigned long long)ts.tv_nsec) + : 0; + default: + fprintf(stderr, "vm_timestamp: invalid arg %zu\n", (size_t)arg); + return 0; + } +} + /* ================================================================= * VM syscalls (for comptime - compile-time code execution) * @@ -480,6 +516,8 @@ void spl_syscall_register(spl_prog_t *prog) { {"vm_memcpy", 0, vm_memcpy}, {"vm_memcmp", 0, vm_memcmp}, {"vm_printf", 0, vm_printf}, + + {"vm_timestamp", 0, vm_timestamp}, /* VM operations (comptime) */ {"vm_new", 0, vm_new}, {"vm_drop", 0, vm_drop}, diff --git a/stage0/spl_vm.c b/stage0/spl_vm.c index 847f59e..8f651ff 100644 --- a/stage0/spl_vm.c +++ b/stage0/spl_vm.c @@ -5,12 +5,15 @@ */ #include "spl_vm.h" +#include "include/core_map.h" #include "spl_mcode.h" #include #include #include +static void spl_vm_profile_free(spl_vm_t *vm); + #if defined(_WIN32) || defined(_WIN64) #define WIN32_LEAN_AND_MEAN #include @@ -566,6 +569,9 @@ void spl_vm_init_ex(spl_vm_t *vm, int stack_size, int call_depth) { vec_init(vm->fn_breakpoints); vm->sp = vm->fp = vm->ip = vm->cp = vm->gp = 0; + vm->steps = 0; + vm->profile = NULL; + vm->profile_interval = 0; vm->prog = NULL; vm->trace = 0; vm->debug = 1; @@ -585,6 +591,7 @@ void spl_vm_drop(spl_vm_t *vm) { free(vm->fn_breakpoints.data[i]); vec_free(vm->fn_breakpoints); vec_free(vm->breakpoints); + spl_vm_profile_free(vm); } // FIXME @@ -643,6 +650,7 @@ void spl_vm_skip_breakpoint(spl_vm_t *vm) { /* 前向声明(定义在文件尾部 dump 区域) */ static const char *func_name_by_ip(spl_prog_t *prog, spl_vm_val_t ip); +static void spl_vm_profile_free(spl_vm_t *vm); /* 函数名断点命中:addr 所在函数名是否在断点表 */ static int fn_breakpoint_hit(spl_vm_t *vm, spl_vm_val_t addr) { @@ -766,6 +774,136 @@ int spl_vm_prepare(spl_vm_t *vm, const char *entry, int argc, const char **argv, * Returns: 0 = still running, 1 = halted, 2 = breakpoint (SPL_DBG), -1 = error * ================================================================ */ +/* ================= native 采样 profiler ================= + * 每隔 interval 条指令采样一次当前调用栈(root;...;leaf),累积计数。 + * 输出折叠格式(folded)供火焰图工具使用,以及每函数 self 采样表。 */ + +static int func_index_by_ip(spl_prog_t *prog, spl_vm_val_t ip) { + if (!prog) + return -1; + isize lo = 0, hi = (isize)vec_size(prog->funcs) - 1; + while (lo <= hi) { + isize mid = (lo + hi) / 2; + spl_vm_func_t *f = &vec_at(prog->funcs, (usize)mid); + if (ip < f->address) + hi = mid - 1; + else if (ip >= f->address + f->ninsns) + lo = mid + 1; + else + return (int)mid; + } + for (usize i = 0; i < vec_size(prog->funcs); i++) { + spl_vm_func_t *f = &vec_at(prog->funcs, i); + if (ip >= f->address && ip < f->address + f->ninsns) + return (int)i; + } + return -1; +} + +static void spl_vm_sample(spl_vm_t *vm) { + spl_prof_t *p = (spl_prof_t *)vm->profile; + spl_prog_t *prog = vm->prog; + if (!p || !prog) + return; + + char buf[8192]; + usize len = 0; + for (isize i = 0; i < vm->cp; i++) { + intptr_t s = (intptr_t)vm->frames.data[i].saved_ip; + if (s <= 0) + continue; + int fi = func_index_by_ip(prog, (spl_vm_val_t)(s - 1)); + const char *n = fi >= 0 ? vec_at(prog->funcs, (usize)fi).name : "?"; + size_t l = strlen(n); + if (len + l + 2 < sizeof(buf)) { + if (len) + buf[len++] = ';'; + memcpy(buf + len, n, l); + len += l; + } + } + int leaf = func_index_by_ip(prog, vm->ip > 0 ? vm->ip - 1 : 0); + const char *ln = leaf >= 0 ? vec_at(prog->funcs, (usize)leaf).name : "?"; + size_t ll = strlen(ln); + if (len + ll + 1 < sizeof(buf)) { + if (len) + buf[len++] = ';'; + memcpy(buf + len, ln, ll); + len += ll; + } + buf[len] = 0; + + usize old = 0; + if (map_get(p->stacks, buf, &old)) { + map_put(p->stacks, buf, old + 1); + } else { + map_put(p->stacks, strdup(buf), (usize)1); + } + if (leaf >= 0 && (usize)leaf < vec_size(p->self_samples)) + p->self_samples.data[leaf]++; + p->samples++; +} + +void spl_vm_profile_enable(spl_vm_t *vm, usize interval) { + if (!vm) + return; + spl_prof_t *p = (spl_prof_t *)malloc(sizeof(*p)); + if (!p) + return; + memset(p, 0, sizeof(*p)); + map_init(p->stacks, MAP_HASH_STR, MAP_CMP_STR); + vec_init(p->self_samples); + if (vm->prog) { + usize n = vec_size(vm->prog->funcs); + for (usize i = 0; i < n; i++) + vec_push(p->self_samples, (usize)0); + } + vm->profile = p; + vm->profile_interval = interval ? interval : 100000; +} + +static void spl_vm_profile_free(spl_vm_t *vm) { + spl_prof_t *p = (spl_prof_t *)vm->profile; + if (!p) + return; + map_for(p->stacks, i) { free((void *)p->stacks.data[i].key); } + map_free(p->stacks); + vec_free(p->self_samples); + free(p); + vm->profile = NULL; +} + +void spl_vm_profile_dump_folded(spl_vm_t *vm, const char *path) { + spl_prof_t *p = vm ? (spl_prof_t *)vm->profile : NULL; + if (!p || !path) + return; + FILE *f = fopen(path, "wb"); + if (!f) + return; + map_for(p->stacks, i) { fprintf(f, "%s %zu\n", p->stacks.data[i].key, p->stacks.data[i].val); } + fclose(f); +} + +void spl_vm_profile_dump_table(spl_vm_t *vm, const char *path) { + spl_prof_t *p = vm ? (spl_prof_t *)vm->profile : NULL; + if (!p || !path || !vm->prog) + return; + FILE *f = fopen(path, "wb"); + if (!f) + return; + fprintf(f, "samples=%zu interval=%zu\n", p->samples, vm->profile_interval); + fprintf(f, "func\tself_samples\tpercent\n"); + for (usize i = 0; i < vec_size(vm->prog->funcs); i++) { + usize c = i < vec_size(p->self_samples) ? p->self_samples.data[i] : 0; + if (c == 0) + continue; + spl_vm_func_t *fn = &vec_at(vm->prog->funcs, i); + fprintf(f, "%s\t%zu\t%.2f\n", fn->name, c, + p->samples ? (100.0 * (double)c / (double)p->samples) : 0.0); + } + fclose(f); +} + int spl_vm_run_once(spl_vm_t *vm) { const spl_vm_ins_t *ins; spl_prog_t *prog; @@ -792,6 +930,9 @@ int spl_vm_run_once(spl_vm_t *vm) { ins = &vec_at(prog->insns, vm->ip); vm->ip++; + vm->steps++; + if (vm->profile && vm->profile_interval && (vm->steps % vm->profile_interval) == 0) + spl_vm_sample(vm); if (vm->trace) { fprintf(stderr, "vm: ip=%zd op=%s type=%s imm=%zu sp=%zd fp=%zd\n", vm->ip - 1, diff --git a/stage0/spl_vm.h b/stage0/spl_vm.h index b546c78..d07c2d6 100644 --- a/stage0/spl_vm.h +++ b/stage0/spl_vm.h @@ -3,6 +3,7 @@ #ifndef __SPL_VM_H__ #define __SPL_VM_H__ +#include "include/core_map.h" #include "include/core_vec.h" #include "spl_mcode.h" #include @@ -19,19 +20,28 @@ typedef struct { typedef VEC(spl_vm_val_t) spl_stack_vec_t; typedef VEC(spl_callframe_t) spl_frame_vec_t; +typedef struct { + MAP(const char *, usize) stacks; + VEC(usize) self_samples; + 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; // 已执行指令数(供性能分析参考) 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; @@ -73,6 +83,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); diff --git a/stage1/spl_ast2ir.c b/stage1/spl_ast2ir.c index 86455c0..9e6edc5 100644 --- a/stage1/spl_ast2ir.c +++ b/stage1/spl_ast2ir.c @@ -72,6 +72,9 @@ static spl_ir_node_ref_t ast2ir_intrin_call(spl_ast2ir_t *a, spl_ast_node_t *n, spl_ast_node_ref_vec_t *args); static spl_ir_node_ref_t ast2ir_intrin_field(spl_ast2ir_t *a, spl_ast_node_t *n, spl_intrin_t in); static spl_ir_node_ref_t ast2ir_intrin_len(spl_ast2ir_t *a, spl_ast_node_t *n); +static spl_ir_node_ref_t ast2ir_intrin_pos(spl_ast2ir_t *a, spl_ast_node_t *n, int which); +static spl_ir_node_ref_t ast2ir_intrin_file_name(spl_ast2ir_t *a, spl_ast_node_t *n); +static spl_ir_node_ref_t ast2ir_intrin_fn_name(spl_ast2ir_t *a, spl_ast_node_t *n); static spl_ir_node_ref_t emit_zero_value(spl_ast2ir_t *a, spl_type_id_t tid) { spl_type_node_t *tn = spl_type_node(&a->sema->type->ir, tid); @@ -461,6 +464,14 @@ static spl_ir_node_ref_t ast2ir_intrin_call(spl_ast2ir_t *a, spl_ast_node_t *n, case SPL_INTRIN_INT2PTR: case SPL_INTRIN_PTR_CAST: return ast2ir_intrin_cast(a, n, in, args); + case SPL_INTRIN_LINE: + return ast2ir_intrin_pos(a, n, 0); + case SPL_INTRIN_COL: + return ast2ir_intrin_pos(a, n, 1); + case SPL_INTRIN_FILE_NAME: + return ast2ir_intrin_file_name(a, n); + case SPL_INTRIN_FN_NAME: + return ast2ir_intrin_fn_name(a, n); case SPL_INTRIN_BREAKPOINT: return spl_ir_builder_dbg_breakpoint(a->ir); default: @@ -469,6 +480,67 @@ static spl_ir_node_ref_t ast2ir_intrin_call(spl_ast2ir_t *a, spl_ast_node_t *n, return 0; } +/* @line() / @col():把调用点位置降为 usize 常量。which: 0=line, 1=col。 */ +static spl_ir_node_ref_t ast2ir_intrin_pos(spl_ast2ir_t *a, spl_ast_node_t *n, int which) { + spl_lang_type_id_t usize_lang = spl_lang_make_int(a->sema->type, sizeof(void *) * 8, 0); + spl_type_id_t usize_ir = to_ir_type(a, usize_lang); + usize v = which == 0 ? (usize)n->dbg.line : (usize)n->dbg.col; + return spl_ir_builder_type_const_int(a->ir, usize_ir, v); +} + +/* @file_name():把调用点所在文件路径降为 []u8(同字符串字面量的构造方式)。 */ +static spl_ir_node_ref_t ast2ir_intrin_file_name(spl_ast2ir_t *a, spl_ast_node_t *n) { + const char *fname = n->dbg.fname ? n->dbg.fname : "?"; + usize len = strlen(fname); + + spl_lang_type_id_t u8_lang = spl_lang_make_int(a->sema->type, 8, 0); + spl_type_id_t u8_ir = to_ir_type(a, u8_lang); + spl_type_id_t ptr_u8_ir = spl_type_builder_ptr(&a->sema->type->ir, u8_ir); + spl_lang_type_id_t usize_lang = spl_lang_make_int(a->sema->type, sizeof(void *) * 8, 0); + spl_type_id_t usize_ir = to_ir_type(a, usize_lang); + spl_lang_type_id_t arr_lang = spl_lang_make_array(a->sema->type, u8_lang, len); + spl_type_id_t arr_ir = to_ir_type(a, arr_lang); + + spl_ir_node_ref_t raw = + spl_ir_builder_type_const_bytes(a->ir, arr_ir, (void *)fname, len); + spl_ir_node_ref_t ptr = spl_ir_builder_mem_global_alloc(a->ir, ptr_u8_ir, raw); + spl_ir_node_ref_t len_c = spl_ir_builder_type_const_int(a->ir, usize_ir, len); + + spl_type_id_t slice_ir = node_ir(a, n); + spl_ir_node_ref_t node = spl_ir_builder_agg_construct(a->ir, slice_ir); + spl_ir_builder_agg_construct_field(a->ir, node, ptr); + spl_ir_builder_agg_construct_field(a->ir, node, len_c); + return node; +} + +/* @fn_name():把当前函数名降为 []u8(同字符串字面量的构造方式)。 */ +static spl_ir_node_ref_t ast2ir_intrin_fn_name(spl_ast2ir_t *a, spl_ast_node_t *n) { + const char *name = "?"; + spl_ir_func_t *f = spl_ir_func(&a->ir->ir, a->ir->current_fn); + if (f && f->name) + name = f->name; + usize len = strlen(name); + + spl_lang_type_id_t u8_lang = spl_lang_make_int(a->sema->type, 8, 0); + spl_type_id_t u8_ir = to_ir_type(a, u8_lang); + spl_type_id_t ptr_u8_ir = spl_type_builder_ptr(&a->sema->type->ir, u8_ir); + spl_lang_type_id_t usize_lang = spl_lang_make_int(a->sema->type, sizeof(void *) * 8, 0); + spl_type_id_t usize_ir = to_ir_type(a, usize_lang); + spl_lang_type_id_t arr_lang = spl_lang_make_array(a->sema->type, u8_lang, len); + spl_type_id_t arr_ir = to_ir_type(a, arr_lang); + + spl_ir_node_ref_t raw = + spl_ir_builder_type_const_bytes(a->ir, arr_ir, (void *)name, len); + spl_ir_node_ref_t ptr = spl_ir_builder_mem_global_alloc(a->ir, ptr_u8_ir, raw); + spl_ir_node_ref_t len_c = spl_ir_builder_type_const_int(a->ir, usize_ir, len); + + spl_type_id_t slice_ir = node_ir(a, n); + spl_ir_node_ref_t node = spl_ir_builder_agg_construct(a->ir, slice_ir); + spl_ir_builder_agg_construct_field(a->ir, node, ptr); + spl_ir_builder_agg_construct_field(a->ir, node, len_c); + return node; +} + static spl_ir_node_ref_t ast2ir_intrin_field(spl_ast2ir_t *a, spl_ast_node_t *n, spl_intrin_t in) { switch (in) { case SPL_INTRIN_LEN: diff --git a/stage1/spl_intrin.c b/stage1/spl_intrin.c index d684af3..71bddf7 100644 --- a/stage1/spl_intrin.c +++ b/stage1/spl_intrin.c @@ -40,6 +40,14 @@ static const spl_intrin_info_t g_intrin_table[] = { 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]); diff --git a/stage1/spl_intrin.h b/stage1/spl_intrin.h index 0a784ea..a46bc9e 100644 --- a/stage1/spl_intrin.h +++ b/stage1/spl_intrin.h @@ -25,6 +25,11 @@ typedef enum { 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 { diff --git a/stage1/spl_ir.c b/stage1/spl_ir.c index 55e69c5..19dfe57 100644 --- a/stage1/spl_ir.c +++ b/stage1/spl_ir.c @@ -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 { diff --git a/stage1/spl_sema.c b/stage1/spl_sema.c index 536688b..d3e70b9 100644 --- a/stage1/spl_sema.c +++ b/stage1/spl_sema.c @@ -1020,6 +1020,16 @@ static spl_lang_type_id_t sema_intrin_call(spl_sema_t *sema, spl_ast_node_t *n, } return spl_lang_make_int(sema->type, sizeof(void *) * 8, 0); } + case SPL_INTRIN_FILE_NAME: + case SPL_INTRIN_FN_NAME: { + spl_lang_type_id_t u8 = spl_lang_make_int(sema->type, 8, 0); + spl_lang_type_id_t slice = spl_lang_make_slice(sema->type, u8); + n->resolved_lang_tid = slice; + return slice; + } + case SPL_INTRIN_LINE: + case SPL_INTRIN_COL: + return spl_lang_make_int(sema->type, sizeof(void *) * 8, 0); default: UNREACHABLE(); } @@ -2843,16 +2853,36 @@ void spl_sema_run(spl_sema_t *sema) { spl_sema_run_module(sema, root->root.members); } +typedef struct { + const char *key; + spl_symbol_t sym; +} spl_sema_sym_t; + +static int spl_sema_sym_cmp(const void *a, const void *b) { + return strcmp(((const spl_sema_sym_t *)a)->key, ((const spl_sema_sym_t *)b)->key); +} + void spl_sema_dump(spl_sema_t *sema) { printf("Scopes:\n"); for (usize i = 1; i < vec_size(sema->scope->scopes); ++i) { spl_scope_node_t *scope = &vec_at(sema->scope->scopes, i); printf("scope[%zu] parent=%zu\n", i, scope->parent); + + /* 按符号名排序输出:与哈希表内部布局解耦,保证确定顺序。 */ + usize nsyms = scope->symbols.size; + spl_sema_sym_t *arr = nsyms ? malloc(nsyms * sizeof(*arr)) : NULL; + usize k = 0; map_for(scope->symbols, j) { - spl_symbol_t *symbol = &unsafe_map_at(scope->symbols, j).val; - printf(" %s -> def`%s`#%zu\n", unsafe_map_at(scope->symbols, j).key, symbol->name, - symbol->node); + arr[k].key = unsafe_map_at(scope->symbols, j).key; + arr[k].sym = unsafe_map_at(scope->symbols, j).val; + k++; } + if (arr && nsyms > 1) + qsort(arr, nsyms, sizeof(*arr), spl_sema_sym_cmp); + for (k = 0; k < nsyms; ++k) { + printf(" %s -> def`%s`#%zu\n", arr[k].key, arr[k].sym.name, arr[k].sym.node); + } + free(arr); } printf("LangTypeTable:\n"); for (usize i = 1; i < vec_size(sema->type->lang_table); ++i) { diff --git a/stage1/splc0.c b/stage1/splc0.c index ecb3a19..faf343c 100644 --- a/stage1/splc0.c +++ b/stage1/splc0.c @@ -81,15 +81,18 @@ static void dump_tokens(spl_tok_vec_t toks) { vec_free(toks); } -/* 折叠重复分隔符并去掉 "." 路径分量,使同一模块的 import 解析结果唯一。 - 就地重写,'\' 统一成 '/';保留前导分隔符(绝对路径)。 */ +/* 折叠重复分隔符、解析 "." 与 ".." 路径分量,使同一模块的 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; - if (p[0] == '/' || p[0] == '\\') + int has_root = (p[0] == '/' || p[0] == '\\'); + if (has_root) *w++ = '/'; char *r = p; @@ -104,7 +107,31 @@ static void normalize_import_path(char *p) { usize len = (usize)(r - seg); if (len == 1 && seg[0] == '.') continue; - if (w != out && w[-1] != '/') + 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; diff --git a/stage2/ast.spl b/stage2/ast.spl index 59bca4e..fb635e8 100644 --- a/stage2/ast.spl +++ b/stage2/ast.spl @@ -209,6 +209,7 @@ type Ast = @self; nodes: Vec, // Node root: usize, sources: *Sources, // 借用;可为 null(dump 时不解析行列) +strings: Vec, // ByteBuffer:已解码的字符串字面量字节(ByteView 指向其中) type AstResult = enum { Ok: Ast, @@ -231,6 +232,7 @@ fn new(sources: *Sources): AstResult { .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) { @@ -257,6 +259,15 @@ fn drop(self: *Ast) { 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; } @@ -281,6 +292,26 @@ fn alloc(self: *Ast, body: Body, span: Span): usize { ret self.nodes.len - 1; } +// 注册一段已解码的字符串字节(Ast 拥有;ByteView 指向其中,生命周期同 Ast)。 +// 空字符串也保证非空 data 指针(镜像 stage1:空字面量仍有稳定的非空字节地址)。 +fn push_string(self: *Ast, bytes: ByteBuffer): ByteView { + var b = bytes; + if b.storage.len == 0 { + match ByteBuffer.reserve(&b, 1) { .Ok => {}, .Err(error) => {} } + } + @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; } diff --git a/stage2/ast2ir.spl b/stage2/ast2ir.spl new file mode 100644 index 0000000..6355338 --- /dev/null +++ b/stage2/ast2ir.spl @@ -0,0 +1,2142 @@ +// Expect retcode = 0 + +// AST -> SIR(移植自 stage1/spl_ast2ir.c)。两遍:先 reserve 所有函数(拿稳定 +// func ref),再发射函数体。def2ir_map 记录 def -> IR 槽位(变量=内存指针, +// 函数=func ref)。loop_stack/defer_stack 驱动 break/continue/ret 的收尾。 + +type .{ .Error } = @import("utils/error.spl"); +type .{ .Vec } = @import("utils/vec.spl"); +type .{ .Map } = @import("utils/map.spl"); +type .{ .ByteView } = @import("utils/byte_view.spl"); +type .{ .ByteBuffer } = @import("utils/byte_buffer.spl"); +type .{ .hash_usize, .eq_usize } = @import("utils/hash.spl"); +type BigInt = @import("utils/mp.spl"); +type .{ .append_usize } = @import("utils/fmt.spl"); +type .{ .Sources, .Source, .set_at } = @import("utils/source.spl"); +type .{ .Span, .span_none, .Code, .no_file } = @import("./diag.spl"); +type Diag = @import("./diag.spl"); +type Ast = @import("./ast.spl"); +type .{ .Node } = @import("./ast.spl"); +type Sema = @import("./sema.spl"); +type LangType = @import("./lang_type.spl"); +type .{ .LangKind, .LangEntry, .LangMethod, .LangExtra } = @import("./lang_type.spl"); +type TypeTable = @import("./type_table.spl"); +type .{ .TypeNode, .TypeEntry, .Member, .AggKind, .LayoutMode } = @import("./type_table.spl"); +type .{ .DefKind, .DefNode, .DeclState } = @import("./def.spl"); +type .{ .Intrin, .IntrinOps } = @import("./intrin.spl"); +type Ir = @import("./ir.spl"); +type .{ .IrKind, .ConstLit, .IrEntry, .IrFunc, .IrBody } = @import("./ir.spl"); +type IrBuilder = @import("./ir_builder.spl"); +type .{ .Punct } = @import("./token.spl"); + +type LoopCtx = struct { + cont_blk: usize, + defer_base: usize, + label: ByteView, + breaks: Vec, // ir node ref + result_slot: usize, + result_tid: usize, +} + +type Ast2Ir = @self; + +sema: *Sema, +ir: *IrBuilder, +ast: *Ast, +def2ir_map: Map, +loop_stack: Vec, // LoopCtx +defer_stack: Vec, // ast node ref +cur_ret_lang: usize, +cur_defer_base: usize, +diag: *Diag, +owned: Vec, // ByteBuffer:为 dbg.panic 消息提供稳定存储 + +// ---- 小工具 ---- + +fn list_at(v: Vec, i: usize): usize { + match Vec.get(&v, i) { .Ok(p) => { ret (p as *usize).*; }, .Err(e) => { ret 0; } } + ret 0; +} + +fn push_u(v: *Vec, x: usize) { + match Vec.push(v, &x as *u8) { .Ok => {}, .Err(e) => {} } +} + +fn irt(self: *Ast2Ir, id: usize): *TypeEntry { + ret TypeTable.node(&self.sema.types.ir, id); +} + +fn ltn(self: *Ast2Ir, id: usize): *LangEntry { + ret LangType.lang_node(&self.sema.types, id); +} + +fn defn(self: *Ast2Ir, id: usize): *DefNode { + ret LangType.def_node(&self.sema.types, id); +} + +fn to_ir_type(self: *Ast2Ir, lang: usize): usize { + if lang == 0 { ret 0; } + ret LangType.get_ir_type(&self.sema.types, lang); +} + +fn node_lang(n: *Node): usize { + if n == null { ret 0; } + ret n.resolved_lang_tid; +} + +fn node_ir(self: *Ast2Ir, n: *Node): usize { + ret to_ir_type(self, node_lang(n)); +} + +fn const_usize(self: *Ast2Ir, v: usize): usize { + var t = LangType.make_int(&self.sema.types, @sizeof(usize) * 8, false); + ret IrBuilder.type_const_int(self.ir, to_ir_type(self, t), v); +} + +fn ir_map_get(self: *Ast2Ir, def_id: usize, out: *usize): bool { + match Map.get(&self.def2ir_map, &def_id as *u8, out as *u8) { + .Found => { ret true; }, + .NotFound => { ret false; }, + } + ret false; +} + +fn ir_map_put(self: *Ast2Ir, def_id: usize, val: usize) { + match Map.put(&self.def2ir_map, &def_id as *u8, &val as *u8) { .Ok => {}, .Err(e) => {} } +} + +fn kind_is(self: *Ast2Ir, lang_id: usize, want: LangKind): bool { + var e = ltn(self, lang_id); + if e == null { ret false; } + ret @enum2int(e.kind) == @enum2int(want); +} + +fn punct_is(op: Punct, want: Punct): bool { ret @enum2int(op) == @enum2int(want); } + +fn is_assign_arith(op: Punct): bool { + match op { + .AddAssign => { ret true; }, .SubAssign => { ret true; }, .MulAssign => { ret true; }, + .DivAssign => { ret true; }, .ModAssign => { ret true; }, .AndAssign => { ret true; }, + .OrAssign => { ret true; }, .XorAssign => { ret true; }, .LShAssign => { ret true; }, + .RShAssign => { ret true; }, _ => { ret false; }, + } + ret false; +} + +fn int_lit_u64(n: *Node): u64 { + match n.body { + .ExprIntLit(x) => { + var out: u64 = 0; + match BigInt.to_u64(&out, x.value) { .Ok => {}, .Err(e) => {} } + ret out; + }, + _ => {}, + } + ret 0; +} + +fn ir_kind_of(self: *Ast2Ir, ref: usize): IrKind { + var e = Ir.node(&self.ir.ir, self.ir.current_fn, ref); + if e == null { ret .ControlUnreachable; } + ret e.kind; +} + +// ---- 零值 ---- + +fn emit_zero_value(self: *Ast2Ir, tid: usize): usize { + var tn = irt(self, tid); + if tn == null { ret 0; } + var is_bool = false; + var is_int = false; + var is_float = false; + var is_ptr = false; + var is_fn = false; + var is_arr = false; + var arr_len: usize = 0; + var arr_elem: usize = 0; + var is_struct = false; + var is_shape = false; + var is_union = false; + var variants = Vec.blank(@sizeof(usize), @alignof(usize)); + match tn.node { + .Bool => { is_bool = true; }, + .Int(x) => { is_int = true; }, + .Float(x) => { is_float = true; }, + .Ptr(x) => { is_ptr = true; }, + .Fn(x) => { is_fn = true; }, + .Array(x) => { is_arr = true; arr_len = x.len; arr_elem = x.element; }, + .Struct(x) => { is_struct = true; variants = x.variants; }, + .Shape(x) => { is_shape = true; variants = x.variants; }, + .Union(x) => { is_union = true; variants = x.variants; }, + _ => { ret 0; }, + } + if is_bool { ret IrBuilder.type_const_bool(self.ir, tid, false); } + if is_int { ret IrBuilder.type_const_int(self.ir, tid, 0); } + if is_float { ret IrBuilder.type_const_float(self.ir, tid, 0.0); } + if is_ptr || is_fn { ret IrBuilder.type_const_ptr(self.ir, tid, 0); } + if is_arr { + var node = IrBuilder.agg_construct(self.ir, tid); + var i: usize = 0; + while i < arr_len { + var f = emit_zero_value(self, arr_elem); + if f == 0 { ret 0; } + IrBuilder.agg_construct_field(self.ir, node, f); + i = i + 1; + } + ret node; + } + if is_struct || is_shape { + var tids = Vec.blank(@sizeof(usize), @alignof(usize)); + var i: usize = 0; + while i < variants.len { + push_u(&tids, variant_at(variants, i)); + i = i + 1; + } + var node = IrBuilder.agg_construct(self.ir, tid); + i = 0; + while i < tids.len { + var f = emit_zero_value(self, list_at(tids, i)); + if f == 0 { Vec.drop(&tids); ret 0; } + IrBuilder.agg_construct_field(self.ir, node, f); + i = i + 1; + } + Vec.drop(&tids); + ret node; + } + if is_union { + if variants.len == 0 { ret IrBuilder.agg_construct(self.ir, tid); } + var node = IrBuilder.agg_construct(self.ir, tid); + var f = emit_zero_value(self, variant_at(variants, 0)); + if f == 0 { ret 0; } + IrBuilder.agg_construct_field(self.ir, node, f); + ret node; + } + ret 0; +} + +fn variant_at(v: Vec, idx: usize): usize { + var m = TypeTable.member_at(&v, idx); + if m == null { ret 0; } + ret m.type_id; +} + +// ---- 模式绑定 ---- + +fn emit_pattern_bind(self: *Ast2Ir, pat_ref: usize, src_ptr: usize, tid: usize) { + if pat_ref == 0 || src_ptr == 0 { ret; } + var pat = Ast.node(self.ast, pat_ref); + if pat == null { ret; } + + match pat.body { + .PatternIdent(x) => { + if pat.resolved_def_id != 0 { ir_map_put(self, pat.resolved_def_id, src_ptr); } + ret; + }, + .ExprIntLit(x) => { ret; }, + .ExprFloatLit(x) => { ret; }, + .ExprCharLit(x) => { ret; }, + .ExprStringLit(x) => { ret; }, + .ExprBoolLit(x) => { ret; }, + .ExprNullLit => { ret; }, + .PatternUnderscore => { ret; }, + .PatternList(x) => { + var i: usize = 0; + while i < x.items.len { + emit_pattern_bind(self, list_at(x.items, i), src_ptr, tid); + i = i + 1; + } + ret; + }, + .PatternTuple(x) => { + var tn = irt(self, tid); + if tn == null { ret; } + var variants = Vec.blank(@sizeof(usize), @alignof(usize)); + match tn.node { .Struct(a) => { variants = a.variants; }, _ => { ret; } } + var n = x.items.len; + var nf = variants.len; + if n > nf { ret; } + var i: usize = 0; + while i < n { + var ft = variant_at(variants, i); + var fp = IrBuilder.mem_field(self.ir, tid, src_ptr, i); + emit_pattern_bind(self, list_at(x.items, i), fp, ft); + i = i + 1; + } + ret; + }, + .PatternDot(x) => { + var tn = irt(self, tid); + if tn == null { ret; } + var variants = Vec.blank(@sizeof(usize), @alignof(usize)); + match tn.node { .Enum(a) => { variants = a.variants; }, _ => { ret; } } + var vidx = pat.resolved_def_id; + if vidx == 0 { ret; } + vidx = vidx - 1; + if vidx >= variants.len { ret; } + var payload_ir = variant_at(variants, vidx); + var pptr = IrBuilder.mem_field(self.ir, tid, src_ptr, vidx + 1); + if x.sub_pattern != 0 { + emit_pattern_bind(self, x.sub_pattern, pptr, payload_ir); + } + ret; + }, + .PatternAggregate(x) => { + var tn = irt(self, tid); + if tn == null { ret; } + var variants = Vec.blank(@sizeof(usize), @alignof(usize)); + match tn.node { + .Struct(a) => { variants = a.variants; }, + .Union(a) => { variants = a.variants; }, + _ => { ret; }, + } + var i: usize = 0; + while i < x.items.len { + var f = Ast.node(self.ast, list_at(x.items, i)); + if f != null { + match f.body { + .PatternField(pf) => { + var idx = f.resolved_def_id; + if idx < variants.len { + var ft = variant_at(variants, idx); + var fp = IrBuilder.mem_field(self.ir, tid, src_ptr, idx); + if pf.pattern != 0 { + emit_pattern_bind(self, pf.pattern, fp, ft); + } + } + }, + _ => {}, + } + } + i = i + 1; + } + ret; + }, + _ => { ret; }, + } +} + +fn emit_match_check(self: *Ast2Ir, pat_ref: usize, scrut_ir: usize, scrut_slot: usize): usize { + var pat = Ast.node(self.ast, pat_ref); + if pat == null { ret 0; } + match pat.body { + .PatternUnderscore => { ret 0; }, + .PatternIdent(x) => { ret 0; }, + .PatternList(x) => { + var bt = LangType.make_bool(&self.sema.types); + var bt_ir = to_ir_type(self, bt); + var combined: usize = 0; + var i: usize = 0; + while i < x.items.len { + var c = emit_match_check(self, list_at(x.items, i), scrut_ir, scrut_slot); + if c == 0 { ret 0; } + if combined == 0 { + combined = c; + } else { + combined = IrBuilder.arith_or(self.ir, bt_ir, combined, c); + } + i = i + 1; + } + ret combined; + }, + .PatternDot(x) => { + var tn = irt(self, scrut_ir); + if tn == null { ret 0; } + var tag_ir: usize = 0; + match tn.node { .Enum(a) => { tag_ir = a.tag_type; }, _ => { ret 0; } } + var vidx = pat.resolved_def_id; + if vidx == 0 { ret 0; } + vidx = vidx - 1; + var tag_ptr = IrBuilder.mem_field(self.ir, scrut_ir, scrut_slot, 0); + var tag_val = IrBuilder.mem_load(self.ir, tag_ir, tag_ptr); + var vidx_const = IrBuilder.type_const_int(self.ir, tag_ir, vidx); + ret IrBuilder.cmp_eq(self.ir, tag_ir, tag_val, vidx_const); + }, + .ExprIntLit(x) => { ret match_check_literal(self, pat_ref, scrut_ir, scrut_slot); }, + .ExprFloatLit(x) => { ret match_check_literal(self, pat_ref, scrut_ir, scrut_slot); }, + .ExprCharLit(x) => { ret match_check_literal(self, pat_ref, scrut_ir, scrut_slot); }, + .ExprStringLit(x) => { ret match_check_literal(self, pat_ref, scrut_ir, scrut_slot); }, + .ExprBoolLit(x) => { ret match_check_literal(self, pat_ref, scrut_ir, scrut_slot); }, + .ExprNullLit => { ret match_check_literal(self, pat_ref, scrut_ir, scrut_slot); }, + _ => { ret 0; }, + } + ret 0; +} + +fn match_check_literal(self: *Ast2Ir, pat_ref: usize, scrut_ir: usize, scrut_slot: usize): usize { + var val = emit_expr(self, pat_ref); + if val == 0 { ret 0; } + var loaded = IrBuilder.mem_load(self.ir, scrut_ir, scrut_slot); + ret IrBuilder.cmp_eq(self.ir, scrut_ir, loaded, val); +} + +fn emit_var_decl(self: *Ast2Ir, n: *Node): usize { + var var_ir = node_ir(self, n); + if var_ir == 0 { ret 0; } + var slot = IrBuilder.mem_alloca(self.ir, var_ir, const_usize(self, 1)); + + var iref: usize = 0; + var pat_ref: usize = 0; + match n.body { + .VarDecl(x) => { iref = x.init_expr; pat_ref = x.pattern; }, + .ConstDecl(x) => { iref = x.init_expr; pat_ref = x.pattern; }, + _ => {}, + } + if iref != 0 { + var v = emit_expr(self, iref); + if v != 0 { IrBuilder.mem_store(self.ir, var_ir, slot, v); } + } + emit_pattern_bind(self, pat_ref, slot, var_ir); + ret slot; +} + +fn copy_attrs_node(self: *Ast2Ir, n: *Node, ir_ref: usize) { + if ir_ref == 0 || n == null || n.attrs.len == 0 { ret; } + var irn = Ir.node(&self.ir.ir, self.ir.current_fn, ir_ref); + if irn == null { ret; } + var i: usize = 0; + while i < n.attrs.len { + var a = list_at(n.attrs, i); + match Vec.push(&irn.attrs, &a as *u8) { .Ok => {}, .Err(e) => {} } + i = i + 1; + } +} + +fn copy_attrs_func(self: *Ast2Ir, n: *Node, fn_ref: usize) { + if fn_ref == 0 || n == null || n.attrs.len == 0 { ret; } + var irf = Ir.func(&self.ir.ir, fn_ref); + if irf == null { ret; } + var i: usize = 0; + while i < n.attrs.len { + var a = list_at(n.attrs, i); + match Vec.push(&irf.attrs, &a as *u8) { .Ok => {}, .Err(e) => {} } + i = i + 1; + } +} + +// ---- intrinsic ---- + +fn ast2ir_intrin_cast(self: *Ast2Ir, n: *Node, in: Intrin, args: Vec): usize { + if args.len < 1 { + Diag.err(self.diag, n.span, .IntrinArgCount, "cast builtin requires at least 1 argument"); + ret 0; + } + var v = emit_expr(self, list_at(args, 0)); + if v == 0 { ret 0; } + var src = Ast.node(self.ast, list_at(args, 0)); + var ft = node_ir(self, src); + var tt: usize = 0; + if args.len >= 2 { + var targ = Ast.node(self.ast, list_at(args, 1)); + tt = node_ir(self, targ); + } else { + tt = node_ir(self, n); + } + if ft == 0 || tt == 0 { ret 0; } + ret ast2ir_emit_cast_builtin(self, n, in, ft, tt, v); +} + +fn ast2ir_intrin_call(self: *Ast2Ir, n: *Node, in: Intrin, args: Vec): usize { + if @enum2int(in) == @enum2int(Intrin.BitSizeof) { + var arg = Ast.node(self.ast, list_at(args, 0)); + ret IrBuilder.type_bitsizeof(self.ir, node_ir(self, arg)); + } + if @enum2int(in) == @enum2int(Intrin.Sizeof) { + var arg = Ast.node(self.ast, list_at(args, 0)); + ret IrBuilder.type_sizeof(self.ir, node_ir(self, arg)); + } + if @enum2int(in) == @enum2int(Intrin.Alignof) { + var arg = Ast.node(self.ast, list_at(args, 0)); + ret IrBuilder.type_alignof(self.ir, node_ir(self, arg)); + } + if @enum2int(in) == @enum2int(Intrin.Enum2Int) { + var src = Ast.node(self.ast, list_at(args, 0)); + var v = emit_expr(self, list_at(args, 0)); + if v == 0 { ret 0; } + var agg_t = node_ir(self, src); + var tag_t = node_ir(self, n); + if agg_t == 0 || tag_t == 0 { ret 0; } + ret IrBuilder.agg_extract(self.ir, agg_t, tag_t, 0, v); + } + if @enum2int(in) == @enum2int(Intrin.FieldCount) { + var arg = Ast.node(self.ast, list_at(args, 0)); + ret IrBuilder.type_field_count(self.ir, node_ir(self, arg)); + } + if @enum2int(in) == @enum2int(Intrin.OffsetOf) { + var arg = Ast.node(self.ast, list_at(args, 0)); + var agg_t = node_ir(self, arg); + if agg_t == 0 { ret 0; } + var fnode = Ast.node(self.ast, list_at(args, 1)); + var idx: usize = 0; + if fnode != null { + match fnode.body { + .ExprIntLit(x) => { idx = int_lit_u64(fnode) as usize; }, + .ExprIdent(e) => { + if !TypeTable.agg_member_idx(&self.sema.types.ir, agg_t, e.name, &idx) { ret 0; } + }, + _ => { ret 0; }, + } + } else { + ret 0; + } + var idx_node = const_usize(self, idx); + ret IrBuilder.type_offsetof(self.ir, agg_t, idx_node); + } + if @enum2int(in) == @enum2int(Intrin.Assert) { + ret ast2ir_intrin_assert(self, n, args); + } + if @enum2int(in) == @enum2int(Intrin.IntCast) || + @enum2int(in) == @enum2int(Intrin.Truncate) || + @enum2int(in) == @enum2int(Intrin.BitCast) || + @enum2int(in) == @enum2int(Intrin.FloatCast) || + @enum2int(in) == @enum2int(Intrin.Float2Int) || + @enum2int(in) == @enum2int(Intrin.Int2Float) || + @enum2int(in) == @enum2int(Intrin.Bool2Int) || + @enum2int(in) == @enum2int(Intrin.Ptr2Int) || + @enum2int(in) == @enum2int(Intrin.Int2Ptr) || + @enum2int(in) == @enum2int(Intrin.PtrCast) { + ret ast2ir_intrin_cast(self, n, in, args); + } + if @enum2int(in) == @enum2int(Intrin.Breakpoint) { + ret IrBuilder.dbg_breakpoint(self.ir); + } + ret 0; +} + +fn ast2ir_intrin_assert(self: *Ast2Ir, n: *Node, args: Vec): usize { + var cond = emit_expr(self, list_at(args, 0)); + if cond == 0 { ret 0; } + var b = LangType.make_bool(&self.sema.types); + var notc = IrBuilder.arith_not(self.ir, to_ir_type(self, b), cond); + var br = IrBuilder.control_br(self.ir, notc); + IrBuilder.block_end(self.ir); + + var panic_blk = IrBuilder.block_begin(self.ir); + + var msg = ByteBuffer.new(); + // 交给 Ast2Ir 持有:dbg.panic 只借用这段字节。 + match Vec.push(&self.owned, &msg as *u8) { + .Ok => {}, + .Err(e) => { ByteBuffer.drop(&msg); ret 0; }, + } + // 消息:::: assert failed[: user] + if self.sema.sources != null && n.span.file != no_file() { + var src = set_at(self.sema.sources, n.span.file); + if src != null { + ignore_bool(append_bytes(&msg, ByteView.as_slice(src.fname))); + var pos = Source.line_col(src.*, n.span.start); + ignore_bool(append_bytes(&msg, ":")); + ignore_bool(append_num(&msg, pos.line)); + ignore_bool(append_bytes(&msg, ":")); + ignore_bool(append_num(&msg, pos.col)); + } + } + ignore_bool(append_bytes(&msg, ": assert failed")); + if args.len == 2 { + var mn = Ast.node(self.ast, list_at(args, 1)); + if mn != null { + match mn.body { + .ExprStringLit(sl) => { + ignore_bool(append_bytes(&msg, ": ")); + ignore_bool(append_bytes(&msg, ByteView.as_slice(sl.data))); + }, + _ => {}, + } + } + } + var msg_view = ByteView.from_slice(ByteBuffer.as_slice(msg)); + @assert(msg_view.len == 0 || msg_view.ptr != null, "panic message bytes must be non-null"); + ignore_usize(IrBuilder.dbg_panic(self.ir, msg_view)); + ignore_usize(IrBuilder.control_trap(self.ir)); + IrBuilder.block_end(self.ir); + + var cont_blk = IrBuilder.block_begin(self.ir); + IrBuilder.control_br_fixup(self.ir, br, panic_blk, cont_blk); + ret 0; +} + +fn append_bytes(buf: *ByteBuffer, bytes: []u8): bool { + match ByteBuffer.append(buf, bytes) { .Ok => { ret true; }, .Err(e) => { ret false; } } + ret false; +} + +fn append_num(buf: *ByteBuffer, v: usize): bool { + match append_usize(buf, v, 10, false) { .Ok => { ret true; }, .Err(e) => { ret false; } } + ret false; +} + +fn ignore_bool(b: bool) {} + +fn ast2ir_intrin_field(self: *Ast2Ir, n: *Node, in: Intrin): usize { + if @enum2int(in) == @enum2int(Intrin.Len) { ret ast2ir_intrin_len(self, n); } + ret 0; +} + +fn ast2ir_intrin_len(self: *Ast2Ir, n: *Node): usize { + var obj_ref: usize = 0; + var fname = ByteView.empty(); + match n.body { .ExprField(x) => { obj_ref = x.obj; fname = x.name; }, _ => {} } + var obj = Ast.node(self.ast, obj_ref); + var recv_lang = node_lang(obj); + var recv = ltn(self, recv_lang); + if recv == null { ret 0; } + + var usize_lang = LangType.make_int(&self.sema.types, @sizeof(usize) * 8, false); + var usize_ir = to_ir_type(self, usize_lang); + + if kind_is(self, recv_lang, .Array) { + var arr = irt(self, recv.ir_type_id); + if arr == null { ret 0; } + match arr.node { + .Array(a) => { ret IrBuilder.type_const_int(self.ir, usize_ir, a.len); }, + _ => { ret 0; }, + } + } + if kind_is(self, recv_lang, .Slice) { + var slice_val = emit_expr(self, obj_ref); + if slice_val == 0 { ret 0; } + ret IrBuilder.agg_extract(self.ir, recv.ir_type_id, usize_ir, 1, slice_val); + } + ret 0; +} + +// ---- 左值 ---- + +fn emit_lvalue(self: *Ast2Ir, ref: usize): usize { + var n = Ast.node(self.ast, ref); + if n == null { ret 0; } + match n.body { + .ExprIdent(x) => { + var d = defn(self, n.resolved_def_id); + if d != null && @enum2int(d.kind) == @enum2int(DefKind.Fn) { ret 0; } + var slot: usize = 0; + if !ir_map_get(self, n.resolved_def_id, &slot) { ret 0; } + ret slot; + }, + .ExprUnary(x) => { + if !punct_is(x.op, .Mul) { ret 0; } + ret emit_expr(self, x.operand); + }, + .ExprField(x) => { ret lvalue_field(self, n, x.obj, x.name); }, + .ExprIndex(x) => { ret lvalue_index(self, n, x.obj, x.index); }, + _ => { ret 0; }, + } + ret 0; +} + +fn lvalue_field(self: *Ast2Ir, n: *Node, obj_ref: usize, field_name: ByteView): usize { + var obj = Ast.node(self.ast, obj_ref); + if obj == null { ret 0; } + var obj_lang = node_lang(obj); + var obj_ln = ltn(self, obj_lang); + if obj_ln == null { ret 0; } + + var base: usize = 0; + var agg_lang: usize = 0; + if kind_is(self, obj_lang, .Ptr) { + base = emit_expr(self, obj_ref); + agg_lang = extra_elem(obj_ln.*); + } else { + base = emit_lvalue(self, obj_ref); + agg_lang = obj_lang; + } + if base == 0 { ret 0; } + var agg_ir = to_ir_type(self, agg_lang); + if agg_ir == 0 { ret 0; } + var idx: usize = 0; + if !TypeTable.agg_member_idx(&self.sema.types.ir, agg_ir, field_name, &idx) { ret 0; } + if kind_is(self, agg_lang, .Enum) { idx = idx + 1; } + ret IrBuilder.mem_field(self.ir, agg_ir, base, idx); +} + +fn lvalue_index(self: *Ast2Ir, n: *Node, obj_ref: usize, index_ref: usize): usize { + var obj = Ast.node(self.ast, obj_ref); + if obj == null { ret 0; } + var obj_lang = node_lang(obj); + var obj_ln = ltn(self, obj_lang); + if obj_ln == null { ret 0; } + + var elem_lang: usize = 0; + if kind_is(self, obj_lang, .Ptr) || kind_is(self, obj_lang, .Array) || + kind_is(self, obj_lang, .Slice) { + elem_lang = extra_elem(obj_ln.*); + } + + if kind_is(self, obj_lang, .Tuple) { + var idx_node = Ast.node(self.ast, index_ref); + if idx_node == null || !is_int_lit(idx_node) { ret 0; } + var i = int_lit_u64(idx_node) as usize; + var base = emit_lvalue(self, obj_ref); + if base == 0 { ret 0; } + var tuple_ir = to_ir_type(self, obj_lang); + if tuple_ir == 0 { ret 0; } + ret IrBuilder.mem_field(self.ir, tuple_ir, base, i); + } + + var base: usize = 0; + if kind_is(self, obj_lang, .Ptr) { + base = emit_expr(self, obj_ref); + } else if kind_is(self, obj_lang, .Array) { + base = emit_lvalue(self, obj_ref); + } else if kind_is(self, obj_lang, .Slice) { + var slice_val = emit_expr(self, obj_ref); + if slice_val == 0 { ret 0; } + var ptr_ir = TypeTable.make_ptr(&self.sema.types.ir, to_ir_type(self, elem_lang)); + base = IrBuilder.agg_extract(self.ir, ptr_ir, ptr_ir, 0, slice_val); + } else { + ret 0; + } + if base == 0 { ret 0; } + var idx = emit_expr(self, index_ref); + if idx == 0 { ret 0; } + var elem_ir = to_ir_type(self, elem_lang); + if elem_ir == 0 { ret 0; } + ret IrBuilder.mem_offset(self.ir, elem_ir, base, idx); +} + +fn is_int_lit(n: *Node): bool { + match n.body { .ExprIntLit(x) => { ret true; }, _ => { ret false; } } + ret false; +} + +fn extra_elem(e: LangEntry): usize { + match e.extra { + .Elem(x) => { ret x; }, + .Array(a) => { ret a.elem; }, + _ => { ret 0; }, + } + ret 0; +} + +fn ignore_usize(x: usize) {} + +// ---- 转换 ---- + +fn ast2ir_emit_cast_builtin(self: *Ast2Ir, n: *Node, in: Intrin, ft: usize, tt: usize, + v: usize): usize { + if @enum2int(in) == @enum2int(Intrin.IntCast) { + var fs = irt(self, ft); + if fs != null { + match fs.node { + .Int(x) => { if x.is_signed { ret IrBuilder.cast_sext(self.ir, ft, tt, v); } }, + _ => {}, + } + } + ret IrBuilder.cast_zext(self.ir, ft, tt, v); + } + if @enum2int(in) == @enum2int(Intrin.Truncate) { ret IrBuilder.cast_trunc(self.ir, ft, tt, v); } + if @enum2int(in) == @enum2int(Intrin.BitCast) { ret IrBuilder.cast_bitcast(self.ir, ft, tt, v); } + if @enum2int(in) == @enum2int(Intrin.FloatCast) { + ret ir_float_cast(self, ft, tt, v); + } + if @enum2int(in) == @enum2int(Intrin.Float2Int) { ret IrBuilder.case_float2int(self.ir, ft, tt, v); } + if @enum2int(in) == @enum2int(Intrin.Int2Float) { ret IrBuilder.case_int2float(self.ir, ft, tt, v); } + if @enum2int(in) == @enum2int(Intrin.Bool2Int) { ret IrBuilder.cast_bool2int(self.ir, ft, tt, v); } + if @enum2int(in) == @enum2int(Intrin.Ptr2Int) { ret IrBuilder.cast_ptr2int(self.ir, ft, tt, v); } + if @enum2int(in) == @enum2int(Intrin.Int2Ptr) { ret IrBuilder.cast_int2ptr(self.ir, ft, tt, v); } + if @enum2int(in) == @enum2int(Intrin.PtrCast) { ret IrBuilder.cast_bitcast(self.ir, ft, tt, v); } + Diag.err(self.diag, n.span, .Internal, "ast2ir: unknown cast builtin"); + ret 0; +} + +fn ir_float_cast(self: *Ast2Ir, ft: usize, tt: usize, v: usize): usize { + var fs = irt(self, ft); + var ts = irt(self, tt); + var fbits: usize = 0; + var tbits: usize = 0; + if fs != null { match fs.node { .Float(x) => { fbits = x.bits; }, _ => {} } } + if ts != null { match ts.node { .Float(x) => { tbits = x.bits; }, _ => {} } } + if fbits > tbits { ret IrBuilder.cast_ftrunc(self.ir, ft, tt, v); } + ret IrBuilder.cast_fext(self.ir, ft, tt, v); +} + +fn emit_cast(self: *Ast2Ir, n: *Node): usize { + var eref: usize = 0; + var tref: usize = 0; + match n.body { .ExprCast(x) => { eref = x.expr; tref = x.type_expr; }, _ => {} } + var v = emit_expr_raw(self, eref); + if v == 0 { ret 0; } + var src = Ast.node(self.ast, eref); + var dst = Ast.node(self.ast, tref); + var ft = node_ir(self, src); + var tt = node_ir(self, dst); + if ft == 0 || tt == 0 { ret 0; } + var in = IntrinOps.lookup_cast(&self.sema.types.ir, ft, tt); + if @enum2int(in) == @enum2int(Intrin.None) { + Diag.err(self.diag, n.span, .CastInvalid, "as: no conversion between the given types"); + ret 0; + } + ret ast2ir_emit_cast_builtin(self, n, in, ft, tt, v); +} + +fn ast2ir_field_from_value(self: *Ast2Ir, n: *Node): usize { + var obj_ref: usize = 0; + var field_name = ByteView.empty(); + match n.body { .ExprField(x) => { obj_ref = x.obj; field_name = x.name; }, _ => {} } + var obj = Ast.node(self.ast, obj_ref); + if obj == null { ret 0; } + var obj_lang = node_lang(obj); + var obj_ln = ltn(self, obj_lang); + if obj_ln == null { ret 0; } + + var agg_ir = to_ir_type(self, obj_lang); + if agg_ir == 0 { ret 0; } + var idx: usize = 0; + if !TypeTable.agg_member_idx(&self.sema.types.ir, agg_ir, field_name, &idx) { ret 0; } + var field_idx = idx; + if kind_is(self, obj_lang, .Enum) { field_idx = idx + 1; } + + var field_ir: usize = 0; + var agg_tn = irt(self, agg_ir); + if agg_tn != null { + var variants = Vec.blank(@sizeof(usize), @alignof(usize)); + match agg_tn.node { + .Struct(a) => { variants = a.variants; }, + .Union(a) => { variants = a.variants; }, + .Enum(a) => { variants = a.variants; }, + .Shape(a) => { variants = a.variants; }, + _ => {}, + } + if field_idx < variants.len { field_ir = variant_at(variants, field_idx); } + } + var val = emit_expr(self, obj_ref); + if val == 0 { ret 0; } + ret IrBuilder.agg_extract(self.ir, agg_ir, field_ir, field_idx, val); +} + +fn ast2ir_enum_variant(self: *Ast2Ir, n: *Node): usize { + var t = node_ir(self, n); + if t == 0 { ret 0; } + var tn = irt(self, t); + if tn == null { ret 0; } + var field_name = ByteView.empty(); + match n.body { .ExprField(x) => { field_name = x.name; }, _ => {} } + var vidx: usize = 0; + if !TypeTable.agg_member_idx(&self.sema.types.ir, t, field_name, &vidx) { ret 0; } + var tag_ir: usize = 0; + var variants = Vec.blank(@sizeof(usize), @alignof(usize)); + match tn.node { .Enum(a) => { tag_ir = a.tag_type; variants = a.variants; }, _ => { ret 0; } } + var eptr = IrBuilder.mem_alloca(self.ir, t, const_usize(self, 1)); + var tptr = IrBuilder.mem_field(self.ir, t, eptr, 0); + var tag_val = IrBuilder.type_const_int(self.ir, tag_ir, vidx); + ignore_usize(IrBuilder.mem_store(self.ir, tag_ir, tptr, tag_val)); + if vidx < variants.len { + var payload_ir = variant_at(variants, vidx); + if payload_ir != 0 { + var pptr = IrBuilder.mem_field(self.ir, t, eptr, vidx + 1); + var zero = emit_zero_value(self, payload_ir); + ignore_usize(IrBuilder.mem_store(self.ir, payload_ir, pptr, zero)); + } + } + ret eptr; +} + +// ---- 表达式 ---- + +fn emit_expr(self: *Ast2Ir, ref: usize): usize { + var val = emit_expr_raw(self, ref); + if val == 0 { ret 0; } + var n = Ast.node(self.ast, ref); + if n == null || n.expected_lang_tid == 0 { ret val; } + copy_attrs_node(self, n, val); + var from = n.resolved_lang_tid; + var to = n.expected_lang_tid; + if from == 0 || from == to { ret val; } + var from_ir = to_ir_type(self, from); + var to_ir = to_ir_type(self, to); + if from_ir == 0 || to_ir == 0 || from_ir == to_ir { ret val; } + ret emit_implicit_cast(self, n, from_ir, to_ir, val); +} + +fn emit_expr_raw(self: *Ast2Ir, ref: usize): usize { + var n = Ast.node(self.ast, ref); + if n == null { ret 0; } + match n.body { + .ExprIntLit(x) => { ret emit_int_lit(self, n); }, + .ExprFloatLit(x) => { ret IrBuilder.type_const_float(self.ir, node_ir(self, n), 0.0); }, + .ExprCharLit(x) => { ret IrBuilder.type_const_char(self.ir, node_ir(self, n), x.value); }, + .ExprStringLit(sl) => { ret emit_string_lit(self, n, sl.data); }, + .ExprBoolLit(x) => { ret IrBuilder.type_const_bool(self.ir, node_ir(self, n), x.value); }, + .ExprNullLit => { ret IrBuilder.type_const_ptr(self.ir, node_ir(self, n), 0); }, + .ExprUndefinedLit => { ret 0; }, + .ExprIdent(x) => { ret emit_ident(self, n); }, + .ExprUnary(x) => { ret emit_unary(self, n, ref, x.op, x.operand); }, + .ExprBinary(x) => { ret emit_binary(self, n, x.op, x.left, x.right); }, + .ExprCall(x) => { ret emit_call(self, n, x.callee, x.args); }, + .ExprField(x) => { ret emit_field(self, n, ref, x.obj, x.name); }, + .ExprIndex(x) => { + var ptr = emit_lvalue(self, ref); + if ptr == 0 { ret 0; } + ret IrBuilder.mem_load(self.ir, node_ir(self, n), ptr); + }, + .ExprCast(x) => { ret emit_cast(self, n); }, + .ExprTuple(x) => { ret emit_tuple(self, n, x.items); }, + .ExprArrayLit(x) => { ret emit_array_lit(self, n, x.elems); }, + .ExprAggregateLit(x) => { ret emit_aggregate_lit(self, n, x.name, x.fields); }, + .ExprBlock(x) => { ret emit_block(self, n, x.stmts, x.tail_expr); }, + .ExprIf(x) => { ret emit_if_like(self, ref); }, + .ExprIfvar(x) => { ret emit_if_like(self, ref); }, + .ExprMatch(x) => { ret emit_match(self, n, x.matched_expr, x.arms); }, + .ExprWhile(x) => { ret emit_loop_like(self, ref); }, + .ExprLoop(x) => { ret emit_loop_like(self, ref); }, + .ExprFor(x) => { + Diag.err(self.diag, n.span, .UnhandledNode, "for is not implemented yet"); + ret 0; + }, + .ExprSlice(x) => { ret emit_slice(self, n, x.obj, x.begin, x.end); }, + .ExprComptimeCall(x) => { + var in = IntrinOps.lookup_call(x.name); + if @enum2int(in) == @enum2int(Intrin.None) { ret 0; } + ret ast2ir_intrin_call(self, n, in, x.args); + }, + _ => { ret 0; }, + } + ret 0; +} + +fn emit_int_lit(self: *Ast2Ir, n: *Node): usize { + var it = node_ir(self, n); + var itn = irt(self, it); + var is_float = false; + if itn != null { + match itn.node { .Float(x) => { is_float = true; }, _ => {} } + } + if is_float { ret IrBuilder.type_const_float(self.ir, it, 0.0); } + ret IrBuilder.type_const_int(self.ir, it, int_lit_u64(n) as usize); +} + +fn emit_string_lit(self: *Ast2Ir, n: *Node, data: ByteView): usize { + // parser 已解码(不含引号);字节由 Ast 持有,IR 只借用。 + @assert(data.len != 0 || data.ptr != null, "empty string bytes must be non-null"); + var len = data.len; + var u8_lang = LangType.make_int(&self.sema.types, 8, false); + var u8_ir = to_ir_type(self, u8_lang); + var ptr_u8_ir = TypeTable.make_ptr(&self.sema.types.ir, u8_ir); + var usize_lang = LangType.make_int(&self.sema.types, @sizeof(usize) * 8, false); + var usize_ir = to_ir_type(self, usize_lang); + var arr_lang = LangType.make_array(&self.sema.types, u8_lang, len); + var arr_ir = to_ir_type(self, arr_lang); + + var bytes_ptr = data.ptr as usize; + var raw_c = IrBuilder.type_const_bytes(self.ir, arr_ir, bytes_ptr); + var ptr = IrBuilder.mem_global_alloc(self.ir, ptr_u8_ir, raw_c); + var len_c = IrBuilder.type_const_int(self.ir, usize_ir, len); + + var slice_ir = node_ir(self, n); + var node = IrBuilder.agg_construct(self.ir, slice_ir); + IrBuilder.agg_construct_field(self.ir, node, ptr); + IrBuilder.agg_construct_field(self.ir, node, len_c); + ret node; +} + +fn emit_ident(self: *Ast2Ir, n: *Node): usize { + var d = defn(self, n.resolved_def_id); + if d != null && @enum2int(d.kind) == @enum2int(DefKind.Fn) { + var fn_ref: usize = 0; + if !ir_map_get(self, n.resolved_def_id, &fn_ref) { ret 0; } + ret IrBuilder.type_const_fn(self.ir, node_ir(self, n), fn_ref); + } + var slot: usize = 0; + if !ir_map_get(self, n.resolved_def_id, &slot) { ret 0; } + ret IrBuilder.mem_load(self.ir, node_ir(self, n), slot); +} + +fn emit_unary(self: *Ast2Ir, n: *Node, ref: usize, op: Punct, operand: usize): usize { + if punct_is(op, .And) { ret emit_lvalue(self, operand); } + if punct_is(op, .Mul) { + var ptr = emit_lvalue(self, ref); + if ptr == 0 { ret 0; } + ret IrBuilder.mem_load(self.ir, node_ir(self, n), ptr); + } + var v = emit_expr(self, operand); + if v == 0 { ret 0; } + var t = node_ir(self, n); + if punct_is(op, .Sub) { ret IrBuilder.arith_neg(self.ir, t, v); } + if punct_is(op, .Not) { ret IrBuilder.arith_not(self.ir, t, v); } + if punct_is(op, .BitNot) { ret IrBuilder.arith_not(self.ir, t, v); } + ret 0; +} + +fn emit_binary(self: *Ast2Ir, n: *Node, op: Punct, left: usize, right: usize): usize { + if punct_is(op, .Assign) { + var ptr = emit_lvalue(self, left); + var v = emit_expr(self, right); + if ptr == 0 || v == 0 { ret 0; } + var ln = Ast.node(self.ast, left); + var t = node_ir(self, ln); + ignore_usize(IrBuilder.mem_store(self.ir, t, ptr, v)); + ret v; + } + if is_assign_arith(op) { ret emit_assign_arith(self, op, left, right); } + if punct_is(op, .OrOr) || punct_is(op, .AndAnd) { + ret emit_short_circuit(self, op, left, right); + } + var l = emit_expr(self, left); + var r = emit_expr(self, right); + if l == 0 || r == 0 { ret 0; } + var ln = Ast.node(self.ast, left); + var lt = node_lang(ln); + var lln = ltn(self, lt); + if lln != null && punct_is_op_ptr_addsub(op) && kind_is(self, lt, .Ptr) { + var elem_lang = extra_elem(lln.*); + var elem_ir = to_ir_type(self, elem_lang); + var rn = Ast.node(self.ast, right); + var rt_ir = node_ir(self, rn); + var off = r; + if punct_is(op, .Sub) { off = IrBuilder.arith_neg(self.ir, rt_ir, r); } + ret IrBuilder.mem_offset(self.ir, elem_ir, l, off); + } + var t = node_ir(self, n); + if punct_is(op, .Add) { ret IrBuilder.arith_add(self.ir, t, l, r); } + if punct_is(op, .Sub) { ret IrBuilder.arith_sub(self.ir, t, l, r); } + if punct_is(op, .Mul) { ret IrBuilder.arith_mul(self.ir, t, l, r); } + if punct_is(op, .Div) { ret IrBuilder.arith_div(self.ir, t, l, r); } + if punct_is(op, .Mod) { ret IrBuilder.arith_rem(self.ir, t, l, r); } + if punct_is(op, .And) { ret IrBuilder.arith_and(self.ir, t, l, r); } + if punct_is(op, .Or) { ret IrBuilder.arith_or(self.ir, t, l, r); } + if punct_is(op, .Xor) { ret IrBuilder.arith_xor(self.ir, t, l, r); } + if punct_is(op, .LSh) { ret IrBuilder.arith_shl(self.ir, t, l, r); } + if punct_is(op, .RSh) { ret IrBuilder.arith_shr(self.ir, t, l, r); } + if is_cmp_punct(op) { + var op_tid = node_ir(self, ln); + if punct_is(op, .Eq) { ret IrBuilder.cmp_eq(self.ir, op_tid, l, r); } + if punct_is(op, .Neq) { ret IrBuilder.cmp_ne(self.ir, op_tid, l, r); } + if punct_is(op, .Lt) { ret IrBuilder.cmp_lt(self.ir, op_tid, l, r); } + if punct_is(op, .Le) { ret IrBuilder.cmp_le(self.ir, op_tid, l, r); } + if punct_is(op, .Gt) { ret IrBuilder.cmp_gt(self.ir, op_tid, l, r); } + if punct_is(op, .Ge) { ret IrBuilder.cmp_ge(self.ir, op_tid, l, r); } + ret 0; + } + ret 0; +} + +fn punct_is_op_ptr_addsub(op: Punct): bool { + ret punct_is(op, .Add) || punct_is(op, .Sub); +} + +fn is_cmp_punct(op: Punct): bool { + match op { + .Eq => { ret true; }, .Neq => { ret true; }, .Lt => { ret true; }, + .Le => { ret true; }, .Gt => { ret true; }, .Ge => { ret true; }, _ => { ret false; }, + } + ret false; +} + +fn emit_assign_arith(self: *Ast2Ir, op: Punct, left: usize, right: usize): usize { + var ptr = emit_lvalue(self, left); + if ptr == 0 { ret 0; } + var ln = Ast.node(self.ast, left); + var t = node_ir(self, ln); + var lv = IrBuilder.mem_load(self.ir, t, ptr); + var rv = emit_expr(self, right); + if lv == 0 || rv == 0 { ret 0; } + var res: usize = 0; + if punct_is(op, .AddAssign) { res = IrBuilder.arith_add(self.ir, t, lv, rv); } + else if punct_is(op, .SubAssign) { res = IrBuilder.arith_sub(self.ir, t, lv, rv); } + else if punct_is(op, .MulAssign) { res = IrBuilder.arith_mul(self.ir, t, lv, rv); } + else if punct_is(op, .DivAssign) { res = IrBuilder.arith_div(self.ir, t, lv, rv); } + else if punct_is(op, .ModAssign) { res = IrBuilder.arith_rem(self.ir, t, lv, rv); } + else if punct_is(op, .AndAssign) { res = IrBuilder.arith_and(self.ir, t, lv, rv); } + else if punct_is(op, .OrAssign) { res = IrBuilder.arith_or(self.ir, t, lv, rv); } + else if punct_is(op, .XorAssign) { res = IrBuilder.arith_xor(self.ir, t, lv, rv); } + else if punct_is(op, .LShAssign) { res = IrBuilder.arith_shl(self.ir, t, lv, rv); } + else if punct_is(op, .RShAssign) { res = IrBuilder.arith_shr(self.ir, t, lv, rv); } + else { ret 0; } + if res == 0 { ret 0; } + ignore_usize(IrBuilder.mem_store(self.ir, t, ptr, res)); + ret res; +} + +fn emit_short_circuit(self: *Ast2Ir, op: Punct, left: usize, right: usize): usize { + var b_lang = LangType.make_bool(&self.sema.types); + var b_ir = to_ir_type(self, b_lang); + var slot = IrBuilder.mem_alloca(self.ir, b_ir, const_usize(self, 1)); + + var l = emit_expr(self, left); + if l == 0 { ret 0; } + ignore_usize(IrBuilder.mem_store(self.ir, b_ir, slot, l)); + + var br = IrBuilder.control_br(self.ir, l); + IrBuilder.block_end(self.ir); + + var rhs_blk = IrBuilder.block_begin(self.ir); + var r = emit_expr(self, right); + if r != 0 { ignore_usize(IrBuilder.mem_store(self.ir, b_ir, slot, r)); } + var rhs_end: usize = 0; + if !self.ir.terminated { rhs_end = IrBuilder.control_jmp(self.ir); } + IrBuilder.block_end(self.ir); + + var end_blk = IrBuilder.block_begin(self.ir); + if punct_is(op, .AndAnd) { + IrBuilder.control_br_fixup(self.ir, br, rhs_blk, end_blk); + } else { + IrBuilder.control_br_fixup(self.ir, br, end_blk, rhs_blk); + } + if rhs_end != 0 { IrBuilder.control_jmp_fixup(self.ir, rhs_end, end_blk); } + ret IrBuilder.mem_load(self.ir, b_ir, slot); +} + +fn emit_call(self: *Ast2Ir, n: *Node, callee_ref: usize, args: Vec): usize { + var callee = Ast.node(self.ast, callee_ref); + if callee != null && is_ident(callee) { + var cname = ident_name(callee); + var in = IntrinOps.lookup_call(cname); + if @enum2int(in) != @enum2int(Intrin.None) { + ret ast2ir_intrin_call(self, n, in, args); + } + } + var fn_val = emit_expr(self, callee_ref); + if fn_val == 0 { ret 0; } + var arg_refs = Vec.blank(@sizeof(usize), @alignof(usize)); + var i: usize = 0; + while i < args.len { + push_u(&arg_refs, emit_expr(self, list_at(args, i))); + i = i + 1; + } + var ret_t = node_ir(self, n); + var call = IrBuilder.control_call(self.ir, ret_t, fn_val); + if call == 0 { Vec.drop(&arg_refs); ret 0; } + i = 0; + while i < arg_refs.len { + IrBuilder.control_call_param(self.ir, call, list_at(arg_refs, i)); + i = i + 1; + } + Vec.drop(&arg_refs); + ret call; +} + +fn is_ident(n: *Node): bool { + match n.body { .ExprIdent(x) => { ret true; }, _ => { ret false; } } + ret false; +} + +fn ident_name(n: *Node): ByteView { + match n.body { .ExprIdent(x) => { ret x.name; }, _ => { ret ByteView.empty(); } } + ret ByteView.empty(); +} + +fn emit_field(self: *Ast2Ir, n: *Node, ref: usize, obj_ref: usize, field_name: ByteView): usize { + var obj = Ast.node(self.ast, obj_ref); + var obj_lang = node_lang(obj); + var obj_ln = ltn(self, obj_lang); + if obj_ln != null { + var in = IntrinOps.lookup_field(obj_ln.kind, field_name); + if @enum2int(in) != @enum2int(Intrin.None) { + ret ast2ir_intrin_field(self, n, in); + } + } + if n.resolved_def_id != 0 { + var fd = defn(self, n.resolved_def_id); + if fd != null && @enum2int(fd.kind) == @enum2int(DefKind.Fn) { + var fn_ref: usize = 0; + if !ir_map_get(self, n.resolved_def_id, &fn_ref) { ret 0; } + ret IrBuilder.type_const_fn(self.ir, node_ir(self, n), fn_ref); + } + } + var objn = Ast.node(self.ast, obj_ref); + if objn != null && is_ident(objn) { + var od = defn(self, objn.resolved_def_id); + var et = node_lang(objn); + if od != null && @enum2int(od.kind) == @enum2int(DefKind.Agg) && kind_is(self, et, .Enum) { + ret ast2ir_enum_variant(self, n); + } + } + var ptr = emit_lvalue(self, ref); + if ptr != 0 { ret IrBuilder.mem_load(self.ir, node_ir(self, n), ptr); } + ret ast2ir_field_from_value(self, n); +} + +fn emit_tuple(self: *Ast2Ir, n: *Node, items: Vec): usize { + var t = node_ir(self, n); + var nelems = items.len; + var field_refs = Vec.blank(@sizeof(usize), @alignof(usize)); + var i: usize = 0; + while i < nelems { + var f = emit_expr(self, list_at(items, i)); + if f == 0 { Vec.drop(&field_refs); ret 0; } + push_u(&field_refs, f); + i = i + 1; + } + var node = IrBuilder.agg_construct(self.ir, t); + if node == 0 { Vec.drop(&field_refs); ret 0; } + i = 0; + while i < nelems { + IrBuilder.agg_construct_field(self.ir, node, list_at(field_refs, i)); + i = i + 1; + } + Vec.drop(&field_refs); + ret node; +} + +fn emit_array_lit(self: *Ast2Ir, n: *Node, elems: Vec): usize { + var t = node_ir(self, n); + if t == 0 { ret 0; } + var nelems = elems.len; + + var sln = ltn(self, node_lang(n)); + if sln != null && kind_is(self, node_lang(n), .Slice) { + ret emit_array_to_slice(self, n, t, elems, extra_elem(sln.*), nelems); + } + var field_refs = Vec.blank(@sizeof(usize), @alignof(usize)); + var i: usize = 0; + while i < nelems { + var f = emit_expr(self, list_at(elems, i)); + if f == 0 { Vec.drop(&field_refs); ret 0; } + push_u(&field_refs, f); + i = i + 1; + } + var node = IrBuilder.agg_construct(self.ir, t); + if node == 0 { Vec.drop(&field_refs); ret 0; } + i = 0; + while i < nelems { + IrBuilder.agg_construct_field(self.ir, node, list_at(field_refs, i)); + i = i + 1; + } + Vec.drop(&field_refs); + ret node; +} + +fn emit_array_to_slice(self: *Ast2Ir, n: *Node, t: usize, elems: Vec, elem_lang: usize, + nelems: usize): usize { + var elem_ir = to_ir_type(self, elem_lang); + if elem_ir == 0 { ret 0; } + var usize_lang = LangType.make_int(&self.sema.types, @sizeof(usize) * 8, false); + var usize_ir = to_ir_type(self, usize_lang); + var ptr_ir = TypeTable.make_ptr(&self.sema.types.ir, elem_ir); + + var base: usize = 0; + if nelems == 0 { + base = IrBuilder.type_const_ptr(self.ir, ptr_ir, 0); + } else { + var arr_ir = TypeTable.make_array(&self.sema.types.ir, elem_ir, nelems); + var slot = IrBuilder.mem_alloca(self.ir, arr_ir, const_usize(self, 1)); + var i: usize = 0; + while i < nelems { + var fptr = IrBuilder.mem_field(self.ir, arr_ir, slot, i); + var v = emit_expr(self, list_at(elems, i)); + if v == 0 { ret 0; } + ignore_usize(IrBuilder.mem_store(self.ir, elem_ir, fptr, v)); + i = i + 1; + } + base = IrBuilder.mem_field(self.ir, arr_ir, slot, 0); + } + if base == 0 { ret 0; } + var len = IrBuilder.type_const_int(self.ir, usize_ir, nelems); + var node = IrBuilder.agg_construct(self.ir, t); + if node == 0 { ret 0; } + IrBuilder.agg_construct_field(self.ir, node, base); + IrBuilder.agg_construct_field(self.ir, node, len); + ret node; +} + +fn emit_aggregate_lit(self: *Ast2Ir, n: *Node, type_name: ByteView, fields: Vec): usize { + var t = node_ir(self, n); + if t == 0 { ret 0; } + var tn = irt(self, t); + if tn == null { ret 0; } + var is_enum = false; + var tag_ir: usize = 0; + var variants = Vec.blank(@sizeof(usize), @alignof(usize)); + match tn.node { + .Enum(a) => { is_enum = true; tag_ir = a.tag_type; variants = a.variants; }, + .Struct(a) => { variants = a.variants; }, + .Union(a) => { variants = a.variants; }, + .Shape(a) => { variants = a.variants; }, + _ => {}, + } + + if is_enum { + if fields.len != 1 { ret 0; } + var item = Ast.node(self.ast, list_at(fields, 0)); + if item == null { ret 0; } + var iname = ByteView.empty(); + var iexpr: usize = 0; + match item.body { .StructInitItem(si) => { iname = si.name; iexpr = si.expr; }, _ => { ret 0; } } + var vidx: usize = 0; + if !TypeTable.agg_member_idx(&self.sema.types.ir, t, iname, &vidx) { ret 0; } + var eptr = IrBuilder.mem_alloca(self.ir, t, const_usize(self, 1)); + var tptr = IrBuilder.mem_field(self.ir, t, eptr, 0); + var tag_val = IrBuilder.type_const_int(self.ir, tag_ir, vidx); + ignore_usize(IrBuilder.mem_store(self.ir, tag_ir, tptr, tag_val)); + if iexpr != 0 { + var payload_ir = variant_at(variants, vidx); + var pptr = IrBuilder.mem_field(self.ir, t, eptr, vidx + 1); + var payload = emit_expr(self, iexpr); + if payload == 0 { ret 0; } + ignore_usize(IrBuilder.mem_store(self.ir, payload_ir, pptr, payload)); + } + ret eptr; + } + + var nfields = variants.len; + var field_tids = Vec.blank(@sizeof(usize), @alignof(usize)); + var i: usize = 0; + while i < nfields { + push_u(&field_tids, variant_at(variants, i)); + i = i + 1; + } + var fvals = Vec.blank(@sizeof(usize), @alignof(usize)); + i = 0; + while i < nfields { + push_u(&fvals, 0); + i = i + 1; + } + i = 0; + while i < fields.len { + var item = Ast.node(self.ast, list_at(fields, i)); + if item != null { + var iname = ByteView.empty(); + var iexpr: usize = 0; + match item.body { .StructInitItem(si) => { iname = si.name; iexpr = si.expr; }, _ => { i = i + 1; continue; } } + var idx: usize = 0; + if TypeTable.agg_member_idx(&self.sema.types.ir, t, iname, &idx) { + var f = emit_expr(self, iexpr); + if f != 0 { set_u(&fvals, idx, f); } + } + } + i = i + 1; + } + i = 0; + while i < nfields { + if list_at(fvals, i) == 0 { + var f = emit_zero_value(self, list_at(field_tids, i)); + if f == 0 { Vec.drop(&field_tids); Vec.drop(&fvals); ret 0; } + set_u(&fvals, i, f); + } + i = i + 1; + } + var node = IrBuilder.agg_construct(self.ir, t); + if node == 0 { Vec.drop(&field_tids); Vec.drop(&fvals); ret 0; } + i = 0; + while i < nfields { + IrBuilder.agg_construct_field(self.ir, node, list_at(fvals, i)); + i = i + 1; + } + Vec.drop(&field_tids); + Vec.drop(&fvals); + ret node; +} + +fn set_u(v: *Vec, i: usize, x: usize) { + match Vec.set(v, i, &x as *u8) { .Ok => {}, .Err(e) => {} } +} + +fn emit_deferred_until(self: *Ast2Ir, base: usize) { + if self.defer_stack.len <= base { ret; } + var saved = self.defer_stack; + self.defer_stack = Vec.blank(@sizeof(usize), @alignof(usize)); + var i: usize = 0; + while i < saved.len { + push_u(&self.defer_stack, list_at(saved, i)); + i = i + 1; + } + while !self.ir.terminated && self.defer_stack.len > base { + var out: usize = 0; + match Vec.pop(&self.defer_stack, &out as *u8) { .Ok => {}, .Err(e) => { break; } } + emit_stmt(self, out); + } + Vec.drop(&self.defer_stack); + self.defer_stack = saved; +} + +fn emit_block(self: *Ast2Ir, n: *Node, stmts: Vec, tail: usize): usize { + var defer_base = self.defer_stack.len; + var i: usize = 0; + while i < stmts.len && !self.ir.terminated { + emit_stmt(self, list_at(stmts, i)); + i = i + 1; + } + var value: usize = 0; + if !self.ir.terminated && tail != 0 { value = emit_expr(self, tail); } + if !self.ir.terminated { emit_deferred_until(self, defer_base); } + self.defer_stack.len = defer_base; + ret value; +} + +fn emit_match(self: *Ast2Ir, n: *Node, matched_ref: usize, arms: Vec): usize { + var matched_node = Ast.node(self.ast, matched_ref); + var matched_lang = node_lang(matched_node); + var matched_ir = to_ir_type(self, matched_lang); + if matched_ir == 0 { ret 0; } + var scrut_slot = IrBuilder.mem_alloca(self.ir, matched_ir, const_usize(self, 1)); + var scrut_val = emit_expr(self, matched_ref); + if scrut_val == 0 { ret 0; } + ignore_usize(IrBuilder.mem_store(self.ir, matched_ir, scrut_slot, scrut_val)); + + var narms = arms.len; + var arm_branches = Vec.blank(@sizeof(usize), @alignof(usize)); + var cond_blks = Vec.blank(@sizeof(usize), @alignof(usize)); + var i: usize = 0; + while i < narms { + push_u(&cond_blks, self.ir.current_block); + var arm = Ast.node(self.ast, list_at(arms, i)); + var cond: usize = 0; + if arm != null { + var pat: usize = 0; + match arm.body { .MatchArm(ma) => { pat = ma.pattern; }, _ => {} } + cond = emit_match_check(self, pat, matched_ir, scrut_slot); + } + if cond != 0 { + push_u(&arm_branches, IrBuilder.control_br(self.ir, cond)); + } else { + push_u(&arm_branches, IrBuilder.control_jmp(self.ir)); + } + IrBuilder.block_end(self.ir); + if i + 1 < narms { ignore_usize(IrBuilder.block_begin(self.ir)); } + i = i + 1; + } + var no_match_blk = IrBuilder.block_begin(self.ir); + var no_match_jmp = IrBuilder.control_jmp(self.ir); + IrBuilder.block_end(self.ir); + + var body_jmps = Vec.blank(@sizeof(usize), @alignof(usize)); + var body_blks = Vec.blank(@sizeof(usize), @alignof(usize)); + i = 0; + while i < narms { + push_u(&body_blks, IrBuilder.block_begin(self.ir)); + var arm = Ast.node(self.ast, list_at(arms, i)); + if arm != null { + var pat: usize = 0; + var body: usize = 0; + match arm.body { .MatchArm(ma) => { pat = ma.pattern; body = ma.body; }, _ => {} } + emit_pattern_bind(self, pat, scrut_slot, matched_ir); + if body != 0 { ignore_usize(emit_expr(self, body)); } + } + var j: usize = 0; + if !self.ir.terminated { j = IrBuilder.control_jmp(self.ir); } + push_u(&body_jmps, j); + IrBuilder.block_end(self.ir); + i = i + 1; + } + var end_blk = IrBuilder.block_begin(self.ir); + i = 0; + while i < narms { + var branch = list_at(arm_branches, i); + if branch != 0 { + var bk = ir_kind_of(self, branch); + var body_blk = list_at(body_blks, i); + if @enum2int(bk) == @enum2int(IrKind.ControlBr) { + var next_blk = no_match_blk; + if i + 1 < narms { next_blk = list_at(cond_blks, i + 1); } + IrBuilder.control_br_fixup(self.ir, branch, body_blk, next_blk); + } else if @enum2int(bk) == @enum2int(IrKind.ControlJmp) { + IrBuilder.control_jmp_fixup(self.ir, branch, body_blk); + } + } + i = i + 1; + } + i = 0; + while i < narms { + var j = list_at(body_jmps, i); + if j != 0 { IrBuilder.control_jmp_fixup(self.ir, j, end_blk); } + i = i + 1; + } + IrBuilder.control_jmp_fixup(self.ir, no_match_jmp, end_blk); + Vec.drop(&arm_branches); + Vec.drop(&cond_blks); + Vec.drop(&body_jmps); + Vec.drop(&body_blks); + ret 0; +} + +fn emit_slice(self: *Ast2Ir, n: *Node, obj_ref: usize, begin_ref: usize, end_ref: usize): usize { + var obj = Ast.node(self.ast, obj_ref); + var obj_lang = node_lang(obj); + var obj_ln = ltn(self, obj_lang); + if obj_ln == null { ret 0; } + var obj_elem = extra_elem(obj_ln.*); + var obj_ir = obj_ln.ir_type_id; + var slice_ir = node_ir(self, n); + if slice_ir == 0 { ret 0; } + var elem_ir = to_ir_type(self, obj_elem); + var usize_lang = LangType.make_int(&self.sema.types, @sizeof(usize) * 8, false); + var usize_ir = to_ir_type(self, usize_lang); + + var base: usize = 0; + if kind_is(self, obj_lang, .Ptr) { + base = emit_expr(self, obj_ref); + } else if kind_is(self, obj_lang, .Slice) { + var slice_val = emit_expr(self, obj_ref); + if slice_val == 0 { ret 0; } + var ptr_ir = TypeTable.make_ptr(&self.sema.types.ir, elem_ir); + base = IrBuilder.agg_extract(self.ir, obj_ir, ptr_ir, 0, slice_val); + } else { + base = emit_lvalue(self, obj_ref); + } + if base == 0 { ret 0; } + + var begin_v: usize = 0; + if begin_ref != 0 { begin_v = emit_expr(self, begin_ref); } + else { begin_v = IrBuilder.type_const_int(self.ir, usize_ir, 0); } + if begin_v == 0 { ret 0; } + var ptr = IrBuilder.mem_offset(self.ir, elem_ir, base, begin_v); + if ptr == 0 { ret 0; } + + var end_v: usize = 0; + if end_ref != 0 { + end_v = emit_expr(self, end_ref); + } else if kind_is(self, obj_lang, .Array) { + var arr = irt(self, obj_ir); + if arr == null { ret 0; } + var arr_len: usize = 0; + match arr.node { .Array(a) => { arr_len = a.len; }, _ => { ret 0; } } + end_v = IrBuilder.type_const_int(self.ir, usize_ir, arr_len); + } else if kind_is(self, obj_lang, .Slice) { + var slice_val = emit_expr(self, obj_ref); + end_v = IrBuilder.agg_extract(self.ir, obj_ir, usize_ir, 1, slice_val); + } else { + ret 0; + } + if end_v == 0 { ret 0; } + var len = IrBuilder.arith_sub(self.ir, usize_ir, end_v, begin_v); + + var node = IrBuilder.agg_construct(self.ir, slice_ir); + if node == 0 { ret 0; } + IrBuilder.agg_construct_field(self.ir, node, ptr); + IrBuilder.agg_construct_field(self.ir, node, len); + ret node; +} + +// ---- if / loop ---- + +fn emit_if_like(self: *Ast2Ir, ref: usize): usize { + var n = Ast.node(self.ast, ref); + if n == null { ret 0; } + var is_ifvar = false; + match n.body { .ExprIfvar(x) => { is_ifvar = true; }, _ => {} } + + var result_t = node_ir(self, n); + var need_result = false; + if result_t != 0 { + var rtn = irt(self, result_t); + if rtn != null && !ir_is_unit(rtn) { need_result = true; } + } + var result_slot: usize = 0; + if need_result { result_slot = IrBuilder.mem_alloca(self.ir, result_t, const_usize(self, 1)); } + + var cond: usize = 0; + var then_ref: usize = 0; + var else_ref: usize = 0; + if is_ifvar { + var vd_ref: usize = 0; + match n.body { .ExprIfvar(x) => { vd_ref = x.var_decl; }, _ => {} } + var vd = Ast.node(self.ast, vd_ref); + if vd == null { ret 0; } + var slot = emit_var_decl(self, vd); + if slot == 0 { ret 0; } + cond = IrBuilder.mem_load(self.ir, node_ir(self, vd), slot); + match n.body { + .ExprIfvar(x) => { then_ref = x.then_block; else_ref = x.else_branch; }, + _ => {}, + } + } else { + match n.body { + .ExprIf(x) => { cond = emit_expr(self, x.cond); then_ref = x.then_block; else_ref = x.else_branch; }, + _ => {}, + } + } + if cond == 0 { ret 0; } + + var br = IrBuilder.control_br(self.ir, cond); + IrBuilder.block_end(self.ir); + + var then_blk = IrBuilder.block_begin(self.ir); + var then_v: usize = 0; + if then_ref != 0 { then_v = emit_expr(self, then_ref); } + if result_slot != 0 && then_v != 0 { + ignore_usize(IrBuilder.mem_store(self.ir, result_t, result_slot, then_v)); + } + var then_end: usize = 0; + if !self.ir.terminated { then_end = IrBuilder.control_jmp(self.ir); } + IrBuilder.block_end(self.ir); + + var else_blk = IrBuilder.block_begin(self.ir); + var else_v: usize = 0; + if else_ref != 0 { else_v = emit_expr(self, else_ref); } + if result_slot != 0 && else_v != 0 { + ignore_usize(IrBuilder.mem_store(self.ir, result_t, result_slot, else_v)); + } + var else_end: usize = 0; + if !self.ir.terminated { else_end = IrBuilder.control_jmp(self.ir); } + IrBuilder.block_end(self.ir); + + var merge_blk = IrBuilder.block_begin(self.ir); + IrBuilder.control_br_fixup(self.ir, br, then_blk, else_blk); + if then_end != 0 { IrBuilder.control_jmp_fixup(self.ir, then_end, merge_blk); } + if else_end != 0 { IrBuilder.control_jmp_fixup(self.ir, else_end, merge_blk); } + + if result_slot == 0 { ret 0; } + ret IrBuilder.mem_load(self.ir, result_t, result_slot); +} + +fn ir_is_unit(e: *TypeEntry): bool { + match e.node { .Unit => { ret true; }, _ => { ret false; } } + ret false; +} + +fn emit_loop_like(self: *Ast2Ir, ref: usize): usize { + var n = Ast.node(self.ast, ref); + if n == null { ret 0; } + var is_while = false; + match n.body { .ExprWhile(x) => { is_while = true; }, _ => {} } + + var result_t = node_ir(self, n); + var need_result = false; + if result_t != 0 { + var rtn = irt(self, result_t); + if rtn != null && !ir_is_unit(rtn) { need_result = true; } + } + var result_slot: usize = 0; + if need_result { result_slot = IrBuilder.mem_alloca(self.ir, result_t, const_usize(self, 1)); } + + var pre_jmp: usize = 0; + if !self.ir.terminated { pre_jmp = IrBuilder.control_jmp(self.ir); } + IrBuilder.block_end(self.ir); + + var head_blk: usize = 0; + var br: usize = 0; + if is_while { + head_blk = IrBuilder.block_begin(self.ir); + var cond: usize = 0; + match n.body { .ExprWhile(x) => { cond = emit_expr(self, x.cond); }, _ => {} } + if cond == 0 { ret 0; } + br = IrBuilder.control_br(self.ir, cond); + IrBuilder.block_end(self.ir); + } + var body_blk = IrBuilder.block_begin(self.ir); + var cont_blk = body_blk; + if is_while { cont_blk = head_blk; } + + var ctx = LoopCtx { + .cont_blk = cont_blk, + .defer_base = self.defer_stack.len, + .label = ByteView.empty(), + .breaks = Vec.blank(@sizeof(usize), @alignof(usize)), + .result_slot = result_slot, + .result_tid = result_t, + }; + var label_ref: usize = 0; + var body_ref: usize = 0; + var else_ref: usize = 0; + if is_while { + match n.body { + .ExprWhile(x) => { label_ref = x.label; body_ref = x.body; else_ref = x.else_branch; }, + _ => {}, + } + } else { + match n.body { + .ExprLoop(x) => { label_ref = x.label; body_ref = x.body; }, + _ => {}, + } + } + if label_ref != 0 { ctx.label = label_name_of(self, label_ref); } + match Vec.push(&self.loop_stack, &ctx as *u8) { .Ok => {}, .Err(e) => {} } + + if body_ref != 0 { ignore_usize(emit_expr(self, body_ref)); } + + var body_end: usize = 0; + if !self.ir.terminated { body_end = IrBuilder.control_jmp(self.ir); } + IrBuilder.block_end(self.ir); + + var after_blk = IrBuilder.block_begin(self.ir); + var else_v: usize = 0; + if is_while && else_ref != 0 { else_v = emit_expr(self, else_ref); } + if result_slot != 0 && else_v != 0 { + ignore_usize(IrBuilder.mem_store(self.ir, result_t, result_slot, else_v)); + } + if pre_jmp != 0 { IrBuilder.control_jmp_fixup(self.ir, pre_jmp, cont_blk); } + if br != 0 { IrBuilder.control_br_fixup(self.ir, br, body_blk, after_blk); } + if body_end != 0 { IrBuilder.control_jmp_fixup(self.ir, body_end, cont_blk); } + + var top = loop_at(self, self.loop_stack.len - 1); + if top != null { + var i: usize = 0; + while i < top.breaks.len { + IrBuilder.control_jmp_fixup(self.ir, list_at(top.breaks, i), after_blk); + i = i + 1; + } + Vec.drop(&top.breaks); + } + pop_loop(self); + + if result_slot == 0 { ret 0; } + ret IrBuilder.mem_load(self.ir, result_t, result_slot); +} + +fn loop_at(self: *Ast2Ir, i: usize): *LoopCtx { + match Vec.get(&self.loop_stack, i) { .Ok(p) => { ret p as *LoopCtx; }, .Err(e) => { ret null; } } + ret null; +} + +fn pop_loop(self: *Ast2Ir) { + var out: LoopCtx = LoopCtx { + .cont_blk = 0, .defer_base = 0, .label = ByteView.empty(), + .breaks = Vec.blank(@sizeof(usize), @alignof(usize)), .result_slot = 0, .result_tid = 0, + }; + match Vec.pop(&self.loop_stack, &out as *u8) { .Ok => {}, .Err(e) => {} } +} + +fn label_name_of(self: *Ast2Ir, ref: usize): ByteView { + if ref == 0 { ret ByteView.empty(); } + var ln = Ast.node(self.ast, ref); + if ln == null { ret ByteView.empty(); } + match ln.body { .Label(l) => { ret l.name; }, _ => { ret ByteView.empty(); } } + ret ByteView.empty(); +} + +fn find_loop_with_label(self: *Ast2Ir, lbl: ByteView): usize { + var i = self.loop_stack.len; + while i > 0 { + var c = loop_at(self, i - 1); + if c != null && c.label.len != 0 && ByteView.eq(c.label, lbl) { ret i - 1; } + i = i - 1; + } + ret ~(0 as usize); +} + +fn emit_break_stmt(self: *Ast2Ir, n: *Node): usize { + if self.loop_stack.len == 0 { ret 0; } + var lbl = ByteView.empty(); + var expr_ref: usize = 0; + match n.body { + .StatementBreak(x) => { lbl = label_name_of(self, x.label); expr_ref = x.expr; }, + _ => {}, + } + var target_index = self.loop_stack.len - 1; + if lbl.len != 0 { target_index = find_loop_with_label(self, lbl); } + if target_index == ~(0 as usize) { ret 0; } + + var target = loop_at(self, target_index); + if target == null { ret 0; } + if expr_ref != 0 && target.result_slot != 0 { + var bv = emit_expr(self, expr_ref); + if bv != 0 { + ignore_usize(IrBuilder.mem_store(self.ir, target.result_tid, target.result_slot, bv)); + } + } + emit_deferred_until(self, target.defer_base); + if self.ir.terminated { ret 0; } + var jmp = IrBuilder.control_jmp(self.ir); + var t2 = loop_at(self, target_index); + if t2 != null { push_u(&t2.breaks, jmp); } + ret 0; +} + +// ---- 语句 / 声明 ---- + +fn emit_stmt(self: *Ast2Ir, ref: usize) { + var n = Ast.node(self.ast, ref); + if n == null { ret; } + match n.body { + .StatementRet(x) => { + var ret_ir = to_ir_type(self, self.cur_ret_lang); + var v: usize = 0; + if x.expr != 0 { v = emit_expr(self, x.expr); } + emit_deferred_until(self, self.cur_defer_base); + if !self.ir.terminated { ignore_usize(IrBuilder.control_ret(self.ir, ret_ir, v)); } + }, + .StatementDefer(x) => { if x.body != 0 { push_u(&self.defer_stack, x.body); } }, + .StatementComptime(x) => { if x.body != 0 { ignore_usize(emit_expr(self, x.body)); } }, + .StatementDecl(x) => { if x.decl != 0 { emit_decl(self, x.decl); } }, + .StatementExpr(x) => { if x.expr != 0 { ignore_usize(emit_expr(self, x.expr)); } }, + .ExprIf(x) => { ignore_usize(emit_if_like(self, ref)); }, + .ExprIfvar(x) => { ignore_usize(emit_if_like(self, ref)); }, + .ExprWhile(x) => { ignore_usize(emit_loop_like(self, ref)); }, + .ExprLoop(x) => { ignore_usize(emit_loop_like(self, ref)); }, + .StatementBreak(x) => { ignore_usize(emit_break_stmt(self, n)); }, + .StatementContinue(x) => { emit_continue_stmt(self, n); }, + .ExprBlock(x) => { ignore_usize(emit_expr(self, ref)); }, + _ => { ignore_usize(emit_expr(self, ref)); }, + } +} + +fn emit_continue_stmt(self: *Ast2Ir, n: *Node) { + if self.loop_stack.len == 0 { ret; } + var lbl = ByteView.empty(); + match n.body { .StatementContinue(x) => { lbl = label_name_of(self, x.label); }, _ => {} } + var target_index = self.loop_stack.len - 1; + if lbl.len != 0 { target_index = find_loop_with_label(self, lbl); } + if target_index == ~(0 as usize) { ret; } + var target = loop_at(self, target_index); + if target == null { ret; } + var cont_blk = target.cont_blk; + var defer_base = target.defer_base; + emit_deferred_until(self, defer_base); + if !self.ir.terminated { + var jmp = IrBuilder.control_jmp(self.ir); + IrBuilder.control_jmp_fixup(self.ir, jmp, cont_blk); + } +} + +fn emit_decl(self: *Ast2Ir, ref: usize) { + var n = Ast.node(self.ast, ref); + if n == null { ret; } + match n.body { + .VarDecl(x) => { if self.ir.current_fn != 0 { ignore_usize(emit_var_decl(self, n)); } }, + .ConstDecl(x) => { if self.ir.current_fn != 0 { ignore_usize(emit_var_decl(self, n)); } }, + _ => {}, + } +} + +fn emit_fn_reserve(self: *Ast2Ir, n: *Node): usize { + if n == null || n.resolved_def_id == 0 { ret 0; } + var existing: usize = 0; + if ir_map_get(self, n.resolved_def_id, &existing) { + diag_name(self, n.span, .Internal, "fn reserved twice `", fn_name_of(n)); + ret 0; + } + var fn_lang = LangType.def_lang_id(&self.sema.types, n.resolved_def_id); + var fn_ln = ltn(self, fn_lang); + if fn_ln == null || !kind_is(self, fn_lang, .Fn) { ret 0; } + var fn_ir = fn_ln.ir_type_id; + if fn_ir == 0 { ret 0; } + var fn_ref = IrBuilder.fn_reserve(self.ir, fn_name_of(n), fn_ir); + ir_map_put(self, n.resolved_def_id, fn_ref); + copy_attrs_func(self, n, fn_ref); + ret fn_ref; +} + +fn fn_name_of(n: *Node): ByteView { + match n.body { .FnDecl(x) => { ret x.name; }, _ => { ret ByteView.empty(); } } + ret ByteView.empty(); +} + +fn diag_name(self: *Ast2Ir, span: Span, code: Code, prefix: []u8, name: ByteView) { + var b = ByteBuffer.new(); + defer { ByteBuffer.drop(&b); } + match ByteBuffer.append(&b, prefix) { .Ok => {}, .Err(e) => {} } + match ByteBuffer.append(&b, ByteView.as_slice(name)) { .Ok => {}, .Err(e) => {} } + Diag.err(self.diag, span, code, ByteBuffer.as_slice(b)); +} + +fn emit_fn_body(self: *Ast2Ir, n: *Node) { + if n == null || n.resolved_def_id == 0 { ret; } + var fn_ref: usize = 0; + if !ir_map_get(self, n.resolved_def_id, &fn_ref) { ret; } + var fn_lang = LangType.def_lang_id(&self.sema.types, n.resolved_def_id); + var fn_ln = ltn(self, fn_lang); + if fn_ln == null || !kind_is(self, fn_lang, .Fn) { ret; } + var ret_lang = fn_ret(fn_ln.*); + var ret_ir = to_ir_type(self, ret_lang); + var ret_is_unit = kind_is(self, ret_lang, .Unit); + + IrBuilder.fn_begin_at(self.ir, fn_ref); + + var saved_ret = self.cur_ret_lang; + self.cur_ret_lang = ret_lang; + var saved_defer_base = self.cur_defer_base; + self.cur_defer_base = self.defer_stack.len; + var defer_base = self.cur_defer_base; + var loop_base = self.loop_stack.len; + + ignore_usize(IrBuilder.block_begin(self.ir)); + + var params = Vec.blank(@sizeof(usize), @alignof(usize)); + var body_ref: usize = 0; + match n.body { .FnDecl(x) => { params = x.params; body_ref = x.body; }, _ => {} } + var i: usize = 0; + while i < params.len { + var pref = list_at(params, i); + var pd = Ast.node(self.ast, pref); + if pd != null && pd.resolved_def_id != 0 { + var p_lang = LangType.def_lang_id(&self.sema.types, pd.resolved_def_id); + var p_ir = to_ir_type(self, p_lang); + if p_ir != 0 { + var idx = const_usize(self, i); + var pv = IrBuilder.control_param(self.ir, p_ir, idx); + var one = const_usize(self, 1); + var slot = IrBuilder.mem_alloca(self.ir, p_ir, one); + ignore_usize(IrBuilder.mem_store(self.ir, p_ir, slot, pv)); + ir_map_put(self, pd.resolved_def_id, slot); + } + } + i = i + 1; + } + + if body_ref != 0 { ignore_usize(emit_expr(self, body_ref)); } + if !self.ir.terminated { emit_deferred_until(self, defer_base); } + if !self.ir.terminated { + if ret_is_unit { + ignore_usize(IrBuilder.control_ret(self.ir, ret_ir, 0)); + } else if ret_ir != 0 { + var zero = emit_zero_value(self, ret_ir); + ignore_usize(IrBuilder.control_ret(self.ir, ret_ir, zero)); + } else { + ignore_usize(IrBuilder.control_unreachable(self.ir)); + } + } + + self.defer_stack.len = defer_base; + while self.loop_stack.len > loop_base { + var top = loop_at(self, self.loop_stack.len - 1); + if top != null { Vec.drop(&top.breaks); } + pop_loop(self); + } + self.cur_ret_lang = saved_ret; + self.cur_defer_base = saved_defer_base; + + IrBuilder.block_end(self.ir); + IrBuilder.fn_end(self.ir); +} + +fn fn_ret(e: LangEntry): usize { + match e.extra { .Fn(f) => { ret f.ret_type; }, _ => { ret 0; } } + ret 0; +} + +fn emit_aggregate_fns_reserve(self: *Ast2Ir, te: *Node) { + var members = Vec.blank(@sizeof(usize), @alignof(usize)); + match te.body { .TypeAggregate(x) => { members = x.members; }, _ => { ret; } } + var i: usize = 0; + while i < members.len { + var mn = Ast.node(self.ast, list_at(members, i)); + if mn != null { + match mn.body { + .FnDecl(x) => { ignore_usize(emit_fn_reserve(self, mn)); }, + .MemberDecl(md) => { + if md.type_expr != 0 { + var mt = Ast.node(self.ast, md.type_expr); + if mt != null && is_type_aggregate(mt) { emit_aggregate_fns_reserve(self, mt); } + } + }, + _ => {}, + } + } + i = i + 1; + } +} + +fn emit_aggregate_fns_body(self: *Ast2Ir, te: *Node) { + var members = Vec.blank(@sizeof(usize), @alignof(usize)); + match te.body { .TypeAggregate(x) => { members = x.members; }, _ => { ret; } } + var i: usize = 0; + while i < members.len { + var mn = Ast.node(self.ast, list_at(members, i)); + if mn != null { + match mn.body { + .FnDecl(x) => { emit_fn_body(self, mn); }, + .MemberDecl(md) => { + if md.type_expr != 0 { + var mt = Ast.node(self.ast, md.type_expr); + if mt != null && is_type_aggregate(mt) { emit_aggregate_fns_body(self, mt); } + } + }, + _ => {}, + } + } + i = i + 1; + } +} + +fn is_type_aggregate(n: *Node): bool { + match n.body { .TypeAggregate(x) => { ret true; }, _ => { ret false; } } + ret false; +} + +fn emit_container_reserve(self: *Ast2Ir, ref: usize) { + var n = Ast.node(self.ast, ref); + if n == null { ret; } + var is_tlb = false; + var tlb_block: usize = 0; + match n.body { .TopLevelBlock(x) => { is_tlb = true; tlb_block = x.block; }, _ => {} } + if is_tlb { + if tlb_block != 0 { emit_container_reserve(self, tlb_block); } + ret; + } + var decls = Vec.blank(@sizeof(usize), @alignof(usize)); + var is_cm = false; + match n.body { .ContainerMembers(x) => { is_cm = true; decls = x.declarations; }, _ => {} } + if !is_cm { ret; } + var i: usize = 0; + while i < decls.len { + var dref = list_at(decls, i); + var d = Ast.node(self.ast, dref); + if d != null { + match d.body { + .FnDecl(x) => { ignore_usize(emit_fn_reserve(self, d)); }, + .TypeDecl(td) => { + var tpat = Ast.node(self.ast, td.pattern); + if tpat != null && is_pattern_ident(tpat) { + var te = Ast.node(self.ast, td.type_expr); + if te != null && is_type_aggregate(te) { emit_aggregate_fns_reserve(self, te); } + } + }, + .TopLevelBlock(x) => { emit_container_reserve(self, dref); }, + _ => {}, + } + } + i = i + 1; + } +} + +fn emit_container_body(self: *Ast2Ir, ref: usize) { + var n = Ast.node(self.ast, ref); + if n == null { ret; } + var is_tlb = false; + var tlb_block: usize = 0; + match n.body { .TopLevelBlock(x) => { is_tlb = true; tlb_block = x.block; }, _ => {} } + if is_tlb { + if tlb_block != 0 { emit_container_body(self, tlb_block); } + ret; + } + var decls = Vec.blank(@sizeof(usize), @alignof(usize)); + var is_cm = false; + match n.body { .ContainerMembers(x) => { is_cm = true; decls = x.declarations; }, _ => {} } + if !is_cm { ret; } + var i: usize = 0; + while i < decls.len { + var dref = list_at(decls, i); + var d = Ast.node(self.ast, dref); + if d != null { + match d.body { + .FnDecl(x) => { emit_fn_body(self, d); }, + .TypeDecl(td) => { + var tpat = Ast.node(self.ast, td.pattern); + if tpat != null && is_pattern_ident(tpat) { + var te = Ast.node(self.ast, td.type_expr); + if te != null && is_type_aggregate(te) { emit_aggregate_fns_body(self, te); } + } + }, + .TopLevelBlock(x) => { emit_container_body(self, dref); }, + _ => {}, + } + } + i = i + 1; + } +} + +fn is_pattern_ident(n: *Node): bool { + match n.body { .PatternIdent(x) => { ret true; }, _ => { ret false; } } + ret false; +} + +fn emit_implicit_cast(self: *Ast2Ir, n: *Node, ft: usize, tt: usize, v: usize): usize { + if ft == 0 || tt == 0 || ft == tt { ret v; } + var fs = irt(self, ft); + var ts = irt(self, tt); + if fs == null || ts == null { ret v; } + var f_int = false; + var t_int = false; + var f_bits: usize = 0; + var t_bits: usize = 0; + var f_signed = false; + var f_flt = false; + var t_flt = false; + var f_bool = false; + var f_ptr = false; + var t_ptr = false; + var f_fn = false; + var t_fn = false; + match fs.node { + .Int(x) => { f_int = true; f_bits = x.bits; f_signed = x.is_signed; }, + .Float(x) => { f_flt = true; f_bits = x.bits; }, + .Bool => { f_bool = true; }, + .Ptr(x) => { f_ptr = true; }, + .Fn(x) => { f_fn = true; }, + _ => {}, + } + match ts.node { + .Int(x) => { t_int = true; t_bits = x.bits; }, + .Float(x) => { t_flt = true; t_bits = x.bits; }, + .Ptr(x) => { t_ptr = true; }, + .Fn(x) => { t_fn = true; }, + _ => {}, + } + if f_int && t_int { + if f_bits == t_bits { ret IrBuilder.cast_bitcast(self.ir, ft, tt, v); } + if f_bits > t_bits { ret IrBuilder.cast_trunc(self.ir, ft, tt, v); } + if f_signed { ret IrBuilder.cast_sext(self.ir, ft, tt, v); } + ret IrBuilder.cast_zext(self.ir, ft, tt, v); + } + if f_flt && t_flt { + if f_bits > t_bits { ret IrBuilder.cast_ftrunc(self.ir, ft, tt, v); } + ret IrBuilder.cast_fext(self.ir, ft, tt, v); + } + if f_bool && t_int { ret IrBuilder.cast_bool2int(self.ir, ft, tt, v); } + var f_ptr_like = f_ptr || f_fn; + var t_ptr_like = t_ptr || t_fn; + if f_ptr_like && t_ptr_like { ret v; } + if f_ptr && t_int { ret IrBuilder.cast_ptr2int(self.ir, ft, tt, v); } + if f_int && t_ptr { ret IrBuilder.cast_int2ptr(self.ir, ft, tt, v); } + Diag.err(self.diag, n.span, .ImplicitCastFail, "implicit cast: cannot convert"); + ret 0; +} + +// ---- 驱动 ---- + +fn new(builder: *IrBuilder, sema: *Sema, diag: *Diag): Ast2Ir { + ret Ast2Ir { + .sema = sema, + .ir = builder, + .ast = sema.ast, + .def2ir_map = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize), + hash_usize, eq_usize), + .loop_stack = Vec.blank(@sizeof(LoopCtx), @alignof(LoopCtx)), + .defer_stack = Vec.blank(@sizeof(usize), @alignof(usize)), + .cur_ret_lang = 0, + .cur_defer_base = 0, + .diag = diag, + .owned = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)), + }; +} + +fn drop(self: *Ast2Ir) { + var i: usize = 0; + while i < self.loop_stack.len { + var c = loop_at(self, i); + if c != null { Vec.drop(&c.breaks); } + i = i + 1; + } + Vec.drop(&self.loop_stack); + Vec.drop(&self.defer_stack); + Map.drop(&self.def2ir_map); + var k: usize = 0; + while k < self.owned.len { + match Vec.get(&self.owned, k) { + .Ok(ptr) => { ByteBuffer.drop(ptr as *ByteBuffer); }, + .Err(e) => {}, + } + k = k + 1; + } + Vec.drop(&self.owned); +} + +fn run(self: *Ast2Ir, asts: Vec) { + var n = asts.len; + var i: usize = 0; + while i < n { + var ast = list_ptr(asts, i); + if ast != null && ast.root != 0 { + self.ast = ast; + var root = Ast.node(ast, ast.root); + if root != null { + var members: usize = 0; + match root.body { .Root(r) => { members = r.members; }, _ => {} } + if members != 0 { emit_container_reserve(self, members); } + } + } + i = i + 1; + } + i = 0; + while i < n { + var ast = list_ptr(asts, i); + if ast != null && ast.root != 0 { + self.ast = ast; + var root = Ast.node(ast, ast.root); + if root != null { + var members: usize = 0; + match root.body { .Root(r) => { members = r.members; }, _ => {} } + if members != 0 { emit_container_body(self, members); } + } + } + i = i + 1; + } +} + +fn list_ptr(v: Vec, i: usize): *Ast { + match Vec.get(&v, i) { .Ok(p) => { ret (p as **Ast).*; }, .Err(e) => { ret null; } } + ret null; +} + +// ---- 测试 ---- + +fn main() {} diff --git a/stage2/attr.spl b/stage2/attr.spl new file mode 100644 index 0000000..57bb1c2 --- /dev/null +++ b/stage2/attr.spl @@ -0,0 +1,426 @@ +// Expect retcode = 0 + +// 属性注册表与属性节点(移植自 stage1/spl_attr.c)。注册表是数据:内置属性 +// 在 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, ®_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; } + match Vec.get(&self.table, id) { + .Ok(ptr) => { ret ptr as *AttrNode; }, + .Err(error) => { ret null; }, + } + ret null; +} + +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) { + match Vec.push(&self.registry, &info as *u8) { + .Ok => {}, + .Err(error) => {}, + } +} + +// 返回注册表下标(>=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)); +} + +// ---- dump(镜像 stage1 spl_attr_dump / spl_attr_dump_id)---- + +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 == 7, "1 sentinel + 6 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(); +} diff --git a/stage2/def.spl b/stage2/def.spl new file mode 100644 index 0000000..435aa44 --- /dev/null +++ b/stage2/def.spl @@ -0,0 +1,316 @@ +// Expect retcode = 0 + +// 声明句柄表(移植自 stage1/spl_lang_type.h 的 spl_def_node_t 部分)。 +// DefId 是「声明」的稳定身份:作用域符号、AST 节点 resolved_def_id、以及将来 +// comptime 生成的声明都用它。前向引用/环形引用由 state 状态机表达。 +// +// 与 stage1 的差异:owner_ast 改为不透明 `*any`(借用),使本表不依赖具体 AST +// 表示,便于将来 comptime/序列化复用;sema 侧自行 cast 回 *Ast。 +// 名称是 ByteView(借用源/AST),本表不做拥有。 + +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(对应 stage1 spl_def_node)。 +fn node(self: *DefTable, id: usize): *DefNode { + if id == 0 || id >= self.table.len { ret null; } + match Vec.get(&self.table, id) { + .Ok(ptr) => { ret ptr as *DefNode; }, + .Err(error) => { ret null; }, + } + ret null; +} + +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(对应 stage1 sema_def_is_type)。 +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; +} + +// ---- dump(镜像 stage1 spl_type_def_dump 的文本)---- + +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(); +} diff --git a/stage2/eval.spl b/stage2/eval.spl new file mode 100644 index 0000000..2459122 --- /dev/null +++ b/stage2/eval.spl @@ -0,0 +1,139 @@ +// Expect retcode = 0 + +// comptime / const 求值的**接口 seam**(本轮只占位,不实现求值)。 +// +// 目标:把「编译期求值」从 sema 主体里隔离出来,将来接一个解释器(可直接复用 +// VM 或 AST 求值器)时不动 sema 结构。sema 只在 comptime 语句 / const 初始化 / +// @import 等点调用这里;未实现时统一返回 NotConst(= 放弃折叠,不报错)。 +// +// 分块编译相关:eval 未来的产物是「编译期生成的声明」,统一经 DeclSink 登记, +// 由驱动在固定点循环里插入并触发重跑;eval 自己不直接改 sema 表,避免重入。 + +type .{ .Error } = @import("utils/error.spl"); +type .{ .Vec } = @import("utils/vec.spl"); +type Ast = @import("./ast.spl"); +type Diag = @import("./diag.spl"); + +// 编译期值:占位。未来扩展为 BigInt / 聚合 / 类型句柄等。 +type EvalValue = enum { + Int: usize, // TODO: BigInt + 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 = 完全不求值(等价旧行为) + +// ---- DeclSink ---- + +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; +} + +// ---- Eval ---- + +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 块(可能产生声明,登记到 sink)。本轮不做。 +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(); +} diff --git a/stage2/intrin.spl b/stage2/intrin.spl new file mode 100644 index 0000000..cc696b1 --- /dev/null +++ b/stage2/intrin.spl @@ -0,0 +1,331 @@ +// Expect retcode = 0 + +// 内建(intrinsic)注册表(移植自 stage1/spl_intrin.c)。表是数据:按 kind 查 +// 描述信息,按名字/字段/`as` 转换规则反查 kind。新增内建只改 info_at, +// 不改 sema 逻辑(对应 SPL.md 的 @/# 扩展点)。 + +type .{ .ByteView } = @import("utils/byte_view.spl"); +type TypeTable = @import("./type_table.spl"); +type .{ .TypeNode, .TypeEntry } = @import("./type_table.spl"); +type .{ .LangKind } = @import("./lang_type.spl"); + +// 文件命名空间(供跨模块用 IntrinOps.foo 调用)。 +type IntrinOps = @self; + +type Intrin = enum { + None, + BitSizeof, + Sizeof, + Alignof, + Assert, + Breakpoint, + Len, + IntCast, + Truncate, + BitCast, + FloatCast, + Float2Int, + Int2Float, + Bool2Int, + Ptr2Int, + Int2Ptr, + PtrCast, + Enum2Int, + OffsetOf, + FieldCount, +} + +type IntrinForm = enum { + Call, + Field, +} + +type IntrinResult = enum { + Unit, + Bool, + Usize, +} + +type IntrinInfo = struct { + name: ByteView, + kind: Intrin, + form: IntrinForm, + min_args: i32, + max_args: i32, // -1 = variadic + result: IntrinResult, +} + +fn form_is(a: IntrinForm, b: IntrinForm): bool { + ret @enum2int(a) == @enum2int(b); +} + +fn kind_is(a: LangKind, b: LangKind): bool { + ret @enum2int(a) == @enum2int(b); +} + +// 与 stage1 表逐项对应(枚举顺序即 kind 数值)。 +fn info_at(index: usize): IntrinInfo { + match index { + 1 => { + ret IntrinInfo { .name = ByteView.from_slice("@bitsizeof"), .kind = .BitSizeof, + .form = .Call, .min_args = 1, .max_args = 1, .result = .Usize }; + }, + 2 => { + ret IntrinInfo { .name = ByteView.from_slice("@sizeof"), .kind = .Sizeof, + .form = .Call, .min_args = 1, .max_args = 1, .result = .Usize }; + }, + 3 => { + ret IntrinInfo { .name = ByteView.from_slice("@alignof"), .kind = .Alignof, + .form = .Call, .min_args = 1, .max_args = 1, .result = .Usize }; + }, + 4 => { + ret IntrinInfo { .name = ByteView.from_slice("@assert"), .kind = .Assert, + .form = .Call, .min_args = 1, .max_args = 2, .result = .Unit }; + }, + 5 => { + ret IntrinInfo { .name = ByteView.from_slice("@breakpoint"), .kind = .Breakpoint, + .form = .Call, .min_args = 0, .max_args = 0, .result = .Unit }; + }, + 6 => { + ret IntrinInfo { .name = ByteView.from_slice("len"), .kind = .Len, + .form = .Field, .min_args = 0, .max_args = 0, .result = .Usize }; + }, + 7 => { + ret IntrinInfo { .name = ByteView.from_slice("@int_cast"), .kind = .IntCast, + .form = .Call, .min_args = 2, .max_args = 2, .result = .Unit }; + }, + 8 => { + ret IntrinInfo { .name = ByteView.from_slice("@truncate"), .kind = .Truncate, + .form = .Call, .min_args = 2, .max_args = 2, .result = .Unit }; + }, + 9 => { + ret IntrinInfo { .name = ByteView.from_slice("@bit_cast"), .kind = .BitCast, + .form = .Call, .min_args = 2, .max_args = 2, .result = .Unit }; + }, + 10 => { + ret IntrinInfo { .name = ByteView.from_slice("@float_cast"), .kind = .FloatCast, + .form = .Call, .min_args = 2, .max_args = 2, .result = .Unit }; + }, + 11 => { + ret IntrinInfo { .name = ByteView.from_slice("@float2int"), .kind = .Float2Int, + .form = .Call, .min_args = 2, .max_args = 2, .result = .Unit }; + }, + 12 => { + ret IntrinInfo { .name = ByteView.from_slice("@int2float"), .kind = .Int2Float, + .form = .Call, .min_args = 2, .max_args = 2, .result = .Unit }; + }, + 13 => { + ret IntrinInfo { .name = ByteView.from_slice("@bool2int"), .kind = .Bool2Int, + .form = .Call, .min_args = 1, .max_args = 2, .result = .Unit }; + }, + 14 => { + ret IntrinInfo { .name = ByteView.from_slice("@ptr2int"), .kind = .Ptr2Int, + .form = .Call, .min_args = 1, .max_args = 2, .result = .Unit }; + }, + 15 => { + ret IntrinInfo { .name = ByteView.from_slice("@int2ptr"), .kind = .Int2Ptr, + .form = .Call, .min_args = 2, .max_args = 2, .result = .Unit }; + }, + 16 => { + ret IntrinInfo { .name = ByteView.from_slice("@ptr_cast"), .kind = .PtrCast, + .form = .Call, .min_args = 2, .max_args = 2, .result = .Unit }; + }, + 17 => { + ret IntrinInfo { .name = ByteView.from_slice("@enum2int"), .kind = .Enum2Int, + .form = .Call, .min_args = 1, .max_args = 1, .result = .Usize }; + }, + 18 => { + ret IntrinInfo { .name = ByteView.from_slice("@offsetof"), .kind = .OffsetOf, + .form = .Call, .min_args = 2, .max_args = 2, .result = .Usize }; + }, + 19 => { + ret IntrinInfo { .name = ByteView.from_slice("@field_count"), .kind = .FieldCount, + .form = .Call, .min_args = 1, .max_args = 1, .result = .Usize }; + }, + _ => { + ret IntrinInfo { .name = ByteView.empty(), .kind = .None, .form = .Call, + .min_args = 0, .max_args = 0, .result = .Unit }; + }, + } + ret IntrinInfo { .name = ByteView.empty(), .kind = .None, .form = .Call, + .min_args = 0, .max_args = 0, .result = .Unit }; +} + +fn info(kind: Intrin): IntrinInfo { + ret info_at(@enum2int(kind) as usize); +} + +fn table_count(): usize { + ret 20; +} + +fn strip_at(name: ByteView): ByteView { + if name.len > 0 && name.ptr[0] == '@' { + match ByteView.slice(name, 1, name.len) { + .Ok(rest) => { ret rest; }, + .Err(error) => { ret name; }, + } + } + 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(); +} diff --git a/stage2/ir.spl b/stage2/ir.spl new file mode 100644 index 0000000..cfc4c68 --- /dev/null +++ b/stage2/ir.spl @@ -0,0 +1,605 @@ +// Expect retcode = 0 + +// SIR 中间表示(移植自 stage1/spl_ir.c/.h)。节点用 arena 序号引用(0 = error), +// 按函数分桶。节点公共字段(kind/dbg/attrs)在外层 entry,kind 专属载荷在 ADT +// IrBody;kind 与载荷变体一一对应(多种 kind 可共享同一载荷形状)。 +// +// builder 维护当前函数/基本块/fixup 表,负责发射节点与回填跳转。 + +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, +} + +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 + +// ---- 生命周期 ---- + +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)) }; + 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(); + match Vec.push(&sentinel.nodes, &sn as *u8) { .Ok => {}, .Err(error) => {} } + match Vec.push(&ir.funcs, &sentinel as *u8) { .Ok => {}, .Err(error) => {} } + 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 { + match Vec.get(&self.funcs, i) { + .Ok(ptr) => { + var f = ptr as *IrFunc; + var j: usize = 0; + while j < f.nodes.len { + match Vec.get(&f.nodes, j) { + .Ok(np) => { + var e = np as *IrEntry; + Vec.drop(&e.attrs); + drop_body(e.body); + }, + .Err(error) => {}, + } + j = j + 1; + } + Vec.drop(&f.attrs); + Vec.drop(&f.nodes); + }, + .Err(error) => {}, + } + i = i + 1; + } + Vec.drop(&self.funcs); +} + +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; } + match Vec.get(&self.funcs, fn_id) { + .Ok(ptr) => { + var f = ptr as *IrFunc; + var n = new_entry(); + match Vec.push(&f.nodes, &n as *u8) { .Ok => {}, .Err(error) => { ret 0; } } + ret f.nodes.len - 1; + }, + .Err(error) => {}, + } + ret 0; +} + +fn func(self: *Ir, fn_id: usize): *IrFunc { + if fn_id == 0 || fn_id >= self.funcs.len { ret null; } + match Vec.get(&self.funcs, fn_id) { + .Ok(ptr) => { ret ptr as *IrFunc; }, + .Err(error) => { ret null; }, + } + ret null; +} + +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; } + match Vec.get(&f.nodes, node_id) { + .Ok(ptr) => { ret ptr as *IrEntry; }, + .Err(error) => { ret null; }, + } + ret null; +} + +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 "?"; +} + +// ---- dump ---- + +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 { + match Vec.get(&v, i) { .Ok(p) => { ret (p as *usize).*; }, .Err(e) => { ret 0; } } + ret 0; +} + +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(镜像 stage1)。 + if @enum2int(e.kind) == @enum2int(IrKind.ArithNeg) || + @enum2int(e.kind) == @enum2int(IrKind.ArithAbs) || + @enum2int(e.kind) == @enum2int(IrKind.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; } + 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, "'"); + } + // stage1 对其他常量统一按 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(); +} diff --git a/stage2/ir2vm.spl b/stage2/ir2vm.spl new file mode 100644 index 0000000..fb309d5 --- /dev/null +++ b/stage2/ir2vm.spl @@ -0,0 +1,1368 @@ +// Expect retcode = 0 + +// IR -> VM 机器码(移植自 stage1/spl_ir2vm.c)。 +// 依赖:IR(ir.spl)、IR 类型表(type_table.spl)、布局(layout.spl)、 +// 属性(attr.spl)、字节码容器(mcode.spl)。 +// +// extern_native 用「按函数 ref 下标的 isize 数组」代替 stage1 的 map: +// 默认 -1 表示非 extern,>=0 表示 native 索引(语义等价)。 + +type .{ .Vec } = @import("utils/vec.spl"); +type .{ .ByteView } = @import("utils/byte_view.spl"); +type .{ .ByteBuffer } = @import("utils/byte_buffer.spl"); +type TypeTable = @import("./type_table.spl"); +type LayoutM = @import("./layout.spl"); +type .{ .Layout } = @import("./layout.spl"); +type AttrM = @import("./attr.spl"); +type .{ .Attr, .AttrArg } = @import("./attr.spl"); +type IrM = @import("./ir.spl"); +type .{ .Ir, .IrFunc, .IrEntry } = @import("./ir.spl"); +type Mcode = @import("./mcode.spl"); +type .{ .Prog, .Op, .VmKind, .VmIns, .VmFunc } = @import("./mcode.spl"); +type Mem = @import("sys/mem.spl"); + +type Ir2Vm = @self; + +ir: *Ir, +ty: *TypeTable, +attr: *Attr, +layout: *Layout, +extern_native: Vec, // isize +prog: Prog, + +// ---- 类型/布局查询 ---- + +fn info_of(ctx: *Ir2Vm, tid: usize): *LayoutM.LayoutInfo { + ret LayoutM.info_of(ctx.layout, tid); +} + +fn ir_type_size(ctx: *Ir2Vm, tid: usize): usize { + var info = info_of(ctx, tid); + if info == null { ret 0; } + ret (info.size_bits + 7) / 8; +} + +fn ir_type_align(ctx: *Ir2Vm, tid: usize): usize { + var info = info_of(ctx, tid); + if info == null { ret 1; } + var a = info.align_bits / 8; + if a == 0 { a = 1; } + ret a; +} + +fn ir_is_aggregate(ctx: *Ir2Vm, tid: usize): bool { + var te = TypeTable.node(ctx.ty, tid); + if te == null { ret false; } + match te.node { + .Array => { ret true; }, + .Struct => { ret true; }, + .Union => { ret true; }, + .Enum => { ret true; }, + .Shape => { ret true; }, + _ => { ret false; }, + } + ret false; +} + +fn fn_ret_type(ctx: *Ir2Vm, fn_tid: usize): usize { + var te = TypeTable.node(ctx.ty, fn_tid); + if te == null { ret 0; } + match te.node { + .Fn(f) => { ret f.ret_type; }, + _ => { ret 0; }, + } + ret 0; +} + +fn fn_nargs(ctx: *Ir2Vm, fn_tid: usize): usize { + var te = TypeTable.node(ctx.ty, fn_tid); + if te == null { ret 0; } + match te.node { + .Fn(f) => { ret f.params.len; }, + _ => { ret 0; }, + } + ret 0; +} + +fn ir_agg_field_tid(ctx: *Ir2Vm, tid: usize, idx: usize): usize { + var te = TypeTable.node(ctx.ty, tid); + if te == null { ret 0; } + match te.node { + .Enum(agg) => { + if idx == 0 { ret agg.tag_type; } + var vidx = idx - 1; + if vidx < agg.variants.len { + var m = TypeTable.member_at(&agg.variants, vidx); + if m != null { ret m.type_id; } + } + ret 0; + }, + .Struct(agg) => { + if idx < agg.variants.len { + var m = TypeTable.member_at(&agg.variants, idx); + if m != null { ret m.type_id; } + } + ret 0; + }, + .Union(agg) => { + if idx < agg.variants.len { + var m = TypeTable.member_at(&agg.variants, idx); + if m != null { ret m.type_id; } + } + ret 0; + }, + .Array(a) => { ret a.element; }, + _ => { ret 0; }, + } + ret 0; +} + +fn ir_agg_field_off(ctx: *Ir2Vm, tid: usize, idx: usize): usize { + var te = TypeTable.node(ctx.ty, tid); + if te == null { ret 0; } + match te.node { + .Array(a) => { ret idx * ir_type_size(ctx, a.element); }, + _ => {}, + } + var info = info_of(ctx, tid); + if info == null || idx >= info.field_bit_offsets.len { ret 0; } + ret usize_at(&info.field_bit_offsets, idx) / 8; +} + +fn vm_kind_of(ctx: *Ir2Vm, tid: usize): VmKind { + var te = TypeTable.node(ctx.ty, tid); + if te == null { ret .Void; } + match te.node { + .Unit => { ret .Void; }, + .Bool => { ret .Bool; }, + .Int(x) => { + if x.bits == 0 { ret .I32; } + if x.bits == 8 { if x.is_signed { ret .I8; } ret .U8; } + if x.bits == 16 { if x.is_signed { ret .I16; } ret .U16; } + if x.bits == 32 { if x.is_signed { ret .I32; } ret .U32; } + if x.is_signed { ret .I64; } + ret .U64; + }, + .Float(x) => { if x.bits == 32 { ret .F32; } ret .F64; }, + _ => { ret .Ptr; }, + } + ret .Ptr; +} + +fn tid_is_signed(ctx: *Ir2Vm, tid: usize): bool { + var te = TypeTable.node(ctx.ty, tid); + if te == null { ret false; } + match te.node { + .Int(x) => { ret x.is_signed; }, + _ => { ret false; }, + } + ret false; +} + +// 节点的「值类型」;0 表示无结果(控制/存储类)。 +fn ir_result_tid(ctx: *Ir2Vm, n: *IrEntry): usize { + match n.body { + .Arith(a) => { ret a.tid; }, + .Cmp(c) => { ret c.tid; }, + .Cast(c) => { ret c.to_tid; }, + .MemAlloc(m) => { ret TypeTable.make_ptr(ctx.ty, m.tid); }, + .MemGlobalAlloc(m) => { ret m.tid; }, + .MemLoad(m) => { ret m.tid; }, + .MemOffset(m) => { ret TypeTable.make_ptr(ctx.ty, m.tid); }, + .MemFieldPtr(m) => { + var field = ir_agg_field_tid(ctx, m.tid, m.field_idx); + ret TypeTable.make_ptr(ctx.ty, field); + }, + .Const(c) => { ret c.tid; }, + .TypeOp(t) => { ret t.tid; }, + .AggConstruct(a) => { ret a.tid; }, + .AggExtract(a) => { ret a.field_tid; }, + .AggInsert(a) => { ret a.tid; }, + .Select(s) => { ret s.tid; }, + .Call(c) => { ret c.tid; }, + .Param(p) => { ret p.tid; }, + _ => { ret 0; }, + } + ret 0; +} + +// ---- 小工具 ---- + +// 内部热路径:假定索引有效,直接取址(省去 Vec.get/valid)。 +fn usize_at(v: *Vec, i: usize): usize { + ret (v.data as *usize)[i]; +} + +fn push_usize(v: *Vec, x: usize) { + ignore_bool(Vec.push_raw(v, &x as *u8)); +} + +fn fnode(f: *IrFunc, i: usize): *IrEntry { + ret (f.nodes.data as *IrEntry) + i; +} + +fn align_up(x: usize, a: usize): usize { + if a <= 1 { ret x; } + ret (x + a - 1) / a * a; +} + +fn const_usize(n: *IrEntry): usize { + if n == null { ret 0; } + match n.body { + .Const(c) => { + match c.lit { + .Int(v) => { ret v; }, + .Char(v) => { ret v as usize; }, + .Bool(v) => { if v { ret 1; } ret 0; }, + .Ptr(v) => { ret v; }, + .Fn(v) => { ret v; }, + .Float(v) => { ret 0; }, + } + }, + _ => { ret 0; }, + } + ret 0; +} + +fn const_fn(n: *IrEntry): usize { + if n == null { ret 0; } + match n.body { + .Const(c) => { + match c.lit { + .Fn(v) => { ret v; }, + _ => { ret 0; }, + } + }, + _ => { ret 0; }, + } + ret 0; +} + +// ---- extern ---- + +fn fn_check_extern(ctx: *Ir2Vm, f: *IrFunc): bool { + if ctx.attr == null || f == null { ret false; } + var i: usize = 0; + while i < f.attrs.len { + var a = AttrM.node(ctx.attr, usize_at(&f.attrs, i)); + if a != null { + var info = AttrM.info_at(ctx.attr, a.info_index); + if info != null && ByteView.eq(info.name, ByteView.from_slice("extern")) { + a.consumed_mask = a.consumed_mask | (1 as u32 << 2); + ret true; + } + } + i = i + 1; + } + ret false; +} + +fn extern_get(ctx: *Ir2Vm, ref: usize): isize { + if ref == 0 || ref >= ctx.extern_native.len { ret -1; } + match Vec.get(&ctx.extern_native, ref) { + .Ok(ptr) => { ret (ptr as *isize).*; }, + .Err(error) => { ret -1; }, + } + ret -1; +} + +fn extern_set(ctx: *Ir2Vm, ref: usize, value: isize) { + if ref >= ctx.extern_native.len { ret; } + match Vec.get(&ctx.extern_native, ref) { + .Ok(ptr) => { (ptr as *isize).* = value; }, + .Err(error) => {}, + } +} + +// ---- 帧布局 ---- + +type Slot = struct { + result_off: usize, + data_off: usize, +} + +type FrameLayout = struct { + slots: Vec, // Slot + args_bytes: usize, + frame_bytes: usize, +} + +fn slot_at(frame: *FrameLayout, i: usize): *Slot { + ret (frame.slots.data as *Slot) + i; +} + +fn slot_result(frame: *FrameLayout, i: usize): usize { + var s = slot_at(frame, i); + if s == null { ret 0; } + ret s.result_off; +} + +fn slot_data(frame: *FrameLayout, i: usize): usize { + var s = slot_at(frame, i); + if s == null { ret 0; } + ret s.data_off; +} + +fn node_data_size(ctx: *Ir2Vm, f: *IrFunc, node: *IrEntry): usize { + match node.body { + .MemAlloc(m) => { + var count: usize = 1; + if m.count < f.nodes.len { + var cn = fnode(f, m.count); + count = const_usize(cn); + } + if count == 0 { count = 1; } + var sz = ir_type_size(ctx, m.tid); + if sz == 0 { sz = 1; } + ret sz * count; + }, + .AggConstruct(a) => { + var sz = ir_type_size(ctx, a.tid); + if sz == 0 { ret 8; } + ret sz; + }, + .AggInsert(a) => { + var sz = ir_type_size(ctx, a.tid); + if sz == 0 { ret 8; } + ret sz; + }, + .AggExtract(a) => { + if ir_is_aggregate(ctx, a.field_tid) { ret ir_type_size(ctx, a.field_tid); } + ret 0; + }, + .MemLoad(m) => { + if ir_is_aggregate(ctx, m.tid) { ret ir_type_size(ctx, m.tid); } + ret 0; + }, + .Panic(p) => { ret @sizeof(usize) * 4; }, + .Call(c) => { + if ir_is_aggregate(ctx, c.tid) { ret ir_type_size(ctx, c.tid); } + ret 0; + }, + _ => { ret 0; }, + } + ret 0; +} + +fn frame_build(fl: *FrameLayout, ctx: *Ir2Vm, fid: usize) { + fl.args_bytes = 0; + fl.frame_bytes = 0; + + var f = IrM.func(ctx.ir, fid); + if f == null { + fl.slots = Vec.blank(@sizeof(Slot), @alignof(Slot)); + ret; + } + + var n = f.nodes.len; + match Vec.with_capacity(@sizeof(Slot), @alignof(Slot), n) { + .Ok(v) => { fl.slots = v; }, + .Err(error) => { fl.slots = Vec.blank(@sizeof(Slot), @alignof(Slot)); }, + } + var i: usize = 0; + while i < n { + var blank: Slot = .{ .result_off = 0, .data_off = 0 }; + match Vec.push(&fl.slots, &blank as *u8) { .Ok => {}, .Err(error) => {} } + i = i + 1; + } + + var nargs = fn_nargs(ctx, f.fn_tid); + var hidden_sret: usize = 0; + if ir_is_aggregate(ctx, fn_ret_type(ctx, f.fn_tid)) { hidden_sret = 1; } + fl.args_bytes = (nargs + hidden_sret) * @sizeof(usize); + + var off = fl.args_bytes; + i = 1; + while i < n { + var node = fnode(f, i); + if ir_result_tid(ctx, node) != 0 { + var s = slot_at(fl, i); + s.result_off = off; + off = off + @sizeof(usize); + } + var extra = node_data_size(ctx, f, node); + if extra != 0 { + off = align_up(off, 8); + var s2 = slot_at(fl, i); + s2.data_off = off; + off = off + align_up(extra, 8); + } + i = i + 1; + } + fl.frame_bytes = align_up(off, 8); +} + +fn frame_drop(fl: *FrameLayout) { + Vec.drop(&fl.slots); +} + +// ---- cgen ---- + +type FixKind = enum { + Jmp, + Fnaddr, +} + +type Fix = struct { + kind: FixKind, + ins_idx: usize, + node_target: usize, + fn_target: usize, +} + +type Cgen = @self; + +ctx: *Ir2Vm, +fid: usize, +irf: *IrFunc, +frame: FrameLayout, +node_ip: Vec, // usize +fixups: *Vec, +terminated: bool, + +fn emit(cg: *Cgen, op: Op, ty: VmKind, imm: usize) { + ignore_usize(Mcode.add_instr(&cg.ctx.prog, op, ty, imm)); +} + +fn ip_of(cg: *Cgen): usize { + ret cg.ctx.prog.insns.len; +} + +fn ins_ptr(prog: *Prog, i: usize): *VmIns { + ret (prog.insns.data as *VmIns) + i; +} + +fn func_ptr(prog: *Prog, i: usize): *VmFunc { + ret (prog.funcs.data as *VmFunc) + i; +} + +fn cg_laddr(cg: *Cgen, off: usize) { emit(cg, .Laddr, .Void, off); } +fn cg_load(cg: *Cgen, k: VmKind) { emit(cg, .Load, k, 0); } +fn cg_store(cg: *Cgen, k: VmKind) { emit(cg, .Store, k, 0); } +fn cg_push(cg: *Cgen, k: VmKind, v: usize) { emit(cg, .Push, k, v); } +fn cg_op(cg: *Cgen, op: Op, k: VmKind) { emit(cg, op, k, 0); } +fn cg_swap(cg: *Cgen) { emit(cg, .Swap, .Void, 0); } +fn cg_gaddr(cg: *Cgen, idx: usize) { emit(cg, .Gaddr, .Void, idx); } + +fn cg_load_slot(cg: *Cgen, ref: usize, k: VmKind) { + cg_laddr(cg, slot_result(&cg.frame, ref)); + cg_load(cg, k); +} + +fn cg_store_slot(cg: *Cgen, ref: usize, k: VmKind) { + cg_laddr(cg, slot_result(&cg.frame, ref)); + cg_swap(cg); + cg_store(cg, k); +} + +fn cg_store_at(cg: *Cgen, addr: usize, k: VmKind) { + cg_laddr(cg, addr); + cg_swap(cg); + cg_store(cg, k); +} + +type DataLoc = struct { + indirect: bool, + off: usize, + base: usize, +} + +fn direct_data(off: usize): DataLoc { + ret DataLoc { .indirect = false, .off = off, .base = 0 }; +} + +fn direct_data_base(off: usize, base: usize): DataLoc { + ret DataLoc { .indirect = false, .off = off, .base = base }; +} + +fn ptr_slot(slot_off: usize): DataLoc { + ret DataLoc { .indirect = true, .off = slot_off, .base = 0 }; +} + +fn ptr_slot_base(slot_off: usize, base: usize): DataLoc { + ret DataLoc { .indirect = true, .off = slot_off, .base = base }; +} + +fn cg_push_addr(cg: *Cgen, loc: DataLoc, extra: usize) { + var delta = loc.base + extra; + if loc.indirect { + cg_laddr(cg, loc.off); + cg_load(cg, .Ptr); + if delta != 0 { + cg_push(cg, .U64, delta); + cg_op(cg, .Add, .Ptr); + } + } else { + cg_laddr(cg, loc.off + delta); + } +} + +fn cg_copy_agg(cg: *Cgen, src: DataLoc, dst: DataLoc, tid: usize) { + var sz = ir_type_size(cg.ctx, tid); + var done: usize = 0; + while sz - done >= 8 { + cg_push_addr(cg, dst, done); + cg_push_addr(cg, src, done); + cg_load(cg, .U64); + cg_store(cg, .U64); + done = done + 8; + } + while done < sz { + cg_push_addr(cg, dst, done); + cg_push_addr(cg, src, done); + cg_load(cg, .U8); + cg_store(cg, .U8); + done = done + 1; + } +} + +fn cg_add_fix(cg: *Cgen, ins_idx: usize, kind: FixKind, node_target: usize, fn_target: usize) { + var fx = Fix { + .kind = kind, + .ins_idx = ins_idx, + .node_target = node_target, + .fn_target = fn_target, + }; + match Vec.push(cg.fixups, &fx as *u8) { .Ok => {}, .Err(error) => {} } +} + +fn arith_tid(n: *IrEntry): usize { + match n.body { + .Arith(a) => { ret a.tid; }, + _ => { ret 0; }, + } + ret 0; +} + +fn cg_binop(cg: *Cgen, ref: usize, n: *IrEntry, op: Op) { + match n.body { + .Arith(a) => { + var k = vm_kind_of(cg.ctx, a.tid); + cg_load_slot(cg, a.left, k); + cg_load_slot(cg, a.right, k); + cg_op(cg, op, k); + cg_store_slot(cg, ref, k); + }, + _ => {}, + } +} + +fn cg_unop(cg: *Cgen, ref: usize, n: *IrEntry, op: Op) { + match n.body { + .Arith(a) => { + var k = vm_kind_of(cg.ctx, a.tid); + cg_load_slot(cg, a.left, k); + cg_op(cg, op, k); + cg_store_slot(cg, ref, k); + }, + _ => {}, + } +} + +fn cg_cmp(cg: *Cgen, ref: usize, n: *IrEntry, op: Op) { + match n.body { + .Cmp(c) => { + var k = vm_kind_of(cg.ctx, c.tid); + cg_load_slot(cg, c.a, k); + cg_load_slot(cg, c.b, k); + cg_op(cg, op, k); + cg_store_slot(cg, ref, .Bool); + }, + _ => {}, + } +} + +fn cg_const(cg: *Cgen, ref: usize, n: *IrEntry) { + var ctx = cg.ctx; + var tid: usize = 0; + match n.body { .Const(c) => { tid = c.tid; }, _ => { ret; } } + var te = TypeTable.node(ctx.ty, tid); + if te == null { ret; } + + match te.node { + .Fn(f) => { + var cref = const_fn(n); + var nat_idx: isize = -1; + if cref > 0 { nat_idx = extern_get(ctx, cref); } + if nat_idx >= 0 { + cg_push(cg, .U64, nat_idx as usize); + } else { + var idx = ip_of(cg); + cg_push(cg, .Ptr, 0); + cg_add_fix(cg, idx, .Fnaddr, 0, cref); + } + cg_store_slot(cg, ref, .Ptr); + ret; + }, + .Unit => { ret; }, + _ => {}, + } + + var k = vm_kind_of(ctx, tid); + var imm: usize = 0; + match te.node { + .Bool => { + match n.body { + .Const(c) => { + match c.lit { .Bool(v) => { if v { imm = 1; } }, _ => {} } + }, + _ => {}, + } + }, + .Int(x) => { imm = const_usize(n); }, + .Float => { + var fv: f64 = 0.0; + match n.body { + .Const(c) => { + match c.lit { .Float(v) => { fv = v; }, _ => {} } + }, + _ => {}, + } + Mem.copy(&imm as *any, &fv as *any, 8); + }, + .Ptr => { imm = const_usize(n); }, + _ => {}, + } + cg_push(cg, k, imm); + cg_store_slot(cg, ref, k); +} + +fn cg_cast(cg: *Cgen, ref: usize, n: *IrEntry) { + var ctx = cg.ctx; + match n.body { + .Cast(c) => { + var ft = TypeTable.node(ctx.ty, c.from_tid); + var tt = TypeTable.node(ctx.ty, c.to_tid); + if ft == null || tt == null { ret; } + + var fk = vm_kind_of(ctx, c.from_tid); + var tk = vm_kind_of(ctx, c.to_tid); + cg_load_slot(cg, c.val, fk); + + var f_int = false; + var f_bits: usize = 0; + var f_signed = false; + match ft.node { + .Int(x) => { f_int = true; f_bits = x.bits; f_signed = x.is_signed; }, + _ => {}, + } + var t_int = false; + var t_bits: usize = 0; + match tt.node { + .Int(x) => { t_int = true; t_bits = x.bits; }, + _ => {}, + } + var t_bool = false; + match tt.node { .Bool => { t_bool = true; }, _ => {} } + var f_bool = false; + match ft.node { .Bool => { f_bool = true; }, _ => {} } + + if f_int && t_int { + if f_bits > t_bits { + emit(cg, .Trunc, .Void, t_bits); + } else if f_bits < t_bits { + if f_signed { emit(cg, .Sext, .Void, f_bits); } + else { emit(cg, .Zext, .Void, f_bits); } + } + } else if f_bool && t_int { + emit(cg, .Zext, .Void, 1); + } + ignore_bool(t_bool); + cg_store_slot(cg, ref, tk); + }, + _ => {}, + } +} + +fn cg_mem(cg: *Cgen, ref: usize, n: *IrEntry) { + var ctx = cg.ctx; + match n.body { + .MemAlloc(m) => { + cg_laddr(cg, slot_data(&cg.frame, ref)); + cg_store_slot(cg, ref, .Ptr); + }, + .MemGlobalAlloc(m) => { + var idx: usize = 0; + if m.const_node < cg.irf.nodes.len { + var cn = fnode(cg.irf, m.const_node); + var pv: usize = 0; + var ctid: usize = 0; + match cn.body { + .Const(c) => { + ctid = c.tid; + match c.lit { .Ptr(v) => { pv = v; }, _ => {} } + }, + _ => {}, + } + if pv != 0 { + var size = ir_type_size(ctx, ctid); + idx = Mcode.add_data(&ctx.prog, pv, size); + } + } + if idx > 0 { emit(cg, .Gaddr, .Void, idx); } + else { cg_push(cg, .Ptr, 0); } + cg_store_slot(cg, ref, .Ptr); + }, + .MemLoad(m) => { + var tid = m.tid; + if ir_is_aggregate(ctx, tid) { + var data = slot_data(&cg.frame, ref); + cg_copy_agg(cg, ptr_slot(slot_result(&cg.frame, m.ptr)), direct_data(data), tid); + cg_laddr(cg, data); + cg_store_slot(cg, ref, .Ptr); + } else { + var k = vm_kind_of(ctx, tid); + cg_load_slot(cg, m.ptr, .Ptr); + cg_load(cg, k); + cg_store_slot(cg, ref, k); + } + }, + .MemStore(m) => { + var tid = m.tid; + if ir_is_aggregate(ctx, tid) { + cg_copy_agg(cg, ptr_slot(slot_result(&cg.frame, m.val)), + ptr_slot(slot_result(&cg.frame, m.ptr)), tid); + } else { + var k = vm_kind_of(ctx, tid); + cg_load_slot(cg, m.ptr, .Ptr); + cg_load_slot(cg, m.val, k); + cg_store(cg, k); + } + }, + .MemOffset(m) => { + var elem_size = ir_type_size(ctx, m.tid); + if elem_size == 0 { elem_size = 1; } + cg_load_slot(cg, m.ptr, .Ptr); + cg_load_slot(cg, m.offset, .U64); + cg_push(cg, .U64, elem_size); + cg_op(cg, .Mul, .U64); + cg_op(cg, .Add, .Ptr); + cg_store_slot(cg, ref, .Ptr); + }, + .MemFieldPtr(m) => { + var off = ir_agg_field_off(ctx, m.tid, m.field_idx); + cg_load_slot(cg, m.agg, .Ptr); + if off != 0 { + cg_push(cg, .U64, off); + cg_op(cg, .Add, .Ptr); + } + cg_store_slot(cg, ref, .Ptr); + }, + _ => {}, + } +} + +fn cg_agg(cg: *Cgen, ref: usize, n: *IrEntry) { + var ctx = cg.ctx; + match n.body { + .AggConstruct(a) => { + var base = slot_data(&cg.frame, ref); + var agg_t = a.tid; + var i: usize = 0; + while i < a.fields.len { + var fref = usize_at(&a.fields, i); + var ftid = ir_agg_field_tid(ctx, agg_t, i); + if ftid != 0 { + var foff = ir_agg_field_off(ctx, agg_t, i); + if ir_is_aggregate(ctx, ftid) { + cg_copy_agg(cg, ptr_slot(slot_result(&cg.frame, fref)), + direct_data_base(base, foff), ftid); + } else { + var fk = vm_kind_of(ctx, ftid); + cg_load_slot(cg, fref, fk); + cg_store_at(cg, base + foff, fk); + } + } + i = i + 1; + } + cg_laddr(cg, base); + cg_store_slot(cg, ref, .Ptr); + }, + .AggExtract(a) => { + var agg_t = a.tid; + var ftid = a.field_tid; + var foff = ir_agg_field_off(ctx, agg_t, a.field_idx); + if ir_is_aggregate(ctx, ftid) { + var data = slot_data(&cg.frame, ref); + cg_copy_agg(cg, ptr_slot_base(slot_result(&cg.frame, a.val), foff), + direct_data(data), ftid); + cg_laddr(cg, data); + cg_store_slot(cg, ref, .Ptr); + } else { + var fk = vm_kind_of(ctx, ftid); + cg_load_slot(cg, a.val, .Ptr); + if foff != 0 { + cg_push(cg, .U64, foff); + cg_op(cg, .Add, .Ptr); + } + cg_load(cg, fk); + cg_store_slot(cg, ref, fk); + } + }, + .AggInsert(a) => { + var base = slot_data(&cg.frame, ref); + var agg_t = a.tid; + cg_copy_agg(cg, ptr_slot(slot_result(&cg.frame, a.agg)), direct_data(base), agg_t); + + var foff = ir_agg_field_off(ctx, agg_t, a.field_idx); + var ftid = ir_agg_field_tid(ctx, agg_t, a.field_idx); + if ftid != 0 { + if ir_is_aggregate(ctx, ftid) { + cg_copy_agg(cg, ptr_slot(slot_result(&cg.frame, a.field)), + direct_data_base(base, foff), ftid); + } else { + var fk = vm_kind_of(ctx, ftid); + cg_load_slot(cg, a.field, fk); + cg_store_at(cg, base + foff, fk); + } + } + cg_laddr(cg, base); + cg_store_slot(cg, ref, .Ptr); + }, + _ => {}, + } +} + +fn cg_typeop(cg: *Cgen, ref: usize, n: *IrEntry) { + var ctx = cg.ctx; + var v: usize = 0; + match n.body { + .TypeOp(t) => { + match n.kind { + .TypeSizeof => { v = ir_type_size(ctx, t.tid); }, + .TypeBitsizeof => { + var info = info_of(ctx, t.tid); + if info != null { v = info.size_bits; } + }, + .TypeAlignof => { v = ir_type_align(ctx, t.tid); }, + .TypeOffsetof => { + var idx: usize = 0; + if t.field_idx < cg.irf.nodes.len { + idx = const_usize(fnode(cg.irf, t.field_idx)); + } + v = ir_agg_field_off(ctx, t.tid, idx); + }, + .TypeFieldCount => { + var te = TypeTable.node(ctx.ty, t.tid); + if te != null { + match te.node { + .Struct(agg) => { v = agg.variants.len; }, + .Union(agg) => { v = agg.variants.len; }, + .Enum(agg) => { v = agg.variants.len; }, + .Shape(agg) => { v = agg.variants.len; }, + _ => {}, + } + } + }, + _ => { ret; }, + } + }, + _ => { ret; }, + } + cg_push(cg, .U64, v); + cg_store_slot(cg, ref, .U64); +} + +fn cg_control(cg: *Cgen, ref: usize, n: *IrEntry) { + var ctx = cg.ctx; + match n.body { + .Param(p) => { + var k = vm_kind_of(ctx, p.tid); + var idx: usize = 0; + if p.idx < cg.irf.nodes.len { + idx = const_usize(fnode(cg.irf, p.idx)); + } + var shift: usize = 0; + if ir_is_aggregate(ctx, fn_ret_type(ctx, cg.irf.fn_tid)) { shift = 1; } + cg_laddr(cg, (idx + shift) * @sizeof(usize)); + cg_load(cg, k); + cg_store_slot(cg, ref, k); + }, + .Ret(r) => { + var ret_tid = r.tid; + if ir_is_aggregate(ctx, ret_tid) { + if r.val != 0 { + var sz = ir_type_size(ctx, ret_tid); + cg_laddr(cg, 0); + cg_load(cg, .Ptr); + cg_load_slot(cg, r.val, .Ptr); + cg_push(cg, .U64, sz); + var nat = Mcode.get_or_add_native(&ctx.prog, ByteView.from_slice("vm_memcpy")); + cg_push(cg, .U64, nat as usize); + emit(cg, .Ncall, .Void, 3); + emit(cg, .Drop, .Void, 0); + } + emit(cg, .Ret, .Void, 0); + } else { + var k = vm_kind_of(ctx, ret_tid); + if @enum2int(k) != @enum2int(VmKind.Void) && r.val != 0 { + cg_load_slot(cg, r.val, k); + } + emit(cg, .Ret, k, 0); + } + }, + .Br(b) => { + cg_load_slot(cg, b.cond, .Bool); + var bz_idx = ip_of(cg); + emit(cg, .Bz, .Void, 0); + cg_add_fix(cg, bz_idx, .Jmp, b.false_label, 0); + var jmp_idx = ip_of(cg); + emit(cg, .Jmp, .Void, 0); + cg_add_fix(cg, jmp_idx, .Jmp, b.true_label, 0); + }, + .Jmp(j) => { + var idx = ip_of(cg); + emit(cg, .Jmp, .Void, 0); + cg_add_fix(cg, idx, .Jmp, j.label, 0); + }, + .Call(cl) => { + var callee: usize = 0; + if cl.func < cg.irf.nodes.len { + callee = const_fn(fnode(cg.irf, cl.func)); + } + var nat_idx: isize = -1; + if callee > 0 { nat_idx = extern_get(ctx, callee); } + var nargs = cl.params.len; + + if nat_idx >= 0 { + var i: usize = 0; + while i < nargs { + var pref = usize_at(&cl.params, i); + var pn = fnode(cg.irf, pref); + var pk = vm_kind_of(ctx, ir_result_tid(ctx, pn)); + cg_load_slot(cg, pref, pk); + i = i + 1; + } + cg_push(cg, .U64, nat_idx as usize); + emit(cg, .Ncall, .Void, nargs); + var rk = vm_kind_of(ctx, cl.tid); + if @enum2int(rk) == @enum2int(VmKind.Void) { + emit(cg, .Drop, .Void, 0); + } else { + cg_store_slot(cg, ref, rk); + } + } else { + var ret_t = cl.tid; + var ret_is_sret = ir_is_aggregate(ctx, ret_t); + var sret_off: usize = 0; + if ret_is_sret { + sret_off = slot_data(&cg.frame, ref); + cg_laddr(cg, sret_off); + } + var i: usize = 0; + while i < nargs { + var pref = usize_at(&cl.params, i); + var pn = fnode(cg.irf, pref); + var pk = vm_kind_of(ctx, ir_result_tid(ctx, pn)); + cg_load_slot(cg, pref, pk); + i = i + 1; + } + cg_load_slot(cg, cl.func, .Ptr); + var actual = nargs; + if ret_is_sret { actual = actual + 1; } + emit(cg, .Call, .Void, actual); + + if ret_is_sret { + cg_laddr(cg, sret_off); + cg_store_slot(cg, ref, .Ptr); + } else { + var rk = vm_kind_of(ctx, ret_t); + if @enum2int(rk) != @enum2int(VmKind.Void) { + cg_store_slot(cg, ref, rk); + } + } + } + }, + _ => {}, + } +} + +fn cg_node(cg: *Cgen, ref: usize, n: *IrEntry) { + var ctx = cg.ctx; + match n.kind { + .TypeConst => { cg_const(cg, ref, n); }, + + .ArithAdd => { cg_binop(cg, ref, n, .Add); }, + .ArithSub => { cg_binop(cg, ref, n, .Sub); }, + .ArithMul => { cg_binop(cg, ref, n, .Mul); }, + .ArithDiv => { + if tid_is_signed(ctx, arith_tid(n)) { cg_binop(cg, ref, n, .DivS); } + else { cg_binop(cg, ref, n, .DivU); } + }, + .ArithRem => { + if tid_is_signed(ctx, arith_tid(n)) { cg_binop(cg, ref, n, .RemS); } + else { cg_binop(cg, ref, n, .RemU); } + }, + .ArithAnd => { cg_binop(cg, ref, n, .And); }, + .ArithOr => { cg_binop(cg, ref, n, .Or); }, + .ArithXor => { cg_binop(cg, ref, n, .Xor); }, + .ArithShl => { cg_binop(cg, ref, n, .Shl); }, + .ArithShr => { + if tid_is_signed(ctx, arith_tid(n)) { cg_binop(cg, ref, n, .ShrS); } + else { cg_binop(cg, ref, n, .ShrU); } + }, + .ArithNeg => { cg_unop(cg, ref, n, .Neg); }, + .ArithNot => { + var tid = arith_tid(n); + var te = TypeTable.node(ctx.ty, tid); + var is_bool = false; + if te != null { + match te.node { .Bool => { is_bool = true; }, _ => {} } + } + if is_bool { + match n.body { .Arith(a) => { + cg_load_slot(cg, a.left, .Bool); + }, _ => {} } + cg_push(cg, .Bool, 0); + cg_op(cg, .Eq, .Bool); + cg_store_slot(cg, ref, .Bool); + } else { + cg_unop(cg, ref, n, .Not); + } + }, + .ArithAbs => {}, + + .CmpEq => { cg_cmp(cg, ref, n, .Eq); }, + .CmpNe => { cg_cmp(cg, ref, n, .Ne); }, + .CmpLt => { + var k = cmp_tid(n); + if tid_is_signed(ctx, k) { cg_cmp(cg, ref, n, .Slt); } else { cg_cmp(cg, ref, n, .Ult); } + }, + .CmpLe => { + var k = cmp_tid(n); + if tid_is_signed(ctx, k) { cg_cmp(cg, ref, n, .Sle); } else { cg_cmp(cg, ref, n, .Ule); } + }, + .CmpGt => { + var k = cmp_tid(n); + if tid_is_signed(ctx, k) { cg_cmp(cg, ref, n, .Sgt); } else { cg_cmp(cg, ref, n, .Ugt); } + }, + .CmpGe => { + var k = cmp_tid(n); + if tid_is_signed(ctx, k) { cg_cmp(cg, ref, n, .Sge); } else { cg_cmp(cg, ref, n, .Uge); } + }, + + .CastTrunc => { cg_cast(cg, ref, n); }, + .CastZext => { cg_cast(cg, ref, n); }, + .CastSext => { cg_cast(cg, ref, n); }, + .CastFext => { cg_cast(cg, ref, n); }, + .CastFtrunc => { cg_cast(cg, ref, n); }, + .CastBitcast => { cg_cast(cg, ref, n); }, + .CastPtr2int => { cg_cast(cg, ref, n); }, + .CastInt2ptr => { cg_cast(cg, ref, n); }, + .CastBool2int => { cg_cast(cg, ref, n); }, + .CaseInt2float => { cg_cast(cg, ref, n); }, + .CaseFloat2int => { cg_cast(cg, ref, n); }, + + .MemAlloca => { cg_mem(cg, ref, n); }, + .MemGlobalAlloc => { cg_mem(cg, ref, n); }, + .MemLoad => { cg_mem(cg, ref, n); }, + .MemStore => { cg_mem(cg, ref, n); }, + .MemOffset => { cg_mem(cg, ref, n); }, + .MemFieldPtr => { cg_mem(cg, ref, n); }, + + .AggConstruct => { cg_agg(cg, ref, n); }, + .AggExtract => { cg_agg(cg, ref, n); }, + .AggInsert => { cg_agg(cg, ref, n); }, + + .TypeSizeof => { cg_typeop(cg, ref, n); }, + .TypeBitsizeof => { cg_typeop(cg, ref, n); }, + .TypeAlignof => { cg_typeop(cg, ref, n); }, + .TypeOffsetof => { cg_typeop(cg, ref, n); }, + .TypeFieldCount => { cg_typeop(cg, ref, n); }, + + .ControlParam => { cg_control(cg, ref, n); }, + .ControlRet => { cg_control(cg, ref, n); }, + .ControlBr => { cg_control(cg, ref, n); }, + .ControlJmp => { cg_control(cg, ref, n); }, + .ControlCall => { cg_control(cg, ref, n); }, + .ControlUnreachable => { emit(cg, .Halt, .Void, 1); }, + .ControlTrap => { emit(cg, .Halt, .Void, 1); }, + + .DbgPanic => { + var data = slot_data(&cg.frame, ref); + var msg = ByteView.empty(); + match n.body { .Panic(p) => { msg = p.msg; }, _ => {} } + var msg_idx = Mcode.add_data(&ctx.prog, msg.ptr as usize, msg.len); + var fmt = ByteView.from_slice("%s"); + var fmt_idx = Mcode.add_data(&ctx.prog, fmt.ptr as usize, 2); + if msg_idx <= 0 || fmt_idx <= 0 { ret; } + + cg_gaddr(cg, fmt_idx); + cg_store_at(cg, data + 0, .Ptr); + cg_push(cg, .U64, 2); + cg_store_at(cg, data + 8, .U64); + + cg_gaddr(cg, msg_idx); + cg_store_at(cg, data + 16, .Ptr); + cg_push(cg, .U64, msg.len); + cg_store_at(cg, data + 24, .U64); + + cg_laddr(cg, data + 0); + cg_laddr(cg, data + 16); + + var nat = Mcode.get_or_add_native(&ctx.prog, ByteView.from_slice("vm_printf")); + cg_push(cg, .U64, nat as usize); + emit(cg, .Ncall, .Void, 2); + emit(cg, .Drop, .Void, 0); + }, + .DbgBreakpoint => { emit(cg, .Bk, .Void, 0); }, + + _ => {}, + } +} + +fn cmp_tid(n: *IrEntry): usize { + match n.body { + .Cmp(c) => { ret c.tid; }, + _ => { ret 0; }, + } + ret 0; +} + +// ---- 翻译 ---- + +fn translate_function(ctx: *Ir2Vm, fid: usize, all_fixups: *Vec): usize { + var irf = IrM.func(ctx.ir, fid); + if irf == null { ret 0; } + + var local_fixups = Vec.blank(@sizeof(Fix), @alignof(Fix)); + var cg: Cgen = undefined; + cg.ctx = ctx; + cg.fid = fid; + cg.irf = irf; + cg.frame.slots = Vec.blank(@sizeof(Slot), @alignof(Slot)); + cg.frame.args_bytes = 0; + cg.frame.frame_bytes = 0; + match Vec.with_capacity(@sizeof(usize), @alignof(usize), irf.nodes.len) { + .Ok(v) => { cg.node_ip = v; }, + .Err(error) => { cg.node_ip = Vec.blank(@sizeof(usize), @alignof(usize)); }, + } + cg.fixups = &local_fixups; + cg.terminated = false; + frame_build(&cg.frame, ctx, fid); + + var func_ip = ip_of(&cg); + + ignore_usize(Mcode.add_func(&ctx.prog, irf.name, fn_nargs(ctx, irf.fn_tid), func_ip)); + + var local_slots = (cg.frame.frame_bytes - cg.frame.args_bytes + 7) / 8; + emit(&cg, .Alloc, .Void, local_slots); + + var nnodes = irf.nodes.len; + var i: usize = 0; + while i < nnodes { + push_usize(&cg.node_ip, 0); + i = i + 1; + } + + i = 1; + while i < nnodes { + (cg.node_ip.data as *usize)[i] = ip_of(&cg); + cg_node(&cg, i, fnode(irf, i)); + i = i + 1; + } + + i = 0; + while i < local_fixups.len { + match Vec.get(&local_fixups, i) { + .Ok(ptr) => { + var fx = ptr as *Fix; + if @enum2int(fx.kind) == @enum2int(FixKind.Jmp) { + var ins = ins_ptr(&ctx.prog, fx.ins_idx); + if ins != null { + var target_ip: usize = 0; + if fx.node_target < nnodes { + target_ip = usize_at(&cg.node_ip, fx.node_target); + } + ins.imm = target_ip - fx.ins_idx - 1; + } + } else if @enum2int(fx.kind) == @enum2int(FixKind.Fnaddr) { + match Vec.push(all_fixups, ptr as *u8) { .Ok => {}, .Err(error) => {} } + } + }, + .Err(error) => {}, + } + i = i + 1; + } + + Vec.drop(&cg.node_ip); + Vec.drop(&local_fixups); + frame_drop(&cg.frame); + ret func_ip; +} + +// ---- 生命周期 / 入口 ---- + +// 就地初始化(避免返回含 Prog 的大聚合;对应 stage1 spl_ir2vm_init)。 +fn init(ctx: *Ir2Vm, ir: *Ir, ty: *TypeTable, layout: *Layout, attr: *Attr) { + ctx.ir = ir; + ctx.ty = ty; + ctx.attr = attr; + ctx.layout = layout; + ctx.extern_native = Vec.blank(@sizeof(isize), @alignof(isize)); + + match Mcode.new() { + .Ok(v) => { ctx.prog = v; }, + .Err(error) => { @assert(false, "ir2vm: prog init failed"); }, + } +} + +fn drop(ctx: *Ir2Vm) { + Mcode.drop(&ctx.prog); + Vec.drop(&ctx.extern_native); +} + +fn run_func(ctx: *Ir2Vm, ref: usize): i32 { + var all_fixups = Vec.blank(@sizeof(Fix), @alignof(Fix)); + defer { Vec.drop(&all_fixups); } + ignore_usize(translate_function(ctx, ref, &all_fixups)); + ret 0; +} + +fn run(ctx: *Ir2Vm): i32 { + var n = ctx.ir.funcs.len; + if n <= 1 { ret 0; } + + // Pass 1:登记 extern 函数 -> native 索引。 + var i: usize = 0; + while i < n { + var v: isize = -1; + match Vec.push(&ctx.extern_native, &v as *u8) { .Ok => {}, .Err(error) => {} } + i = i + 1; + } + i = 1; + while i < n { + var irf = IrM.func(ctx.ir, i); + if irf != null && fn_check_extern(ctx, irf) { + var nat = Mcode.get_or_add_native(&ctx.prog, irf.name); + extern_set(ctx, i, nat); + } + i = i + 1; + } + + // 哨兵:保证真实函数入口地址非零。 + ignore_usize(Mcode.add_instr(&ctx.prog, .Halt, .Void, 0)); + + // 预分配指令容量(按 IR 节点数粗略估计),减少逐条 append 的扩容/拷贝。 + var est: usize = 16; + i = 1; + while i < n { + var irf = IrM.func(ctx.ir, i); + if irf != null { est = est + irf.nodes.len * 8 + 32; } + i = i + 1; + } + match Vec.reserve(&ctx.prog.insns, est) { .Ok => {}, .Err(error) => {} } + + // Pass 2:翻译非 extern 函数。 + var fid_ip = Vec.blank(@sizeof(usize), @alignof(usize)); + i = 0; + while i < n { + push_usize(&fid_ip, 0); + i = i + 1; + } + var all_fixups = Vec.blank(@sizeof(Fix), @alignof(Fix)); + defer { Vec.drop(&all_fixups); } + + i = 1; + while i < n { + if extern_get(ctx, i) < 0 { + var ip = translate_function(ctx, i, &all_fixups); + (fid_ip.data as *usize)[i] = ip; + } + i = i + 1; + } + + // FNADDR 回填。 + i = 0; + while i < all_fixups.len { + match Vec.get(&all_fixups, i) { + .Ok(ptr) => { + var fx = ptr as *Fix; + if @enum2int(fx.kind) == @enum2int(FixKind.Fnaddr) { + var ins = ins_ptr(&ctx.prog, fx.ins_idx); + if ins != null { + var target: usize = 0; + if fx.fn_target > 0 && fx.fn_target < n { + target = usize_at(&fid_ip, fx.fn_target); + } + ins.imm = target; + } + } + }, + .Err(error) => {}, + } + i = i + 1; + } + + // 每个函数的 ninsns = 下一个入口 - 本入口。 + i = 0; + while i < ctx.prog.funcs.len { + var f = func_ptr(&ctx.prog, i); + if f != null { + var next = ctx.prog.insns.len; + if i + 1 < ctx.prog.funcs.len { + var nf = func_ptr(&ctx.prog, i + 1); + if nf != null { next = nf.address; } + } + f.ninsns = next - f.address; + } + i = i + 1; + } + + Vec.drop(&fid_ip); + ret 0; +} + +fn dump(ctx: *Ir2Vm, out: *ByteBuffer): bool { + ret Mcode.dump(&ctx.prog, out); +} + +// 把翻译结果写成 .sir(二进制)。 +fn store(ctx: *Ir2Vm, path: ByteView): bool { + match Mcode.store(&ctx.prog, path) { + .Ok => { ret true; }, + .Err(error) => { ret false; }, + } + ret false; +} + +// ---- 工具 ---- + +fn ignore_usize(v: usize) {} +fn ignore_bool(b: bool) {} + +// ---- 测试 ---- + +fn main() { + var ir = IrM.new(); + defer { IrM.drop(&ir); } + + var tt: TypeTable = undefined; + match TypeTable.new() { + .Ok(v) => { tt = v; }, + .Err(error) => { @assert(false, "tt init"); ret; }, + } + defer { TypeTable.drop(&tt); } + + var layout: Layout = undefined; + match LayoutM.new() { + .Ok(v) => { layout = v; }, + .Err(error) => { @assert(false, "layout init"); ret; }, + } + defer { LayoutM.drop(&layout); } + + var attr: Attr = undefined; + match AttrM.new() { + .Ok(v) => { attr = v; }, + .Err(error) => { @assert(false, "attr init"); ret; }, + } + defer { AttrM.drop(&attr); } + + var ctx: Ir2Vm = undefined; + init(&ctx, &ir, &tt, &layout, &attr); + defer { drop(&ctx); } + var rc = run(&ctx); + @assert(rc == 0, "run empty ir"); + // 只有哨兵函数:无指令。 + @assert(ctx.prog.insns.len == 0, "empty ir yields no insns"); +} diff --git a/stage2/ir_builder.spl b/stage2/ir_builder.spl new file mode 100644 index 0000000..8ef26f1 --- /dev/null +++ b/stage2/ir_builder.spl @@ -0,0 +1,497 @@ +// Expect retcode = 0 + +// SIR builder (IrBuilder). Split from ir.spl: stage1 ast2ir +// infinitely recurses on same-file @self nesting; cross-file is fine. + +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 { + match Vec.get(&v, i) { .Ok(p) => { ret (p as *usize).*; }, .Err(e) => { ret 0; } } + ret 0; +} + +// ---- builder ---- + +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 { ret ref; } + match Vec.get(&self.blocks, self.current_block) { + .Ok(p) => { + var slot = p as *usize; + if slot.* == 0 { slot.* = ref; } + }, + .Err(error) => {}, + } + 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 { + match Vec.get(&self.fixups, k) { + .Ok(fp) => { + var fx = fp as *Fix; + 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 { + if @enum2int(fx.slot) == @enum2int(FixKind.BrTrue) { + match e.body { .Br(b) => { b.true_label = target; e.body = .Br(b); }, _ => {} } + } else if @enum2int(fx.slot) == @enum2int(FixKind.BrFalse) { + match e.body { .Br(b) => { b.false_label = target; e.body = .Br(b); }, _ => {} } + } else { + match e.body { .Jmp(j) => { j.label = target; e.body = .Jmp(j); }, _ => {} } + } + } + } + }, + .Err(error) => {}, + } + 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; + match Vec.push(&self.blocks, &zero as *u8) { .Ok => {}, .Err(error) => {} } + var block = self.blocks.len - 1; + self.current_block = block; + self.terminated = false; + ret block; +} + +fn block_end(self: *IrBuilder) { + // terminated 与空块校验在 stage1 为 Panic;这里保持同样约束但不终止进程。 +} + +// ---- arith / cmp / cast ---- + +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); } + +// ---- memory ---- + +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; +} + +// ---- type const ---- + +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)); } + +// ---- type ops ---- + +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; +} + +// ---- agg ---- + +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) => { + match Vec.push(&a.fields, &field as *u8) { .Ok => {}, .Err(error) => {} } + e.body = .AggConstruct(a); + }, + _ => {}, + } +} + +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; +} + +// ---- control ---- + +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 }; + match Vec.push(&self.fixups, &f1 as *u8) { .Ok => {}, .Err(error) => {} } + var f2 = Fix { .node = ref, .slot = .BrFalse, .block = false_block }; + match Vec.push(&self.fixups, &f2 as *u8) { .Ok => {}, .Err(error) => {} } +} + +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 }; + match Vec.push(&self.fixups, &fx as *u8) { .Ok => {}, .Err(error) => {} } +} + +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) => { + match Vec.push(&c.params, &arg as *u8) { .Ok => {}, .Err(error) => {} } + e.body = .Call(c); + }, + _ => {}, + } +} + +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(); +} diff --git a/stage2/lang_type.spl b/stage2/lang_type.spl new file mode 100644 index 0000000..d611adc --- /dev/null +++ b/stage2/lang_type.spl @@ -0,0 +1,1192 @@ +// Expect retcode = 0 + +// 语言类型表(移植自 stage1/spl_lang_type.c):在 IR 类型表(type_table.spl) +// 之上,登记「语言层类型」= IR 类型 id + 语言种类 + 聚合方法表,同样 hash-consing。 +// 另持有声明句柄表(def.spl)与语言类型子类型/求交/字面量适配。 +// +// 语言类型是 sema 的主类型:AST 节点 resolved_lang_tid、def.lang_id 都指这里。 +// 名称/方法名是 ByteView(借用);聚合方法表与 tuple/fn 的载荷 Vec 由本表拥有。 + +type .{ .Error } = @import("utils/error.spl"); +type .{ .Vec } = @import("utils/vec.spl"); +type .{ .Map } = @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 BigInt = @import("utils/mp.spl"); +type .{ .Span, .span_none } = @import("./diag.spl"); +type TypeTable = @import("./type_table.spl"); +type .{ .TypeNode, .Member, .AggKind, .LayoutMode } = @import("./type_table.spl"); +type .{ .DefTable, .DefKind, .DefNode, .DeclState } = @import("./def.spl"); + +type LangKind = enum { + Error, + Infer, + Never, + Any, + Unit, + Bool, + Int, + Float, + Ptr, + Array, + Slice, + Tuple, + Range, + Fn, + Struct, + Union, + Enum, + Shape, + Varargs, +} + +type LangMethod = struct { + name: ByteView, + def_id: usize, +} + +// kind 决定语义;extra 只装 kind 专属载荷(其余为 None)。 +type LangExtra = enum { + None, + Elem: usize, // Ptr / Slice / Range + Array: struct { elem: usize, len: usize }, + Tuple: Vec, // usize(元素 lang id) + Fn: struct { params: Vec, ret_type: usize }, // params: usize(lang id) +} + +type LangEntry = struct { + hash_next: usize, + kind: LangKind, + ir_type_id: usize, + methods: Vec, // LangMethod(仅聚合使用) + extra: LangExtra, +} + +type LangType = @self; + +ir: TypeTable, +lang_table: Vec, // LangEntry +fold: Map, // usize hash -> usize chain head +defs: DefTable, +owned_names: Vec, // ByteBuffer:为 tuple 字段生成的 "0"/"1" 名字(稳定存储) + +type LangTypeResult = enum { + Ok: LangType, + Err: Error, +} + +// ---- 小工具 ---- + +fn seed(): usize { + ret 14695981039346656037; +} + +fn mix(h: usize, x: usize): usize { + ret (h ^ x) * 1099511628211; +} + +fn mix_name(h0: usize, name: ByteView): usize { + var h = h0; + var i: usize = 0; + while i < name.len { + h = mix(h, name.ptr[i] as usize); + i = i + 1; + } + ret mix(h, 0); +} + +fn kind_code(kind: LangKind): usize { + var k: LangKind = kind; + ret @enum2int(k) as usize; +} + +// SPL 的 enum 不能用 `==`,统一走 @enum2int。 +fn kind_is(kind: LangKind, expected: LangKind): bool { + ret @enum2int(kind) == @enum2int(expected); +} + +fn empty_methods(): Vec { + ret Vec.blank(@sizeof(LangMethod), @alignof(LangMethod)); +} + +fn empty_usizes(): Vec { + ret Vec.blank(@sizeof(usize), @alignof(usize)); +} + +fn method_at(v: *Vec, i: usize): *LangMethod { + match Vec.get(v, i) { + .Ok(ptr) => { ret ptr as *LangMethod; }, + .Err(error) => { ret null; }, + } + ret null; +} + +fn is_agg_kind(kind: LangKind): bool { + match kind { + .Struct => { ret true; }, + .Union => { ret true; }, + .Enum => { ret true; }, + .Shape => { ret true; }, + _ => { ret false; }, + } + ret false; +} + +fn elem_of(entry: LangEntry): usize { + match entry.extra { + .Elem(x) => { ret x; }, + _ => { ret 0; }, + } + ret 0; +} + +// ---- 生命周期 ---- + +fn new_entry(kind: LangKind, ir_type_id: usize): LangEntry { + ret LangEntry { + .hash_next = 0, + .kind = kind, + .ir_type_id = ir_type_id, + .methods = empty_methods(), + .extra = .None, + }; +} + +fn new(): LangTypeResult { + var ir: TypeTable = undefined; + match TypeTable.new() { + .Ok(v) => { ir = v; }, + .Err(error) => { ret .Err(error); }, + } + var defs: DefTable = undefined; + match DefTable.new() { + .Ok(v) => { defs = v; }, + .Err(error) => { + TypeTable.drop(&ir); + ret .Err(error); + }, + } + var lang_table = Vec.blank(@sizeof(LangEntry), @alignof(LangEntry)); + var sentinel = new_entry(.Error, 0); + match Vec.push(&lang_table, &sentinel as *u8) { + .Ok => {}, + .Err(error) => { + Vec.drop(&lang_table); + DefTable.drop(&defs); + TypeTable.drop(&ir); + ret .Err(error); + }, + } + var lt = LangType { + .ir = ir, + .lang_table = lang_table, + .fold = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize), + hash_usize, eq_usize), + .defs = defs, + .owned_names = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)), + }; + ret .Ok(lt); +} + +fn drop(self: *LangType) { + var k: usize = 0; + while k < self.owned_names.len { + match Vec.get(&self.owned_names, k) { + .Ok(ptr) => { ByteBuffer.drop(ptr as *ByteBuffer); }, + .Err(error) => {}, + } + k = k + 1; + } + Vec.drop(&self.owned_names); + + var i: usize = 0; + while i < self.lang_table.len { + var e = lang_node(self, i); + if e != null { + free_extra(e); + } + i = i + 1; + } + Vec.drop(&self.lang_table); + Map.drop(&self.fold); + DefTable.drop(&self.defs); + TypeTable.drop(&self.ir); +} + +// id 0 与越界返回 null(对应 stage1 spl_lang_type_node)。 +fn lang_node(self: *LangType, id: usize): *LangEntry { + if id == 0 || id >= self.lang_table.len { ret null; } + match Vec.get(&self.lang_table, id) { + .Ok(ptr) => { ret ptr as *LangEntry; }, + .Err(error) => { ret null; }, + } + ret null; +} + +fn ir_type(self: *LangType, lang_id: usize): usize { + var e = lang_node(self, lang_id); + if e == null { ret 0; } + ret e.ir_type_id; +} + +fn get_ir_type(self: *LangType, lang_id: usize): usize { + ret ir_type(self, lang_id); +} + +// ---- IR 类型槽位预留(自引用聚合)---- + +fn reserve_ir(self: *LangType): usize { + ret TypeTable.reserve(&self.ir); +} + +fn define_ir(self: *LangType, id: usize, src: *TypeNode) { + TypeTable.define(&self.ir, id, src); +} + +// ---- def 便捷封装(sema 侧只用 LangType)---- + +fn alloc_def(self: *LangType, kind: DefKind, name: ByteView, span: Span, owner: *any, + ast_node_ref: usize): usize { + ret DefTable.alloc(&self.defs, kind, name, span, owner, ast_node_ref); +} + +fn def_node(self: *LangType, id: usize): *DefNode { + ret DefTable.node(&self.defs, id); +} + +fn resolve_def(self: *LangType, id: usize, lang_id: usize) { + DefTable.resolve(&self.defs, id, lang_id); +} + +fn def_lang_id(self: *LangType, id: usize): usize { + ret DefTable.get_lang_id(&self.defs, id); +} + +fn def_is_type(self: *LangType, id: usize): bool { + var d = DefTable.node(&self.defs, id); + if d == null { ret false; } + ret DefTable.is_type_kind(d.kind); +} + +// ---- intern ---- + +fn free_extra(e: *LangEntry) { + match e.extra { + .Tuple(v) => { Vec.drop(&v); }, + .Fn(f) => { Vec.drop(&f.params); }, + _ => {}, + } + Vec.drop(&e.methods); +} + +fn hash_entry(e: *LangEntry): usize { + var h = seed(); + h = mix(h, kind_code(e.kind)); + h = mix(h, e.ir_type_id); + match e.extra { + .None => {}, + .Elem(x) => { h = mix(h, x); }, + .Array(a) => { + h = mix(h, a.elem); + h = mix(h, a.len); + }, + .Tuple(v) => { + h = mix(h, v.len); + var i: usize = 0; + while i < v.len { + h = mix(h, TypeTable.usize_at(v, i)); + i = i + 1; + } + }, + .Fn(f) => { + h = mix(h, f.ret_type); + h = mix(h, f.params.len); + var i: usize = 0; + while i < f.params.len { + h = mix(h, TypeTable.usize_at(f.params, i)); + i = i + 1; + } + }, + } + if is_agg_kind(e.kind) { + h = mix(h, e.methods.len); + var i: usize = 0; + while i < e.methods.len { + var m = method_at(&e.methods, i); + if m != null { + h = mix(h, m.def_id); + h = mix_name(h, m.name); + } + i = i + 1; + } + } + ret h; +} + +fn eq_elem(a: LangExtra, b: LangExtra): bool { + match a { + .Elem(x) => { + match b { + .Elem(y) => { ret x == y; }, + _ => { ret false; }, + } + }, + .Array(p) => { + match b { + .Array(q) => { ret p.elem == q.elem && p.len == q.len; }, + _ => { ret false; }, + } + }, + .Tuple(v) => { + match b { + .Tuple(w) => { + if v.len != w.len { ret false; } + var i: usize = 0; + while i < v.len { + if TypeTable.usize_at(v, i) != TypeTable.usize_at(w, i) { ret false; } + i = i + 1; + } + ret true; + }, + _ => { ret false; }, + } + }, + .Fn(f) => { + match b { + .Fn(g) => { + if f.ret_type != g.ret_type { ret false; } + if f.params.len != g.params.len { ret false; } + var i: usize = 0; + while i < f.params.len { + if TypeTable.usize_at(f.params, i) != TypeTable.usize_at(g.params, i) { + ret false; + } + i = i + 1; + } + ret true; + }, + _ => { ret false; }, + } + }, + .None => { + match b { + .None => { ret true; }, + _ => { ret false; }, + } + }, + } + ret false; +} + +fn eq_methods(a: Vec, b: Vec): bool { + if a.len != b.len { ret false; } + var i: usize = 0; + while i < a.len { + var ma = method_at(&a, i); + var mb = method_at(&b, i); + if ma == null || mb == null { ret false; } + if ma.def_id != mb.def_id { ret false; } + if !ByteView.eq(ma.name, mb.name) { ret false; } + i = i + 1; + } + ret true; +} + +fn entry_eq(a: *LangEntry, b: *LangEntry): bool { + if kind_code(a.kind) != kind_code(b.kind) { ret false; } + if a.ir_type_id != b.ir_type_id { ret false; } + if !eq_elem(a.extra, b.extra) { ret false; } + if is_agg_kind(a.kind) && !eq_methods(a.methods, b.methods) { ret false; } + ret true; +} + +fn fold_find(self: *LangType, hash: usize, probe: *LangEntry): 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 e = lang_node(self, cur); + if e == null { ret 0; } + if entry_eq(e, probe) { ret cur; } + cur = e.hash_next; + } + ret 0; +} + +fn fold_insert(self: *LangType, hash: usize, id: usize) { + var old: usize = 0; + match Map.get(&self.fold, &hash as *u8, &old as *u8) { + .Found => {}, + .NotFound => { old = 0; }, + } + var e = lang_node(self, id); + if e == null { ret; } + e.hash_next = old; + match Map.put(&self.fold, &hash as *u8, &id as *u8) { + .Ok => {}, + .Err(error) => {}, + } +} + +// intern 一个 probe:命中释放载荷并返回已有 id;否则复制入表。probe 返回后失效。 +fn push(self: *LangType, probe: *LangEntry): usize { + var hash = hash_entry(probe); + var existing = fold_find(self, hash, probe); + if existing != 0 { + free_extra(probe); + probe.kind = .Error; + probe.extra = .None; + ret existing; + } + var entry = probe.*; + probe.kind = .Error; + probe.extra = .None; + probe.methods = empty_methods(); + var id = self.lang_table.len; + match Vec.push(&self.lang_table, &entry as *u8) { + .Ok => {}, + .Err(error) => { + free_extra(&entry); + ret 0; + }, + } + fold_insert(self, hash, id); + ret id; +} + +fn push_simple(self: *LangType, kind: LangKind, ir: usize): usize { + var e = new_entry(kind, ir); + ret push(self, &e); +} + +// ---- builders ---- + +fn make_error(self: *LangType): usize { + ret push_simple(self, .Error, TypeTable.make_error(&self.ir)); +} + +fn make_infer(self: *LangType): usize { + ret push_simple(self, .Infer, TypeTable.make_infer(&self.ir)); +} + +fn make_never(self: *LangType): usize { + ret push_simple(self, .Never, TypeTable.make_never(&self.ir)); +} + +fn make_any(self: *LangType): usize { + ret push_simple(self, .Any, TypeTable.make_any(&self.ir)); +} + +fn make_unit(self: *LangType): usize { + ret push_simple(self, .Unit, TypeTable.make_unit(&self.ir)); +} + +fn make_bool(self: *LangType): usize { + ret push_simple(self, .Bool, TypeTable.make_bool(&self.ir)); +} + +fn make_varargs(self: *LangType): usize { + ret push_simple(self, .Varargs, TypeTable.make_unit(&self.ir)); +} + +fn make_int(self: *LangType, bits: usize, is_signed: bool): usize { + ret push_simple(self, .Int, TypeTable.make_int(&self.ir, bits, is_signed)); +} + +fn make_float(self: *LangType, bits: usize): usize { + ret push_simple(self, .Float, TypeTable.make_float(&self.ir, bits)); +} + +fn make_ptr(self: *LangType, pointee_lang: usize): usize { + var pointee_ir = ir_type(self, pointee_lang); + if pointee_ir == 0 { ret 0; } + var ir = TypeTable.make_ptr(&self.ir, pointee_ir); + var e = new_entry(.Ptr, ir); + e.extra = .Elem(pointee_lang); + ret push(self, &e); +} + +fn make_array(self: *LangType, elem_lang: usize, len: usize): usize { + var elem_ir = ir_type(self, elem_lang); + if elem_ir == 0 { ret 0; } + var ir = TypeTable.make_array(&self.ir, elem_ir, len); + var e = new_entry(.Array, ir); + e.extra = .Array(.{ .elem = elem_lang, .len = len }); + ret push(self, &e); +} + +fn usize_lang(self: *LangType): usize { + ret make_int(self, @sizeof(usize) * 8, false); +} + +// 切片 = 语言层 Slice,IR 层是 struct { ptr, len }。 +fn make_slice(self: *LangType, elem_lang: usize): usize { + var elem_ir = ir_type(self, elem_lang); + if elem_ir == 0 { ret 0; } + var ptr_lang = make_ptr(self, elem_lang); + var len_lang = usize_lang(self); + var ptr_ir = ir_type(self, ptr_lang); + var len_ir = ir_type(self, len_lang); + var members = Vec.blank(@sizeof(Member), @alignof(Member)); + TypeTable.push_member(&members, "ptr", ptr_ir); + TypeTable.push_member(&members, "len", len_ir); + var agg_ir = TypeTable.make_agg(&self.ir, .Struct, .{ + .mode = .Auto, + .fixed_align_bits = 0, + .tag_type = 0, + .variants = members, + }); + var e = new_entry(.Slice, agg_ir); + e.extra = .Elem(elem_lang); + ret push(self, &e); +} + +// 区间 = 语言层 Range,IR 层是 struct { start, end }。 +fn make_range(self: *LangType, elem_lang: usize): usize { + var elem_ir = ir_type(self, elem_lang); + if elem_ir == 0 { ret 0; } + var members = Vec.blank(@sizeof(Member), @alignof(Member)); + TypeTable.push_member(&members, "start", elem_ir); + TypeTable.push_member(&members, "end", elem_ir); + var agg_ir = TypeTable.make_agg(&self.ir, .Struct, .{ + .mode = .Auto, + .fixed_align_bits = 0, + .tag_type = 0, + .variants = members, + }); + var e = new_entry(.Range, agg_ir); + e.extra = .Elem(elem_lang); + ret push(self, &e); +} + +// 为 tuple 字段生成稳定的十进制名字("0"/"1"/...),镜像 stage1。 +fn intern_index_name(self: *LangType, value: usize): ByteView { + var b = ByteBuffer.new(); + match append_usize(&b, value, 10, false) { + .Ok => {}, + .Err(error) => {}, + } + var view = ByteView.from_slice(ByteBuffer.as_slice(b)); + match Vec.push(&self.owned_names, &b as *u8) { + .Ok => {}, + .Err(error) => { ByteBuffer.drop(&b); }, + } + ret view; +} + +// elems 所有权转移(Tuple 载荷);返回后调用方不得再 drop。 +fn make_tuple(self: *LangType, elems: Vec): usize { + var members = Vec.blank(@sizeof(Member), @alignof(Member)); + var i: usize = 0; + while i < elems.len { + var elem_ir = ir_type(self, TypeTable.usize_at(elems, i)); + if elem_ir == 0 { + Vec.drop(&members); + Vec.drop(&elems); + ret 0; + } + var nm = intern_index_name(self, i); + TypeTable.push_member(&members, ByteView.as_slice(nm), elem_ir); + i = i + 1; + } + var agg_ir = TypeTable.make_agg(&self.ir, .Struct, .{ + .mode = .Auto, + .fixed_align_bits = 0, + .tag_type = 0, + .variants = members, + }); + var e = new_entry(.Tuple, agg_ir); + e.extra = .Tuple(elems); + ret push(self, &e); +} + +// params 所有权转移(Fn 载荷)。 +fn make_fn(self: *LangType, ret_lang: usize, params: Vec): usize { + var ret_ir = ir_type(self, ret_lang); + if ret_ir == 0 { + Vec.drop(¶ms); + ret 0; + } + var param_irs = Vec.blank(@sizeof(usize), @alignof(usize)); + var i: usize = 0; + while i < params.len { + var pir = ir_type(self, TypeTable.usize_at(params, i)); + if pir == 0 { + Vec.drop(¶m_irs); + Vec.drop(¶ms); + ret 0; + } + match Vec.push(¶m_irs, &pir as *u8) { + .Ok => {}, + .Err(error) => { + Vec.drop(¶m_irs); + Vec.drop(¶ms); + ret 0; + }, + } + i = i + 1; + } + var fn_ir = TypeTable.make_fn(&self.ir, ret_ir, param_irs); + var e = new_entry(.Fn, fn_ir); + e.extra = .Fn(.{ .params = params, .ret_type = ret_lang }); + ret push(self, &e); +} + +fn parse_bits(rest: []u8): usize { + if rest.len == 0 { ret 0; } + var value: usize = 0; + var i: usize = 0; + while i < rest.len { + var c = rest.ptr[i]; + if c < '0' || c > '9' { ret 0; } + value = value * 10 + (c - '0') as usize; + i = i + 1; + } + ret value; +} + +// 内置类型名:bool/void/isize/usize/any/iN/uN/fN(对应 stage1 spl_type_lookup_builtin)。 +fn lookup_builtin(self: *LangType, name: ByteView): usize { + if name.len == 0 { ret 0; } + if ByteView.eq(name, ByteView.from_slice("bool")) { ret make_bool(self); } + if ByteView.eq(name, ByteView.from_slice("void")) { ret make_unit(self); } + if ByteView.eq(name, ByteView.from_slice("isize")) { + ret make_int(self, @sizeof(usize) * 8, true); + } + if ByteView.eq(name, ByteView.from_slice("usize")) { ret usize_lang(self); } + if ByteView.eq(name, ByteView.from_slice("any")) { ret make_any(self); } + + var prefix = name.ptr[0]; + if prefix == 'i' || prefix == 'u' || prefix == 'f' { + var rest = ByteView.slice(name, 1, name.len); + match rest { + .Ok(rs) => { + var bits = parse_bits(ByteView.as_slice(rs)); + if bits > 0 { + if prefix == 'i' { ret make_int(self, bits, true); } + if prefix == 'u' { ret make_int(self, bits, false); } + if prefix == 'f' { ret make_float(self, bits); } + } + }, + .Err(error) => {}, + } + } + ret 0; +} + +// ---- IR 位宽读取 ---- + +fn int_bits(self: *LangType, ir_id: usize): usize { + var te = TypeTable.node(&self.ir, ir_id); + if te == null { ret 0; } + match te.node { + .Int(x) => { ret x.bits; }, + _ => { ret 0; }, + } + ret 0; +} + +fn int_signed(self: *LangType, ir_id: usize): bool { + var te = TypeTable.node(&self.ir, ir_id); + if te == null { ret false; } + match te.node { + .Int(x) => { ret x.is_signed; }, + _ => { ret false; }, + } + ret false; +} + +fn float_bits(self: *LangType, ir_id: usize): usize { + var te = TypeTable.node(&self.ir, ir_id); + if te == null { ret 0; } + match te.node { + .Float(x) => { ret x.bits; }, + _ => { ret 0; }, + } + ret 0; +} + +// ---- 语言层子类型 / 求交 / 字面量适配 ---- + +fn is_subtype(self: *LangType, sub: usize, super: usize): bool { + if sub == 0 || super == 0 { ret false; } + if sub == super { ret true; } + + var ns = lang_node(self, sub); + var np = lang_node(self, super); + if ns == null || np == null { ret false; } + + match ns.kind { + .Never => { ret true; }, + _ => {}, + } + match np.kind { + .Any => { ret true; }, + _ => {}, + } + + if kind_is(ns.kind, .Int) && kind_is(np.kind, .Int) { + ret int_bits(self, ns.ir_type_id) == 0; + } + if kind_is(ns.kind, .Float) && kind_is(np.kind, .Float) { + ret float_bits(self, ns.ir_type_id) == 0; + } + + // null(*never)可隐式转为函数指针 + if kind_is(np.kind, .Fn) && kind_is(ns.kind, .Ptr) { + var se = lang_node(self, elem_of(ns.*)); + if se != null && kind_is(se.kind, .Never) { ret true; } + } + if kind_is(ns.kind, .Ptr) && kind_is(np.kind, .Ptr) { + var se_l = elem_of(ns.*); + var pe_l = elem_of(np.*); + if se_l == pe_l { ret true; } + var se = lang_node(self, se_l); + var pe = lang_node(self, pe_l); + if se != null && kind_is(se.kind, .Never) { ret true; } + if pe != null && kind_is(pe.kind, .Any) { ret true; } + ret false; + } + ret false; +} + +fn join(self: *LangType, a: usize, b: usize): usize { + if a == 0 || b == 0 { ret 0; } + if a == b { ret a; } + + var na = lang_node(self, a); + var nb = lang_node(self, b); + if na == null || nb == null { ret 0; } + + if kind_is(na.kind, .Never) { ret b; } + if kind_is(nb.kind, .Never) { ret a; } + if kind_is(na.kind, .Any) || kind_is(nb.kind, .Any) { ret make_any(self); } + + var a_int = kind_is(na.kind, .Int); + var b_int = kind_is(nb.kind, .Int); + var a_flt = kind_is(na.kind, .Float); + var b_flt = kind_is(nb.kind, .Float); + + if a_int && b_int { + var a_ct = int_bits(self, na.ir_type_id) == 0; + var b_ct = int_bits(self, nb.ir_type_id) == 0; + if a_ct && b_ct { ret a; } + if a_ct { ret b; } + if b_ct { ret a; } + ret 0; + } + if a_flt && b_flt { + var a_ct = float_bits(self, na.ir_type_id) == 0; + var b_ct = float_bits(self, nb.ir_type_id) == 0; + if a_ct && b_ct { ret a; } + if a_ct { ret b; } + if b_ct { ret a; } + ret 0; + } + if a_int && b_flt { + if int_bits(self, na.ir_type_id) == 0 && float_bits(self, nb.ir_type_id) == 0 { + ret make_float(self, 0); + } + ret 0; + } + if a_flt && b_int { + if float_bits(self, na.ir_type_id) == 0 && int_bits(self, nb.ir_type_id) == 0 { + ret make_float(self, 0); + } + ret 0; + } + + if kind_is(na.kind, .Fn) && kind_is(nb.kind, .Ptr) { + var eb = lang_node(self, elem_of(nb.*)); + if eb != null && kind_is(eb.kind, .Never) { ret a; } + } + if kind_is(na.kind, .Ptr) && kind_is(nb.kind, .Fn) { + var ea = lang_node(self, elem_of(na.*)); + if ea != null && kind_is(ea.kind, .Never) { ret b; } + } + if kind_is(na.kind, .Ptr) && kind_is(nb.kind, .Ptr) { + var ea_l = elem_of(na.*); + var eb_l = elem_of(nb.*); + if ea_l == eb_l { ret a; } + var ea = lang_node(self, ea_l); + var eb = lang_node(self, eb_l); + if ea != null && kind_is(ea.kind, .Never) { ret b; } + if eb != null && kind_is(eb.kind, .Never) { ret a; } + if ea != null && kind_is(ea.kind, .Any) { ret a; } + if eb != null && kind_is(eb.kind, .Any) { ret b; } + ret 0; + } + ret 0; +} + +// 判断 BigInt 字面量能否装进目标整数类型(任意宽度)。 +fn int_fits(self: *LangType, target: usize, value: BigInt): bool { + var nt = lang_node(self, target); + if nt == null || !kind_is(nt.kind, .Int) { ret false; } + var bits = int_bits(self, nt.ir_type_id); + if bits == 0 { ret true; } + var signed = int_signed(self, nt.ir_type_id); + + var neg = BigInt.is_negative(value); + if neg && !signed { ret false; } + + var mag = BigInt.zero(); + defer { BigInt.drop(&mag); } + match BigInt.abs(&mag, value) { + .Ok => {}, + .Err(error) => { ret false; }, + } + + // 镜像 stage1:目标位宽 >= 64 时不做边界检查(其 mpa 只存低 64 位)。 + if bits >= 64 { ret true; } + + var u: u64 = 0; + match BigInt.to_u64(&u, mag) { + .Ok => { + if signed { + if neg { + var lim: u64 = (1 as u64) << (bits - 1); + ret u <= lim; + } + var lim: u64 = ((1 as u64) << (bits - 1)) - 1; + ret u <= lim; + } + var lim: u64 = ((1 as u64) << bits) - 1; + ret u <= lim; + }, + .Err(error) => { ret false; }, + } + ret false; +} + +// ---- dump(镜像 stage1 spl_lang_type_dump)---- + +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: *LangType, id: usize, out: *ByteBuffer, stack: *Vec): bool { + var i: usize = 0; + while i < stack.len { + if TypeTable.usize_at(stack.*, i) == id { + if !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 e = lang_node(self, id); + if e == null { + if !push_out(out, "none") { ret false; } + var dropped: usize = 0; + match Vec.pop(stack, &dropped as *u8) { + .Ok => {}, + .Err(error) => {}, + } + ret true; + } + + var ok = true; + match e.kind { + .Error => { ok = push_out(out, "error"); }, + .Infer => { ok = push_out(out, "infer"); }, + .Never => { ok = push_out(out, "never"); }, + .Any => { ok = push_out(out, "any"); }, + .Unit => { ok = push_out(out, "unit"); }, + .Bool => { ok = push_out(out, "bool"); }, + .Varargs => { ok = push_out(out, "..."); }, + .Int => { + if int_bits(self, e.ir_type_id) == 0 { + ok = push_out(out, "comptime_int"); + } else { + ok = TypeTable.pure_dump(&self.ir, e.ir_type_id, out); + } + }, + .Float => { + if float_bits(self, e.ir_type_id) == 0 { + ok = push_out(out, "comptime_float"); + } else { + ok = TypeTable.pure_dump(&self.ir, e.ir_type_id, out); + } + }, + .Ptr => { + if !push_out(out, "*") { ok = false; } + if ok { ok = dump_rec(self, elem_of(e.*), out, stack); } + }, + .Array => { + var len: usize = 0; + match e.extra { + .Array(a) => { len = a.len; }, + _ => {}, + } + if !push_out(out, "[") { ok = false; } + if ok && !push_usize(out, len) { ok = false; } + if ok && !push_out(out, "]") { ok = false; } + // 镜像 stage1:数组直接 dump IR 类型(会重复一次 [N])。 + if ok { ok = TypeTable.pure_dump(&self.ir, e.ir_type_id, out); } + }, + .Slice => { + if !push_out(out, "[]") { ok = false; } + if ok { ok = dump_rec(self, elem_of(e.*), out, stack); } + }, + .Tuple => { + var elems = empty_usizes(); + match e.extra { + .Tuple(v) => { elems = v; }, + _ => {}, + } + if !push_out(out, "(") { ok = false; } + var j: usize = 0; + while ok && j < elems.len { + if j != 0 && !push_out(out, ",") { ok = false; } + if ok { ok = dump_rec(self, TypeTable.usize_at(elems, j), out, stack); } + j = j + 1; + } + if ok && !push_out(out, ")") { ok = false; } + }, + .Range => { + if ok { ok = dump_rec(self, elem_of(e.*), out, stack); } + if ok && !push_out(out, "..") { ok = false; } + if ok { ok = dump_rec(self, elem_of(e.*), out, stack); } + }, + .Fn => { + var params = empty_usizes(); + var ret_lang_id: usize = 0; + match e.extra { + .Fn(f) => { + params = f.params; + ret_lang_id = f.ret_type; + }, + _ => {}, + } + if !push_out(out, "fn(") { ok = false; } + var j: usize = 0; + while ok && j < params.len { + if j != 0 && !push_out(out, ",") { ok = false; } + if ok { ok = dump_rec(self, TypeTable.usize_at(params, j), out, stack); } + j = j + 1; + } + if ok && !push_out(out, ")") { ok = false; } + if ok { ok = dump_rec(self, ret_lang_id, out, stack); } + }, + .Struct => { ok = TypeTable.pure_dump(&self.ir, e.ir_type_id, out); }, + .Union => { ok = TypeTable.pure_dump(&self.ir, e.ir_type_id, out); }, + .Enum => { ok = TypeTable.pure_dump(&self.ir, e.ir_type_id, out); }, + .Shape => { ok = TypeTable.pure_dump(&self.ir, e.ir_type_id, out); }, + } + + var dropped2: usize = 0; + match Vec.pop(stack, &dropped2 as *u8) { + .Ok => {}, + .Err(error) => {}, + } + ret ok; +} + +fn dump(self: *LangType, 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 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 dump_str(lt: *LangType, id: usize, expected: []u8): bool { + var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } + if !dump(lt, id, &out) { ret false; } + 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 test_builders_intern() { + var lt: LangType = undefined; + match new() { + .Ok(v) => { lt = v; }, + .Err(error) => { @assert(false, "lang type new failed"); }, + } + defer { drop(<); } + + var i32a = make_int(<, 32, true); + var i32b = make_int(<, 32, true); + var u32 = make_int(<, 32, false); + @assert(i32a != 0 && i32a == i32b, "int interned"); + @assert(u32 != i32a, "u32 distinct"); + @assert(ir_type(<, i32a) != 0, "int has ir"); + + var p1 = make_ptr(<, i32a); + var p2 = make_ptr(<, i32a); + @assert(p1 == p2, "ptr interned"); + @assert(make_ptr(<, u32) != p1, "ptr distinct"); + + var s1 = make_slice(<, i32a); + var s2 = make_slice(<, i32a); + @assert(s1 == s2, "slice interned"); + + var tu1 = empty_usizes(); + match Vec.push(&tu1, &i32a as *u8) { .Ok=>{}, .Err(e)=>{ @assert(false,"push"); } } + var t1 = make_tuple(<, tu1); + var tu2 = empty_usizes(); + match Vec.push(&tu2, &i32a as *u8) { .Ok=>{}, .Err(e)=>{ @assert(false,"push"); } } + var t2 = make_tuple(<, tu2); + @assert(t1 == t2, "tuple interned"); + + var pa1 = empty_usizes(); + match Vec.push(&pa1, &i32a as *u8) { .Ok=>{}, .Err(e)=>{ @assert(false,"push"); } } + var f1 = make_fn(<, u32, pa1); + var pa2 = empty_usizes(); + match Vec.push(&pa2, &i32a as *u8) { .Ok=>{}, .Err(e)=>{ @assert(false,"push"); } } + var f2 = make_fn(<, u32, pa2); + @assert(f1 == f2, "fn interned"); + @assert(make_fn(<, i32a, empty_usizes()) != f1, "fn distinct"); +} + +fn test_subtype_join() { + var lt: LangType = undefined; + match new() { + .Ok(v) => { lt = v; }, + .Err(error) => { @assert(false, "lang type new failed"); }, + } + defer { drop(<); } + + var never = make_never(<); + var anyt = make_any(<); + var ci = make_int(<, 0, true); + var i32t = make_int(<, 32, true); + var u32t = make_int(<, 32, false); + var cf = make_float(<, 0); + + @assert(is_subtype(<, never, i32t), "never <= i32"); + @assert(is_subtype(<, i32t, anyt), "i32 <= any"); + @assert(is_subtype(<, ci, i32t), "comptime_int <= int"); + @assert(!is_subtype(<, cf, i32t), "comptime_float !<= int"); + @assert(is_subtype(<, cf, make_float(<, 64)), "comptime_float <= f64"); + @assert(!is_subtype(<, i32t, u32t), "i32 !<= u32"); + + @assert(join(<, never, i32t) == i32t, "join never"); + @assert(join(<, ci, i32t) == i32t, "join ct+int -> int"); + @assert(join(<, i32t, i32t) == i32t, "join same"); + @assert(join(<, i32t, u32t) == 0, "join distinct ints"); + @assert(join(<, anyt, i32t) == anyt, "join any"); + @assert(join(<, ci, cf) != 0, "join ct int+float -> ct float"); +} + +fn test_int_fits() { + var lt: LangType = undefined; + match new() { + .Ok(v) => { lt = v; }, + .Err(error) => { @assert(false, "lang type new failed"); }, + } + defer { drop(<); } + + var i8t = make_int(<, 8, true); + var u8t = make_int(<, 8, false); + var i64t = make_int(<, 64, true); + var ci = make_int(<, 0, true); + + var v = BigInt.zero(); + defer { BigInt.drop(&v); } + match BigInt.from_i64(&v, 127) { .Ok=>{}, .Err(e)=>{ @assert(false,"from"); } } + @assert(int_fits(<, i8t, v), "127 fits i8"); + + match BigInt.from_i64(&v, 128) { .Ok=>{}, .Err(e)=>{ @assert(false,"from"); } } + @assert(!int_fits(<, i8t, v), "128 !fits i8"); + @assert(int_fits(<, u8t, v), "128 fits u8"); + + match BigInt.from_i64(&v, -128) { .Ok=>{}, .Err(e)=>{ @assert(false,"from"); } } + @assert(int_fits(<, i8t, v), "-128 fits i8"); + @assert(!int_fits(<, u8t, v), "-128 !fits u8"); + + match BigInt.from_i64(&v, -129) { .Ok=>{}, .Err(e)=>{ @assert(false,"from"); } } + @assert(!int_fits(<, i8t, v), "-129 !fits i8"); + + match BigInt.from_i64(&v, 255) { .Ok=>{}, .Err(e)=>{ @assert(false,"from"); } } + @assert(int_fits(<, u8t, v), "255 fits u8"); + @assert(!int_fits(<, i8t, v), "255 !fits i8"); + + match BigInt.from_i64(&v, 42) { .Ok=>{}, .Err(e)=>{ @assert(false,"from"); } } + @assert(int_fits(<, ci, v), "any fits comptime_int"); + @assert(int_fits(<, i64t, v), "42 fits i64"); +} + +fn test_dump() { + var lt: LangType = undefined; + match new() { + .Ok(v) => { lt = v; }, + .Err(error) => { @assert(false, "lang type new failed"); }, + } + defer { drop(<); } + + var i32t = make_int(<, 32, true); + var ci = make_int(<, 0, true); + var u8t = make_int(<, 8, false); + + @assert(dump_str(<, i32t, "i32"), "dump i32"); + @assert(dump_str(<, ci, "comptime_int"), "dump comptime_int"); + @assert(dump_str(<, make_ptr(<, i32t), "*i32"), "dump ptr"); + @assert(dump_str(<, make_slice(<, u8t), "[]u8"), "dump slice"); + @assert(dump_str(<, make_varargs(<), "..."), "dump varargs"); + @assert(dump_str(<, 0, "none"), "dump none"); +} + +fn test_lookup_builtin() { + var lt: LangType = undefined; + match new() { + .Ok(v) => { lt = v; }, + .Err(error) => { @assert(false, "lang type new failed"); }, + } + defer { drop(<); } + + @assert(lookup_builtin(<, ByteView.from_slice("bool")) == make_bool(<), "bool builtin"); + @assert(lookup_builtin(<, ByteView.from_slice("usize")) == usize_lang(<), "usize builtin"); + @assert(lookup_builtin(<, ByteView.from_slice("i32")) == make_int(<, 32, true), "i32 builtin"); + @assert(lookup_builtin(<, ByteView.from_slice("f64")) == make_float(<, 64), "f64 builtin"); + @assert(lookup_builtin(<, ByteView.from_slice("nope")) == 0, "unknown builtin"); +} + +fn main() { + test_builders_intern(); + test_subtype_join(); + test_int_fits(); + test_dump(); + test_lookup_builtin(); +} diff --git a/stage2/layout.spl b/stage2/layout.spl new file mode 100644 index 0000000..847238c --- /dev/null +++ b/stage2/layout.spl @@ -0,0 +1,358 @@ +// Expect retcode = 0 + +// 目标布局(移植自 stage1/spl_layout.c)。输入是 IR 类型表 TypeTable; +// 为每个类型计算 size_bits / align_bits / 字段位偏移,供 ir2vm 使用。 +// 默认 64 位平台:标量按 1/2/4/8 字节对齐,指针 = usize,聚合按最大字段对齐 +// 且不允许重排(与 stage1 的默认策略一致)。 + +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; } + match Vec.get(&self.infos, id) { + .Ok(ptr) => { ret ptr as *LayoutInfo; }, + .Err(error) => { ret null; }, + } + ret null; +} + +// 未解析或越界返回 null(对应 stage1 spl_layout_info_of)。 +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) { + match Vec.push(v, &x as *u8) { + .Ok => {}, + .Err(error) => {}, + } +} + +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; +} diff --git a/stage2/mcode.spl b/stage2/mcode.spl new file mode 100644 index 0000000..654c462 --- /dev/null +++ b/stage2/mcode.spl @@ -0,0 +1,723 @@ +// Expect retcode = 0 + +// SPL VM 机器码容器(移植自 stage0/spl_mcode.c 的构建/序列化/dump 部分)。 +// 只负责「内存中的 prog」与「写入 .sir」,不包含反序列化(由 C 侧 spl_cli 读)。 +// +// 字符串一律是 ByteView(借用上层 IR/attr 的名称,本模块不复制、不释放); +// gdata 条目是 ByteBuffer,由本模块拥有并释放。 +// +// 二进制格式(元数据字段均为 u64 小端): +// [HEADER] magic(8) nfuncs(8) ninsns(8) nnatives(8) nstrs(8) ndata(8) +// [FUNCS] 每项: name_len(8) name(pad8, 含结尾 NUL) idx_of_strtab(8) +// nargs(8) ninsns(8) address(8) +// [INSTRS] 每项: opcode(2) type(2) imm(8) = 12 字节 +// [NATIVES] 每项: name_len(8) name(pad8, 含结尾 NUL) idx_of_strtab(8) +// [STRTAB] 每项: slen(8) str(pad8) +// [GDATA] 每项: dsize(8) data(pad8) + +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("sys/mem.spl"); +type File = @import("sys/file.spl"); +type .{ .open_cstr, .write_all, .read_all } = @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, + 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"; }, + .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 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; } + ignore_ptr(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; +} + +// 返回 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; +} + +// ---- dump(输出必须与 stage0 spl_prog_dump 逐字节一致)---- + +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; } + + if @enum2int(ins.opcode) == @enum2int(Op.Jmp) || + @enum2int(ins.opcode) == @enum2int(Op.Bz) || + @enum2int(ins.opcode) == @enum2int(Op.Bnz) { + if !out_s(out, " ; -> ") { ret false; } + if !out_u(out, i + 1 + ins.imm) { ret false; } + } else if @enum2int(ins.opcode) == @enum2int(Op.Call) { + if !out_s(out, " ; nargs=") { ret false; } + if !out_u(out, ins.imm) { ret false; } + if !out_s(out, ", from stack") { ret false; } + } else if @enum2int(ins.opcode) == @enum2int(Op.Calli) { + if !out_s(out, " ; indirect call") { ret false; } + } else if @enum2int(ins.opcode) == @enum2int(Op.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 { + match Vec.get(&self.insns, i) { + .Ok(ptr) => { ret (ptr as *VmIns).*; }, + .Err(error) => { ret VmIns { .opcode = .Error, .ty = .Void, .imm = 0 }; }, + } + ret VmIns { .opcode = .Error, .ty = .Void, .imm = 0 }; +} + +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)); +} + +fn put_u8(out: *ByteBuffer, b: u8): bool { + match ByteBuffer.append_byte(out, b) { .Ok => { ret true; }, .Err(error) => { ret false; } } + ret false; +} + +fn put_u16(out: *ByteBuffer, v: usize): bool { + if !put_u8(out, (v & 255) as u8) { ret false; } + ret put_u8(out, ((v >> 8) & 255) as u8); +} + +fn put_u64(out: *ByteBuffer, v: usize): bool { + var i: usize = 0; + while i < 8 { + if !put_u8(out, ((v >> (i * 8)) & 255) as u8) { ret false; } + i = i + 1; + } + ret true; +} + +fn put_zeros(out: *ByteBuffer, n: usize): bool { + var i: usize = 0; + while i < n { + if !put_u8(out, 0) { ret false; } + i = i + 1; + } + ret true; +} + +fn put_view(out: *ByteBuffer, v: ByteView): bool { + if v.len == 0 { ret true; } + match ByteBuffer.append(out, ByteView.as_slice(v)) { + .Ok => { ret true; }, + .Err(error) => { ret false; }, + } + ret false; +} + +// 写入「带结尾 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); +} + +fn store(self: *Prog, path: ByteView): Status { + var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } + + 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_at(self, i); + if f == null { ret .Err(make(.Io)); } + 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; + while i < ninsns { + var ins = ins_at(self, i); + if !put_u16(&out, @enum2int(ins.opcode) as usize) { ret .Err(make(.Io)); } + if !put_u16(&out, @enum2int(ins.ty) as usize) { ret .Err(make(.Io)); } + if !put_u64(&out, ins.imm) { ret .Err(make(.Io)); } + i = i + 1; + } + + i = 0; + while i < nnatives { + var n = native_at(self, i); + if n == null { ret .Err(make(.Io)); } + 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_at(self, 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 = gdata_at(self, i); + if e == null { ret .Err(make(.Io)); } + 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; + } + + 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; +} + +fn ignore_ptr(p: *any) {} +fn ignore_usize(v: usize) {} + +// ---- 测试 ---- + +fn test_dump_basic() { + var pr: Prog = undefined; + match new() { + .Ok(v) => { pr = v; }, + .Err(error) => { @assert(false, "prog init"); ret; }, + } + defer { drop(&pr); } + + ignore_usize(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); } + + ignore_usize(add_func(&pr, ByteView.from_slice("main"), 0, 0)); + ignore_usize(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(); +} diff --git a/stage2/parser.spl b/stage2/parser.spl index 90d0282..92aab60 100644 --- a/stage2/parser.spl +++ b/stage2/parser.spl @@ -136,6 +136,48 @@ fn parse_error(self: *Parser, tok: *Token, what: []u8) { Diag.err(self.diag, span_of(self, tok), .ParseExpected, what); } +// 转义解码(镜像 stage1 spl_decode_escape):去掉引号,处理 \n \t \r \0 \\ \' \" \xHH。 +fn hex_digit(c: u8): u8 { + if c >= '0' && c <= '9' { ret c - '0'; } + if c >= 'a' && c <= 'f' { ret (c - 'a') + 10; } + if c >= 'A' && c <= 'F' { ret (c - 'A') + 10; } + ret 0; +} + +fn decode_str(t: ByteView): ByteBuffer { + @assert(t.len >= 2 && t.ptr != null, "string literal token must include quotes"); + var out = ByteBuffer.new(); + if t.len < 2 { ret out; } + var i: usize = 1; + var end = t.len - 1; + while i < end { + var c = t.ptr[i]; + if c == '\\' && i + 1 < end { + var e = t.ptr[i + 1]; + if e == 'x' && i + 3 < end { + var hi = hex_digit(t.ptr[i + 2]); + var lo = hex_digit(t.ptr[i + 3]); + match ByteBuffer.append_byte(&out, (hi * 16 + lo) as u8) { + .Ok => {}, .Err(error) => {}, + } + i = i + 4; + } else { + var d: u8 = e; + if e == 'n' { d = '\n'; } + else if e == 't' { d = '\t'; } + else if e == 'r' { d = '\r'; } + else if e == '0' { d = 0; } + match ByteBuffer.append_byte(&out, d) { .Ok => {}, .Err(error) => {} } + i = i + 2; + } + } else { + match ByteBuffer.append_byte(&out, c) { .Ok => {}, .Err(error) => {} } + i = i + 1; + } + } + ret out; +} + fn expect_punct(self: *Parser, punct: Punct, what: []u8): bool { var t = peek(self); if t == null || !kind_is_punct(t.kind, punct) { @@ -355,7 +397,7 @@ fn parse_pattern_atom(self: *Parser): usize { }, .Str => { advance(self); - ret new_node(self, .ExprStringLit(.{ .data = t.text }), t); + ret new_node(self, .ExprStringLit(.{ .data = Ast.push_string(self.ast, decode_str(t.text)) }), t); }, .Keyword(k) => { match k { @@ -1293,7 +1335,7 @@ fn parse_primary(self: *Parser): usize { }, .Str => { advance(self); - ret new_node(self, .ExprStringLit(.{ .data = t.text }), t); + ret new_node(self, .ExprStringLit(.{ .data = Ast.push_string(self.ast, decode_str(t.text)) }), t); }, .Keyword(k) => { if @enum2int(k) == @enum2int(Keyword.True) { diff --git a/stage2/scope.spl b/stage2/scope.spl new file mode 100644 index 0000000..32cb3ca --- /dev/null +++ b/stage2/scope.spl @@ -0,0 +1,224 @@ +// Expect retcode = 0 + +// 作用域树(移植自 stage1/spl_scope.c)。每个作用域是一张名字 -> Symbol 表, +// 节点用 parent 指向父作用域;查找从 current_scope 沿父链上溯(支持遮蔽)。 +// 名字是 ByteView(借用源/AST);Map 复制键值字节,但 ByteView 指向的字节由 +// 调用方保证存活。node 字段是 def.spl 的 DefId。 + +type .{ .Error } = @import("utils/error.spl"); +type .{ .Vec } = @import("utils/vec.spl"); +type .{ .Map } = @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_view, eq_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; } + match Vec.get(&self.scopes, id) { + .Ok(ptr) => { ret ptr as *ScopeNode; }, + .Err(error) => { ret null; }, + } + ret null; +} + +// 新作用域 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(); +} diff --git a/stage2/sema.spl b/stage2/sema.spl new file mode 100644 index 0000000..150950e --- /dev/null +++ b/stage2/sema.spl @@ -0,0 +1,3088 @@ +// Expect retcode = 0 + +// 语义分析(移植自 stage1/spl_sema.c)。持有语言类型表 / 作用域树 / 属性表 / +// comptime 接口;AST 与诊断是借用。阶段:collect_names -> collect_type_unpacks +// -> 模块根聚合 -> collect_types -> collect_signatures -> infer。 +// +// 与 stage1 的差异:诊断消息从简(不逐字复刻);类型系统/def/scope/attr/intrin +// 全部走 stage2 模块;comptime 只走 eval seam(本轮不求值,返回 NotConst)。 + +type .{ .Error, .Status } = @import("utils/error.spl"); +type .{ .Vec } = @import("utils/vec.spl"); +type .{ .Map } = @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 BigInt = @import("utils/mp.spl"); +type Ast = @import("./ast.spl"); +type .{ .Node } = @import("./ast.spl"); +type .{ .Span, .span_none, .Code, .no_file } = @import("./diag.spl"); +type Diag = @import("./diag.spl"); +type .{ .Sources, .set_at } = @import("utils/source.spl"); +type LangType = @import("./lang_type.spl"); +type .{ .LangKind, .LangEntry, .LangMethod, .LangExtra } = @import("./lang_type.spl"); +type TypeTable = @import("./type_table.spl"); +type .{ .TypeNode, .TypeEntry, .Member, .AggKind, .LayoutMode } = @import("./type_table.spl"); +type .{ .DefTable, .DefKind, .DefNode, .DeclState } = @import("./def.spl"); +type .{ .Scope, .Symbol, .SymbolKind } = @import("./scope.spl"); +type .{ .Attr, .AttrArg, .AttrArgKind } = @import("./attr.spl"); +type .{ .Intrin, .IntrinInfo, .IntrinForm, .IntrinResult, .IntrinOps } = @import("./intrin.spl"); +type .{ .Eval, .EvalOutcome, .EvalValue, .DeclSink } = @import("./eval.spl"); +type .{ .hash_usize, .eq_usize } = @import("utils/hash.spl"); +type .{ .Punct, .Token } = @import("./token.spl"); +type .{ .Lexer } = @import("./lexer.spl"); +type .{ .Parser } = @import("./parser.spl"); +type .{ .set_new, .set_add, .set_drop } = @import("utils/source.spl"); + +// 模块加载接口(seam):驱动提供;本轮可为 null。 +type LoadResult = enum { + Ok: *Ast, + Missing, +} + +type Loader = struct { + load: fn(*any, ByteView, ByteView, *Diag): LoadResult, + user: *any, +} + +type Sema = @self; + +ast: *Ast, // 借用 +diag: *Diag, // 借用 +sources: *Sources, // 借用,可为 null(解析 import 的 from 文件名) +types: LangType, +scope: Scope, +attr: Attr, +eval: Eval, +loader: *Loader, // 借用,可为 null +current_aggregate_lang: usize, +current_fn_ret_tid: usize, +current_loop_break_tid: usize, +label_stack: Vec, // ByteView +ir_to_lang: Map, // ir_type_id -> lang_id 缓存(剪枝 lang_from_ir 的线性扫描) + +type SemaResult = enum { + Ok: Sema, + Err: Error, +} + +// ---- 小工具 ---- + +fn list_at(v: Vec, i: usize): usize { + match Vec.get(&v, i) { + .Ok(ptr) => { ret (ptr as *usize).*; }, + .Err(error) => { ret 0; }, + } + ret 0; +} + +fn push_u(v: *Vec, x: usize) { + match Vec.push(v, &x as *u8) { + .Ok => {}, + .Err(error) => {}, + } +} + +fn push_view(v: *Vec, x: ByteView) { + match Vec.push(v, &x as *u8) { + .Ok => {}, + .Err(error) => {}, + } +} + +fn ref_vec(): Vec { + ret Vec.blank(@sizeof(usize), @alignof(usize)); +} + +fn ulist_vec(): Vec { + ret Vec.blank(@sizeof(usize), @alignof(usize)); +} + +fn node(self: *Sema, ref: usize): *Node { + ret Ast.node(self.ast, ref); +} + +fn def_node(self: *Sema, id: usize): *DefNode { + ret LangType.def_node(&self.types, id); +} + +fn ir_entry(self: *Sema, ir_id: usize): *TypeEntry { + ret TypeTable.node(&self.types.ir, ir_id); +} + +fn ir_of(self: *Sema, lang_id: usize): usize { + ret LangType.get_ir_type(&self.types, lang_id); +} + +fn lang_of(self: *Sema, id: usize): *LangEntry { + ret LangType.lang_node(&self.types, id); +} + +fn kind_is(self: *Sema, lang_id: usize, want: LangKind): bool { + var e = LangType.lang_node(&self.types, lang_id); + if e == null { ret false; } + ret @enum2int(e.kind) == @enum2int(want); +} + +fn is_infer(self: *Sema, tid: usize): bool { + ret kind_is(self, tid, .Infer); +} + +fn lang_from_ir(self: *Sema, ir: usize): usize { + if ir == 0 { ret 0; } + + var cached: usize = 0; + match Map.get(&self.ir_to_lang, &ir as *u8, &cached as *u8) { + .Found => { ret cached; }, + .NotFound => {}, + } + + var i: usize = 1; + while i < self.types.lang_table.len { + var e = LangType.lang_node(&self.types, i); + if e != null && e.ir_type_id == ir { + match Map.put(&self.ir_to_lang, &ir as *u8, &i as *u8) { .Ok => {}, .Err(error) => {} } + ret i; + } + i = i + 1; + } + ret 0; +} + +fn make_bool(self: *Sema): usize { ret LangType.make_bool(&self.types); } +fn make_unit(self: *Sema): usize { ret LangType.make_unit(&self.types); } +fn make_never(self: *Sema): usize { ret LangType.make_never(&self.types); } +fn make_any(self: *Sema): usize { ret LangType.make_any(&self.types); } +fn make_infer(self: *Sema): usize { ret LangType.make_infer(&self.types); } +fn ptr_bits(self: *Sema): usize { ret @sizeof(usize) * 8; } +fn make_usize(self: *Sema): usize { ret LangType.make_int(&self.types, ptr_bits(self), false); } +fn make_isize(self: *Sema): usize { ret LangType.make_int(&self.types, ptr_bits(self), true); } + +fn def_is_type(self: *Sema, id: usize): bool { + ret LangType.def_is_type(&self.types, id); +} + +fn def_state_is(d: *DefNode, want: DeclState): bool { + ret @enum2int(d.state) == @enum2int(want); +} + +// ---- 诊断(消息从简;类型名走 dump 到缓冲)---- + +fn diag(self: *Sema, span: Span, code: Code, msg: []u8) { + Diag.err(self.diag, span, code, msg); +} + +fn diag_name(self: *Sema, span: Span, code: Code, prefix: []u8, name: ByteView) { + var b = ByteBuffer.new(); + defer { ByteBuffer.drop(&b); } + match ByteBuffer.append(&b, prefix) { .Ok => {}, .Err(e) => {} } + match ByteBuffer.append(&b, ByteView.as_slice(name)) { .Ok => {}, .Err(e) => {} } + Diag.err(self.diag, span, code, ByteBuffer.as_slice(b)); +} + +fn diag_ty(self: *Sema, span: Span, code: Code, prefix: []u8, tid: usize) { + var b = ByteBuffer.new(); + defer { ByteBuffer.drop(&b); } + match ByteBuffer.append(&b, prefix) { .Ok => {}, .Err(e) => {} } + LangType.dump(&self.types, tid, &b); + Diag.err(self.diag, span, code, ByteBuffer.as_slice(b)); +} + +fn diag_ty2(self: *Sema, span: Span, code: Code, prefix: []u8, a: usize, b: usize) { + var buf = ByteBuffer.new(); + defer { ByteBuffer.drop(&buf); } + match ByteBuffer.append(&buf, prefix) { .Ok => {}, .Err(e) => {} } + LangType.dump(&self.types, a, &buf); + match ByteBuffer.append(&buf, " vs ") { .Ok => {}, .Err(e) => {} } + LangType.dump(&self.types, b, &buf); + Diag.err(self.diag, span, code, ByteBuffer.as_slice(buf)); +} + +// ---- 生命周期 ---- + +fn new(ast: *Ast, diag: *Diag): SemaResult { + var types: LangType = undefined; + match LangType.new() { + .Ok(v) => { types = v; }, + .Err(error) => { ret .Err(error); }, + } + var scope: Scope = undefined; + match Scope.new() { + .Ok(v) => { scope = v; }, + .Err(error) => { LangType.drop(&types); ret .Err(error); }, + } + var attr: Attr = undefined; + match Attr.new() { + .Ok(v) => { attr = v; }, + .Err(error) => { + Scope.drop(&scope); + LangType.drop(&types); + ret .Err(error); + }, + } + var s = Sema { + .ast = ast, + .diag = diag, + .sources = null, + .types = types, + .scope = scope, + .attr = attr, + .eval = Eval.new(null, diag, false), + .loader = null, + .current_aggregate_lang = 0, + .current_fn_ret_tid = 0, + .current_loop_break_tid = 0, + .label_stack = Vec.blank(@sizeof(ByteView), @alignof(ByteView)), + .ir_to_lang = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize), + hash_usize, eq_usize), + }; + ret .Ok(s); +} + +fn drop(self: *Sema) { + Map.drop(&self.ir_to_lang); + Vec.drop(&self.label_stack); + Attr.drop(&self.attr); + Scope.drop(&self.scope); + LangType.drop(&self.types); +} + +// ---- 作用域 ---- + +fn push_scope(self: *Sema) { + var id = Scope.alloc(&self.scope); + self.scope.current_scope = id; +} + +fn pop_scope(self: *Sema) { + var n = Scope.node(&self.scope, self.scope.current_scope); + if n != null { + self.scope.current_scope = n.parent; + } +} + +// ---- 属性 ---- + +fn node_attr_raw(self: *Sema, n: *Node, raw: Vec) { + var i: usize = 0; + while i < raw.len { + var a = node(self, list_at(raw, i)); + if a != null { + var id = resolve_attr_item(self, a); + if id != 0 { push_u(&n.attrs, id); } + } + i = i + 1; + } +} + +fn resolve_attr_item(self: *Sema, a: *Node): usize { + var name = ByteView.empty(); + var cargs = ref_vec(); + var found = false; + match a.body { + .TagItem(t) => { + name = t.name; + cargs = t.args; + found = true; + }, + .ExternalTagItem(t) => { + name = t.name; + cargs = t.args; + found = true; + }, + _ => {}, + } + if !found { ret 0; } + + var idx = Attr.lookup(&self.attr, ByteView.empty(), name); + if idx == 0 { + diag_name(self, a.span, .UnknownAttr, "unknown attribute `", name); + ret 0; + } + var info = Attr.info_at(&self.attr, idx); + if info == null { ret 0; } + var nargs = cargs.len; + if (nargs as i32) < info.min_args || (info.max_args >= 0 && (nargs as i32) > info.max_args) { + diag(self, a.span, .UnknownAttr, "attribute arg count mismatch"); + ret 0; + } + + var args = Vec.blank(@sizeof(AttrArg), @alignof(AttrArg)); + var i: usize = 0; + while i < cargs.len { + var arg = node(self, list_at(cargs, i)); + if arg == null { i = i + 1; continue; } + match arg.body { + .ExprIdent(e) => { + var av: AttrArg = .Str(e.name); + match Vec.push(&args, &av as *u8) { .Ok => {}, .Err(er) => {} } + }, + .ExprStringLit(sl) => { + var av: AttrArg = .Str(sl.data); + match Vec.push(&args, &av as *u8) { .Ok => {}, .Err(er) => {} } + }, + .ExprIntLit(iv) => { + var num: i64 = 0; + match BigInt.to_i64(&num, iv.value) { .Ok => {}, .Err(er) => {} } + var av: AttrArg = .Int(num); + match Vec.push(&args, &av as *u8) { .Ok => {}, .Err(er) => {} } + }, + _ => { + diag(self, arg.span, .UnknownAttr, "unsupported attribute arg"); + }, + } + i = i + 1; + } + var id = Attr.alloc(&self.attr, idx, args); + ret id; +} + +fn resolve_attrs(self: *Sema, n: *Node) { + if n.attrs.len != 0 { ret; } + match n.body { + .FnDecl(x) => { node_attr_raw(self, n, x.tag_list); }, + .VarDecl(x) => { node_attr_raw(self, n, x.attr_list); }, + .ConstDecl(x) => { node_attr_raw(self, n, x.attr_list); }, + .TypeDecl(x) => { node_attr_raw(self, n, x.attr_list); }, + .MemberDecl(x) => { node_attr_raw(self, n, x.attr_list); }, + .ParamDecl(x) => { node_attr_raw(self, n, x.attr_list); }, + .TopLevelBlock(x) => { node_attr_raw(self, n, x.tag_list); }, + _ => {}, + } +} + +// ---- 聚合构建 ---- + +fn build_aggregate(self: *Sema, te: *Node, owner_def_id: usize): usize { + var kind_name = ByteView.empty(); + match te.body { + .TypeAggregate(x) => { kind_name = x.kind_name; }, + _ => {}, + } + var lk: LangKind = .Struct; + var tk: TypeNode = .Struct(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, + .variants = ref_vec() }); + if ByteView.eq(kind_name, ByteView.from_slice("union")) { + lk = .Union; + } else if ByteView.eq(kind_name, ByteView.from_slice("enum")) { + lk = .Enum; + } else if ByteView.eq(kind_name, ByteView.from_slice("shape")) { + lk = .Shape; + } + + var agg_ir = LangType.reserve_ir(&self.types); + var ln = LangEntry { + .hash_next = 0, + .kind = lk, + .ir_type_id = agg_ir, + .methods = LangType.empty_methods(), + .extra = .None, + }; + var agg_lang = LangType.push(&self.types, &ln); + if owner_def_id != 0 { + var d = def_node(self, owner_def_id); + if d != null { d.lang_id = agg_lang; } + } + + var saved = self.current_aggregate_lang; + self.current_aggregate_lang = agg_lang; + + var members = Vec.blank(@sizeof(Member), @alignof(Member)); + var methods = Vec.blank(@sizeof(LangMethod), @alignof(LangMethod)); + + var mlist = ref_vec(); + match te.body { + .TypeAggregate(x) => { mlist = x.members; }, + _ => {}, + } + var i: usize = 0; + while i < mlist.len { + var mref = list_at(mlist, i); + var mn = node(self, mref); + if mn != null { + match mn.body { + .MemberDecl(md) => { + var mtid: usize = 0; + if md.type_expr != 0 { mtid = infer(self, md.type_expr); } + var mm = Member { .name = md.name, .type_id = ir_of(self, mtid), + .ast_node_ref = mref }; + match Vec.push(&members, &mm as *u8) { .Ok => {}, .Err(er) => {} } + }, + .FnDecl(fd) => { + var fn_def = mn.resolved_def_id; + if fn_def == 0 { + fn_def = LangType.alloc_def(&self.types, .Fn, fd.name, mn.span, + self.ast as *any, mref); + mn.resolved_def_id = fn_def; + } + var fn_d = def_node(self, fn_def); + if fn_d != null { fn_d.owner_def = owner_def_id; } + var meth = LangMethod { .name = fd.name, .def_id = fn_def }; + match Vec.push(&methods, &meth as *u8) { .Ok => {}, .Err(er) => {} } + }, + .TypeDecl(td) => { + var tpat = node(self, td.pattern); + if tpat != null && is_pattern_ident(tpat) { + var tname = pattern_name(tpat); + var tdef = mn.resolved_def_id; + if tdef == 0 { + var dk: DefKind = .Scalar; + var tek = node(self, td.type_expr); + if tek != null && is_type_aggregate(tek) { dk = .Agg; } + tdef = LangType.alloc_def(&self.types, dk, tname, mn.span, + self.ast as *any, mref); + mn.resolved_def_id = tdef; + } + var meth = LangMethod { .name = tname, .def_id = tdef }; + match Vec.push(&methods, &meth as *u8) { .Ok => {}, .Err(er) => {} } + } + }, + _ => {}, + } + } + i = i + 1; + } + + self.current_aggregate_lang = saved; + + // IR 节点 + if @enum2int(lk) == @enum2int(LangKind.Enum) { + var tag_ir = TypeTable.make_int(&self.types.ir, 32, true); + tk = .Enum(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = tag_ir, + .variants = members }); + } else if @enum2int(lk) == @enum2int(LangKind.Union) { + tk = .Union(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, .variants = members }); + } else if @enum2int(lk) == @enum2int(LangKind.Shape) { + tk = .Shape(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, .variants = members }); + } else { + tk = .Struct(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, .variants = members }); + } + TypeTable.define(&self.types.ir, agg_ir, &tk); + + var lslot = LangType.lang_node(&self.types, agg_lang); + if lslot != null { + Vec.drop(&lslot.methods); + lslot.methods = methods; + } + ret agg_lang; +} + +fn build_container_aggregate(self: *Sema, n: *Node, owner_def_id: usize): usize { + var agg_ir = LangType.reserve_ir(&self.types); + var ln = LangEntry { + .hash_next = 0, + .kind = .Struct, + .ir_type_id = agg_ir, + .methods = LangType.empty_methods(), + .extra = .None, + }; + var agg_lang = LangType.push(&self.types, &ln); + if owner_def_id != 0 { + var d = def_node(self, owner_def_id); + if d != null { d.lang_id = agg_lang; } + } + + var saved = self.current_aggregate_lang; + self.current_aggregate_lang = agg_lang; + + var members = Vec.blank(@sizeof(Member), @alignof(Member)); + var methods = Vec.blank(@sizeof(LangMethod), @alignof(LangMethod)); + + var decls = ref_vec(); + match n.body { + .ContainerMembers(x) => { decls = x.declarations; }, + _ => {}, + } + var i: usize = 0; + while i < decls.len { + var dref = list_at(decls, i); + var d = node(self, dref); + if d != null { + match d.body { + .MemberDecl(md) => { + var mtid: usize = 0; + if md.type_expr != 0 { mtid = infer(self, md.type_expr); } + var mm = Member { .name = md.name, .type_id = ir_of(self, mtid), + .ast_node_ref = dref }; + match Vec.push(&members, &mm as *u8) { .Ok => {}, .Err(er) => {} } + }, + .FnDecl(fd) => { + var fn_def = d.resolved_def_id; + if fn_def != 0 { + var fn_d = def_node(self, fn_def); + if fn_d != null { fn_d.owner_def = owner_def_id; } + var meth = LangMethod { .name = fd.name, .def_id = fn_def }; + match Vec.push(&methods, &meth as *u8) { .Ok => {}, .Err(er) => {} } + } + }, + .TypeDecl(td) => { + var tdef = d.resolved_def_id; + if tdef != 0 { + var tpat = node(self, td.pattern); + if tpat != null && is_pattern_ident(tpat) { + var meth = LangMethod { .name = pattern_name(tpat), .def_id = tdef }; + match Vec.push(&methods, &meth as *u8) { .Ok => {}, .Err(er) => {} } + } + } + }, + .VarDecl(vd) => { container_var_method(self, vd.pattern, &methods); }, + .ConstDecl(cd) => { container_var_method(self, cd.pattern, &methods); }, + _ => {}, + } + } + i = i + 1; + } + + self.current_aggregate_lang = saved; + + var tk: TypeNode = .Struct(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, + .variants = members }); + TypeTable.define(&self.types.ir, agg_ir, &tk); + + var lslot = LangType.lang_node(&self.types, agg_lang); + if lslot != null { + Vec.drop(&lslot.methods); + lslot.methods = methods; + } + ret agg_lang; +} + +fn container_var_method(self: *Sema, pat_ref: usize, methods: *Vec) { + var pat = node(self, pat_ref); + if pat == null || !is_pattern_ident(pat) { ret; } + var vdef = pat.resolved_def_id; + if vdef == 0 { ret; } + var meth = LangMethod { .name = pattern_name(pat), .def_id = vdef }; + match Vec.push(methods, &meth as *u8) { .Ok => {}, .Err(er) => {} } +} + +fn is_pattern_ident(n: *Node): bool { + match n.body { .PatternIdent(x) => { ret true; }, _ => { ret false; } } + ret false; +} +fn is_type_aggregate(n: *Node): bool { + match n.body { .TypeAggregate(x) => { ret true; }, _ => { ret false; } } + ret false; +} +fn is_param_decl(n: *Node): bool { + match n.body { .ParamDecl(x) => { ret true; }, _ => { ret false; } } + ret false; +} +fn is_type_decl(n: *Node): bool { + match n.body { .TypeDecl(x) => { ret true; }, _ => { ret false; } } + ret false; +} +fn is_pattern_field(n: *Node): bool { + match n.body { .PatternField(x) => { ret true; }, _ => { ret false; } } + ret false; +} +fn is_expr_string_lit(n: *Node): bool { + match n.body { .ExprStringLit(x) => { ret true; }, _ => { ret false; } } + ret false; +} +fn is_expr_ident(n: *Node): bool { + match n.body { .ExprIdent(x) => { ret true; }, _ => { ret false; } } + ret false; +} +fn is_expr_int_lit(n: *Node): bool { + match n.body { .ExprIntLit(x) => { ret true; }, _ => { ret false; } } + ret false; +} + +fn pattern_name(pat: *Node): ByteView { + match pat.body { + .PatternIdent(x) => { ret x.name; }, + _ => { ret ByteView.empty(); }, + } +} + +// ---- @import ---- + +fn sema_import(self: *Sema, n: *Node, args: Vec): usize { + if self.loader == null { + diag(self, n.span, .Internal, "@import: no module loader configured"); + ret 0; + } + if args.len != 1 { + diag(self, n.span, .IntrinArgCount, "@import expects 1 argument"); + ret 0; + } + var arg = node(self, list_at(args, 0)); + if arg == null || !is_expr_string_lit(arg) { + diag(self, n.span, .TypeMismatch, "@import expects a string literal"); + ret 0; + } + var path = ByteView.empty(); + match arg.body { + .ExprStringLit(sl) => { path = sl.data; }, + _ => {}, + } + var fname = ByteView.empty(); + if self.sources != null && n.span.file != no_file() { + var src = set_at(self.sources, n.span.file); + if src != null { fname = src.fname; } + } + var loaded = self.loader.load(self.loader.user, fname, path, self.diag); + var mod_ast: *Ast = null; + match loaded { + .Ok(a) => { mod_ast = a; }, + .Missing => { ret 0; }, + } + if mod_ast == null { ret 0; } + var mod_root = Ast.node(mod_ast, mod_ast.root); + if mod_root == null { ret 0; } + var cm_ref: usize = 0; + match mod_root.body { + .Root(r) => { cm_ref = r.members; }, + _ => {}, + } + if cm_ref == 0 { ret 0; } + var cm = Ast.node(mod_ast, cm_ref); + if cm != null && cm.resolved_def_id != 0 { + var d = LangType.def_node(&self.types, cm.resolved_def_id); + if d != null && d.lang_id != 0 { ret d.lang_id; } + } + + var saved_ast = self.ast; + var saved_root = self.scope.root_scope; + var saved_cur = self.scope.current_scope; + var saved_agg = self.current_aggregate_lang; + + self.ast = mod_ast; + self.scope.root_scope = 0; + self.scope.current_scope = 0; + self.current_aggregate_lang = 0; + + run_module(self, cm_ref); + + var t: usize = 0; + var cm_after = Ast.node(mod_ast, cm_ref); + if cm_after != null && cm_after.resolved_def_id != 0 { + var d = LangType.def_node(&self.types, cm_after.resolved_def_id); + if d != null { t = d.lang_id; } + } + + self.ast = saved_ast; + self.scope.root_scope = saved_root; + self.scope.current_scope = saved_cur; + self.current_aggregate_lang = saved_agg; + ret t; +} + +// ---- 声明求值 ---- + +fn eval_decl(self: *Sema, def_id: usize): usize { + var d = def_node(self, def_id); + if d == null { ret 0; } + if def_state_is(d, .Resolved) || def_state_is(d, .Error) { ret d.lang_id; } + if d.lang_id != 0 { + if def_state_is(d, .Resolving) { ret d.lang_id; } + d.state = .Resolved; + ret d.lang_id; + } + if def_state_is(d, .Resolving) { + diag_name(self, d.span, .CircularDep, "circular dependency on `", d.name); + d.state = .Error; + ret 0; + } + + var owner = d.owner; + var owner_def_id = d.owner_def; + var node_ref = d.ast_node_ref; + var dbg = d.span; + if owner == null { + diag(self, dbg, .Internal, "def has no owner_ast"); + d.state = .Error; + ret 0; + } + + var saved_ast = self.ast; + self.ast = owner as *Ast; + + d = def_node(self, def_id); + if d != null { d.state = .Resolving; } + + var n = node(self, node_ref); + var result: usize = 0; + if n != null { + match n.body { + .TypeDecl(td) => { + var pat = node(self, td.pattern); + if pat != null && is_pattern_ident(pat) { + if td.type_expr != 0 { + var te = node(self, td.type_expr); + if te != null && is_type_aggregate(te) { + result = build_aggregate(self, te, def_id); + } else { + result = infer(self, td.type_expr); + } + } + n.resolved_lang_tid = result; + } + }, + .FnDecl(fd) => { + var saved_agg = self.current_aggregate_lang; + if owner_def_id != 0 { + var owner_def = def_node(self, owner_def_id); + if owner_def != null && owner_def.lang_id != 0 { + self.current_aggregate_lang = owner_def.lang_id; + } + } + result = infer_fn_signature(self, n); + self.current_aggregate_lang = saved_agg; + }, + .VarDecl(vd) => { + if vd.type_expr != 0 { result = infer(self, vd.type_expr); } + else if vd.init_expr != 0 { result = infer(self, vd.init_expr); } + }, + .ConstDecl(cd) => { + if cd.type_expr != 0 { result = infer(self, cd.type_expr); } + else if cd.init_expr != 0 { result = infer(self, cd.init_expr); } + }, + .ContainerMembers(x) => { + result = build_container_aggregate(self, n, def_id); + n.resolved_lang_tid = result; + }, + _ => {}, + } + } + + var fd = def_node(self, def_id); + if fd != null { + if result != 0 { fd.lang_id = result; } + fd.state = if result != 0 { .Resolved } else { .Error }; + } + + self.ast = saved_ast; + ret result; +} + +fn infer_fn_signature(self: *Sema, n: *Node): usize { + if n.resolved_def_id == 0 { ret 0; } + var d = def_node(self, n.resolved_def_id); + if d != null && d.lang_id != 0 { ret d.lang_id; } + + var params = ref_vec(); + var params_list = ref_vec(); + var ret_ref: usize = 0; + match n.body { + .FnDecl(fd) => { + params_list = fd.params; + ret_ref = fd.ret_type; + }, + _ => {}, + } + var i: usize = 0; + while i < params_list.len { + var pd = node(self, list_at(params_list, i)); + if pd != null && is_param_decl(pd) { + var tref: usize = 0; + match pd.body { .ParamDecl(x) => { tref = x.type_expr; }, _ => {} } + if tref != 0 { + push_u(¶ms, infer(self, tref)); + } else { + push_u(¶ms, LangType.make_varargs(&self.types)); + } + } else { + push_u(¶ms, LangType.make_varargs(&self.types)); + } + i = i + 1; + } + var ret_tid = infer(self, ret_ref); + if ret_tid == 0 { ret_tid = make_unit(self); } + var fn_tid = LangType.make_fn(&self.types, ret_tid, params); + LangType.resolve_def(&self.types, n.resolved_def_id, fn_tid); + ret fn_tid; +} + +fn collect_names(self: *Sema, ref: usize) { + var n = node(self, ref); + if n == null { ret; } + match n.body { + .ContainerMembers(cm) => { + if self.scope.root_scope == 0 { + self.scope.root_scope = Scope.alloc(&self.scope); + } + self.scope.current_scope = self.scope.root_scope; + var mod_scope = Scope.alloc(&self.scope); + self.scope.current_scope = mod_scope; + + var file_def = LangType.alloc_def(&self.types, .Agg, ByteView.from_slice("$file"), + n.span, self.ast as *any, ref); + n.resolved_def_id = file_def; + var file_sym = Symbol { .name = ByteView.from_slice("$file"), .kind = .Type, + .node = file_def }; + ignore_bool(Scope.insert(&self.scope, mod_scope, file_sym)); + + var i: usize = 0; + while i < cm.declarations.len { + collect_names(self, list_at(cm.declarations, i)); + i = i + 1; + } + }, + .TypeDecl(td) => { + var pat = node(self, td.pattern); + if pat == null || !is_pattern_ident(pat) { ret; } + var name = pattern_name(pat); + var te = node(self, td.type_expr); + if te == null { + diag_name(self, n.span, .TypeMismatch, "type declaration missing type expr: `", + name); + ret; + } + var sym = Symbol { .name = name, .kind = .Type, .node = 0 }; + if is_type_aggregate(te) { + sym.node = LangType.alloc_def(&self.types, .Agg, name, te.span, + self.ast as *any, ref); + } else { + sym.node = LangType.alloc_def(&self.types, .Scalar, name, te.span, + self.ast as *any, ref); + } + n.resolved_def_id = sym.node; + if !Scope.insert(&self.scope, self.scope.current_scope, sym) { + diag_name(self, n.span, .DuplicateSymbol, "duplicate symbol `", name); + } + }, + .FnDecl(fd) => { + var sym = Symbol { .name = fd.name, .kind = .Fn, .node = 0 }; + sym.node = LangType.alloc_def(&self.types, .Fn, fd.name, n.span, + self.ast as *any, ref); + n.resolved_def_id = sym.node; + if !Scope.insert(&self.scope, self.scope.current_scope, sym) { + diag_name(self, n.span, .DuplicateSymbol, "duplicate symbol `", fd.name); + } + }, + .VarDecl(vd) => { collect_var(self, n, vd.pattern); }, + .ConstDecl(cd) => { collect_var(self, n, cd.pattern); }, + .TopLevelBlock(tb) => { + if tb.block != 0 { collect_names(self, tb.block); } + }, + _ => {}, + } +} + +fn collect_var(self: *Sema, n: *Node, pat_ref: usize) { + var pat = node(self, pat_ref); + if pat == null || !is_pattern_ident(pat) { + diag(self, n.span, .TypeMismatch, "container-level var only supports simple identifier"); + ret; + } + var name = pattern_name(pat); + var sym = Symbol { .name = name, .kind = .Var, .node = 0 }; + sym.node = LangType.alloc_def(&self.types, .Var, name, n.span, self.ast as *any, pat_ref); + pat.resolved_def_id = sym.node; + if !Scope.insert(&self.scope, self.scope.current_scope, sym) { + diag_name(self, n.span, .DuplicateSymbol, "duplicate symbol `", name); + } +} + +fn collect_types(self: *Sema, ref: usize) { + var n = node(self, ref); + if n == null { ret; } + match n.body { + .ContainerMembers(cm) => { + var i: usize = 0; + while i < cm.declarations.len { + var d = node(self, list_at(cm.declarations, i)); + if d != null && is_type_decl(d) { + match d.body { + .TypeDecl(td) => { + var pat = node(self, td.pattern); + if pat != null && is_pattern_ident(pat) && + d.resolved_def_id != 0 { + ignore_usize(eval_decl(self, d.resolved_def_id)); + } + }, + _ => {}, + } + } + i = i + 1; + } + }, + .TopLevelBlock(tb) => { + if tb.block != 0 { collect_types(self, tb.block); } + }, + _ => {}, + } +} + +fn collect_signatures(self: *Sema) { + var i: usize = 1; + while i < self.types.defs.table.len { + var d = def_node(self, i); + if d != null && d.owner == (self.ast as *any) && + @enum2int(d.kind) == @enum2int(DefKind.Fn) && + def_state_is(d, .Unresolved) { + ignore_usize(eval_decl(self, i)); + } + i = i + 1; + } +} + +fn collect_type_unpacks(self: *Sema, ref: usize) { + var n = node(self, ref); + if n == null { ret; } + match n.body { + .ContainerMembers(cm) => { + var i: usize = 0; + while i < cm.declarations.len { + var d = node(self, list_at(cm.declarations, i)); + if d != null && is_type_decl(d) { + match d.body { + .TypeDecl(td) => { + var pat = node(self, td.pattern); + if pat != null && !is_pattern_ident(pat) { + do_type_unpack(self, d, pat, td.type_expr); + } + }, + _ => {}, + } + } + i = i + 1; + } + }, + .TopLevelBlock(tb) => { + if tb.block != 0 { collect_type_unpacks(self, tb.block); } + }, + _ => {}, + } +} + +// ---- 类型上下文(intrinsic 参数)---- + +fn infer_type_context(self: *Sema, ref: usize): usize { + var n = node(self, ref); + if n == null { ret 0; } + match n.body { + .ExprIdent(x) => { + var builtin = LangType.lookup_builtin(&self.types, x.name); + if builtin != 0 { + n.resolved_lang_tid = builtin; + ret builtin; + } + var sym = Symbol { .name = ByteView.empty(), .kind = .Error, .node = 0 }; + if !Scope.find(&self.scope, x.name, &sym) || @enum2int(sym.kind) != @enum2int(SymbolKind.Type) { + diag_name(self, n.span, .TypeMismatch, "`", x.name); + ret 0; + } + if !def_is_type(self, sym.node) { + diag_name(self, n.span, .TypeMismatch, "not a type: `", x.name); + ret 0; + } + n.resolved_def_id = sym.node; + var t = eval_decl(self, sym.node); + n.resolved_lang_tid = t; + ret t; + }, + .ExprField(x) => { + var owner_id = infer_type_context(self, x.obj); + var owner = lang_of(self, owner_id); + if owner == null { ret 0; } + var i: usize = 0; + while i < owner.methods.len { + var m = method_at(&owner.methods, i); + if m != null && ByteView.eq(m.name, x.name) { + if !def_is_type(self, m.def_id) { ret 0; } + n.resolved_def_id = m.def_id; + var t = eval_decl(self, m.def_id); + n.resolved_lang_tid = t; + ret t; + } + i = i + 1; + } + diag_name(self, n.span, .TypeMismatch, "not a type member `", x.name); + ret 0; + }, + .ExprUnary(x) => { + if punct_is(x.op, .Mul) { + var pointee = infer_type_context(self, x.operand); + if pointee == 0 { ret 0; } + var r = LangType.make_ptr(&self.types, pointee); + n.resolved_lang_tid = r; + ret r; + } + ret 0; + }, + .ExprTuple(x) => { + var elems = ref_vec(); + var i: usize = 0; + while i < x.items.len { + var e = infer_type_context(self, list_at(x.items, i)); + if e == 0 { ret 0; } + push_u(&elems, e); + i = i + 1; + } + var r = LangType.make_tuple(&self.types, elems); + n.resolved_lang_tid = r; + ret r; + }, + .ExprComptimeCall(x) => { + var r = infer(self, ref); + n.resolved_lang_tid = r; + ret r; + }, + _ => {}, + } + diag(self, n.span, .TypeMismatch, "intrinsic argument is not a type expression"); + ret 0; +} + +// ---- intrinsic 调用 ---- + +fn punct_is(op: Punct, want: Punct): bool { + ret @enum2int(op) == @enum2int(want); +} + +fn is_assign_arith(op: Punct): bool { + match op { + .AddAssign => { ret true; }, + .SubAssign => { ret true; }, + .MulAssign => { ret true; }, + .DivAssign => { ret true; }, + .ModAssign => { ret true; }, + .AndAssign => { ret true; }, + .OrAssign => { ret true; }, + .XorAssign => { ret true; }, + .LShAssign => { ret true; }, + .RShAssign => { ret true; }, + _ => { ret false; }, + } + ret false; +} + +fn is_cmp_op(op: Punct): bool { + match op { + .Eq => { ret true; }, + .Neq => { ret true; }, + .Lt => { ret true; }, + .Le => { ret true; }, + .Gt => { ret true; }, + .Ge => { ret true; }, + _ => { ret false; }, + } + ret false; +} + +fn is_shift_op(op: Punct): bool { + match op { + .LSh => { ret true; }, + .RSh => { ret true; }, + _ => { ret false; }, + } + ret false; +} + +fn is_addsub(op: Punct): bool { + ret punct_is(op, .Add) || punct_is(op, .Sub); +} + +fn intrin_is(a: Intrin, b: Intrin): bool { + ret @enum2int(a) == @enum2int(b); +} + +fn intrin_call(self: *Sema, n: *Node, in: Intrin, args: Vec): usize { + var info = IntrinOps.info(in); + var nargs = args.len; + if (nargs as i32) < info.min_args || (info.max_args >= 0 && (nargs as i32) > info.max_args) { + diag(self, n.span, .IntrinArgCount, "intrinsic argument count mismatch"); + ret 0; + } + + if intrin_is(in, .BitSizeof) || intrin_is(in, .Sizeof) || intrin_is(in, .Alignof) { + if infer_type_context(self, list_at(args, 0)) == 0 { ret 0; } + ret make_usize(self); + } + if intrin_is(in, .Assert) { + var bt = make_bool(self); + ignore_usize(coerce(self, list_at(args, 0), bt)); + if nargs == 2 { ignore_usize(infer(self, list_at(args, 1))); } + ret make_unit(self); + } + if intrin_is(in, .IntCast) || intrin_is(in, .Truncate) || intrin_is(in, .BitCast) || + intrin_is(in, .FloatCast) || intrin_is(in, .Float2Int) || intrin_is(in, .Int2Float) || + intrin_is(in, .Int2Ptr) || intrin_is(in, .PtrCast) { + var src = infer(self, list_at(args, 0)); + if src == 0 { ret 0; } + var dst = infer(self, list_at(args, 1)); + if dst == 0 { ret 0; } + var ft = ir_of(self, src); + var tt = ir_of(self, dst); + var expected = IntrinOps.lookup_cast(&self.types.ir, ft, tt); + if intrin_is(expected, .None) { + diag_name(self, n.span, .CastInvalid, "incompatible cast `", info.name); + ret 0; + } + n.resolved_lang_tid = dst; + ret dst; + } + if intrin_is(in, .Bool2Int) || intrin_is(in, .Ptr2Int) { + ignore_usize(infer(self, list_at(args, 0))); + if nargs >= 2 { + var dst = infer(self, list_at(args, 1)); + if dst == 0 { ret 0; } + n.resolved_lang_tid = dst; + ret dst; + } + var r = make_usize(self); + n.resolved_lang_tid = r; + ret r; + } + if intrin_is(in, .Enum2Int) { + var src = infer(self, list_at(args, 0)); + if src == 0 { ret 0; } + var sn = lang_of(self, src); + if sn == null || !kind_is(self, src, .Enum) { + diag(self, n.span, .TypeMismatch, "@enum2int requires an enum operand"); + ret 0; + } + var sir = ir_entry(self, sn.ir_type_id); + var tag_ir: usize = 0; + if sir != null { + match sir.node { + .Enum(a) => { tag_ir = a.tag_type; }, + _ => {}, + } + } + if tag_ir == 0 { + diag(self, n.span, .Internal, "@enum2int: enum has no tag type"); + ret 0; + } + var tag = lang_from_ir(self, tag_ir); + n.resolved_lang_tid = tag; + ret tag; + } + if intrin_is(in, .FieldCount) { + if infer_type_context(self, list_at(args, 0)) == 0 { ret 0; } + ret make_usize(self); + } + if intrin_is(in, .OffsetOf) { + var agg_lang = infer_type_context(self, list_at(args, 0)); + if agg_lang == 0 { ret 0; } + var agg_ir = ir_of(self, agg_lang); + var fnode = node(self, list_at(args, 1)); + if fnode == null { ret 0; } + if is_expr_ident(fnode) { + var fname = ByteView.empty(); + match fnode.body { .ExprIdent(x) => { fname = x.name; }, _ => {} } + var idx: usize = 0; + if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, fname, &idx) { + diag_name(self, n.span, .NoField, "no field `", fname); + ret 0; + } + } else if !is_expr_int_lit(fnode) { + diag(self, n.span, .TypeMismatch, "@offsetof expects field name or index"); + ret 0; + } + ret make_usize(self); + } + ret 0; +} + +fn intrin_field(self: *Sema, n: *Node, in: Intrin): usize { + var info = IntrinOps.info(in); + match info.result { + .Usize => { ret make_usize(self); }, + .Bool => { ret make_bool(self); }, + .Unit => { ret make_unit(self); }, + } + ret 0; +} + +// ---- 块 / 模式 / 解包 ---- + +fn block_body(self: *Sema, n: *Node, expected: usize): usize { + var stmts = ref_vec(); + var tail: usize = 0; + match n.body { + .ExprBlock(x) => { + stmts = x.stmts; + tail = x.tail_expr; + }, + _ => {}, + } + push_scope(self); + var i: usize = 0; + while i < stmts.len { + ignore_usize(infer(self, list_at(stmts, i))); + i = i + 1; + } + var r: usize = 0; + if tail != 0 { + if expected != 0 { + r = coerce(self, tail, expected); + if r == 0 { + pop_scope(self); + ret 0; + } + } else { + r = infer(self, tail); + } + } else { + r = make_unit(self); + } + pop_scope(self); + n.resolved_lang_tid = r; + ret r; +} + +fn pattern_is_literal(n: *Node): bool { + match n.body { + .ExprIntLit(x) => { ret true; }, + .ExprFloatLit(x) => { ret true; }, + .ExprCharLit(x) => { ret true; }, + .ExprStringLit(x) => { ret true; }, + .ExprBoolLit(x) => { ret true; }, + .ExprNullLit => { ret true; }, + _ => { ret false; }, + } + ret false; +} + +fn bind_pattern(self: *Sema, pat_ref: usize, matched_tid: usize) { + if pat_ref == 0 { ret; } + var pat = node(self, pat_ref); + if pat == null { ret; } + + if pattern_is_literal(pat) { + var lt = infer(self, pat_ref); + if lt != 0 && matched_tid != 0 { + ignore_usize(coerce(self, pat_ref, matched_tid)); + } + ret; + } + + match pat.body { + .PatternIdent(x) => { + if x.name.len == 0 { ret; } + if ByteView.eq(x.name, ByteView.from_slice("_")) { ret; } + if pat.resolved_def_id != 0 { + LangType.resolve_def(&self.types, pat.resolved_def_id, matched_tid); + pat.resolved_lang_tid = matched_tid; + ret; + } + var sym = Symbol { .name = x.name, .kind = .Var, .node = 0 }; + sym.node = LangType.alloc_def(&self.types, .Var, x.name, pat.span, + self.ast as *any, pat_ref); + LangType.resolve_def(&self.types, sym.node, matched_tid); + if !Scope.insert(&self.scope, self.scope.current_scope, sym) { + diag_name(self, pat.span, .DuplicateSymbol, "duplicate symbol `", x.name); + } + pat.resolved_def_id = sym.node; + pat.resolved_lang_tid = matched_tid; + }, + .PatternUnderscore => {}, + .PatternList(x) => { + var i: usize = 0; + while i < x.items.len { + bind_pattern(self, list_at(x.items, i), matched_tid); + i = i + 1; + } + }, + .PatternTuple(x) => { + var tn = lang_of(self, matched_tid); + if tn == null || !kind_is(self, matched_tid, .Tuple) { + diag(self, pat.span, .TypeMismatch, "tuple pattern requires tuple value"); + ret; + } + var elem_tids = ref_vec(); + match tn.extra { + .Tuple(v) => { elem_tids = v; }, + _ => {}, + } + var natoms = x.items.len; + var nelems = elem_tids.len; + if natoms != nelems { + diag(self, pat.span, .TypeMismatch, "tuple pattern arity mismatch"); + ret; + } + var i: usize = 0; + while i < nelems { + bind_pattern(self, list_at(x.items, i), list_at(elem_tids, i)); + i = i + 1; + } + pat.resolved_lang_tid = matched_tid; + }, + .PatternDot(x) => { + if !kind_is(self, matched_tid, .Enum) { + diag(self, pat.span, .TypeMismatch, "variant pattern requires enum value"); + ret; + } + var agg_ir = ir_of(self, matched_tid); + var idx: usize = 0; + if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, x.name, &idx) { + diag_name(self, pat.span, .NoField, "no variant `", x.name); + ret; + } + var payload_ir = agg_variant_ir(self, agg_ir, idx); + var payload_lang = lang_from_ir(self, payload_ir); + pat.resolved_def_id = idx + 1; + pat.resolved_lang_tid = matched_tid; + if x.sub_pattern != 0 && payload_lang != 0 { + bind_pattern(self, x.sub_pattern, payload_lang); + } + }, + .PatternAggregate(x) => { + if !(kind_is(self, matched_tid, .Struct) || kind_is(self, matched_tid, .Union) || + kind_is(self, matched_tid, .Enum)) { + diag(self, pat.span, .TypeMismatch, "aggregate pattern requires aggregate value"); + ret; + } + var agg_ir = ir_of(self, matched_tid); + if kind_is(self, matched_tid, .Enum) && x.items.len != 1 { + diag(self, pat.span, .TypeMismatch, "enum pattern must have one variant"); + ret; + } + var i: usize = 0; + while i < x.items.len { + var f = node(self, list_at(x.items, i)); + if f != null && is_pattern_field(f) { + var fname = ByteView.empty(); + var subp: usize = 0; + match f.body { + .PatternField(pf) => { + fname = pf.name; + subp = pf.pattern; + }, + _ => {}, + } + var idx: usize = 0; + if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, fname, &idx) { + diag_name(self, f.span, .NoField, "no field `", fname); + i = i + 1; + continue; + } + var fir = agg_variant_ir(self, agg_ir, idx); + var ft = lang_from_ir(self, fir); + f.resolved_def_id = idx; + if subp != 0 && ft != 0 { + bind_pattern(self, subp, ft); + } + } + i = i + 1; + } + pat.resolved_lang_tid = matched_tid; + }, + _ => { + diag(self, pat.span, .UnhandledNode, "unsupported pattern kind"); + }, + } +} + +fn agg_variant_ir(self: *Sema, agg_ir: usize, idx: usize): usize { + var irn = ir_entry(self, agg_ir); + if irn == null { ret 0; } + match irn.node { + .Struct(a) => { ret variant_at(a.variants, idx); }, + .Union(a) => { ret variant_at(a.variants, idx); }, + .Enum(a) => { ret variant_at(a.variants, idx); }, + .Shape(a) => { ret variant_at(a.variants, idx); }, + _ => { ret 0; }, + } + ret 0; +} + +fn variant_at(v: Vec, idx: usize): usize { + var m = TypeTable.member_at(&v, idx); + if m == null { ret 0; } + ret m.type_id; +} + +fn type_unpack_one(self: *Sema, pat: *Node, name: ByteView, src_lang: usize) { + if name.len == 0 { ret; } + var src = lang_of(self, src_lang); + if src == null { ret; } + var i: usize = 0; + while i < src.methods.len { + var m = method_at(&src.methods, i); + if m != null && ByteView.eq(m.name, name) { + var d = def_node(self, m.def_id); + var k: SymbolKind = .Var; + if d != null { + if @enum2int(d.kind) == @enum2int(DefKind.Fn) { + k = .Fn; + } else if @enum2int(d.kind) == @enum2int(DefKind.Agg) || + @enum2int(d.kind) == @enum2int(DefKind.Scalar) || + @enum2int(d.kind) == @enum2int(DefKind.Distinct) || + @enum2int(d.kind) == @enum2int(DefKind.Alias) || + @enum2int(d.kind) == @enum2int(DefKind.Builtin) { + k = .Type; + } + } + var sym = Symbol { .name = name, .kind = k, .node = m.def_id }; + if !Scope.insert(&self.scope, self.scope.current_scope, sym) { + diag_name(self, pat.span, .DuplicateSymbol, "type unpack duplicate `", name); + } + pat.resolved_def_id = m.def_id; + ret; + } + i = i + 1; + } + diag_name(self, pat.span, .NoField, "type unpack: no symbol `", name); +} + +fn type_unpack_walk(self: *Sema, pat: *Node, src_lang: usize) { + if pat == null { ret; } + match pat.body { + .PatternUnderscore => {}, + .PatternDot(x) => { + if x.sub_pattern != 0 { + diag(self, pat.span, .TypeMismatch, "type unpack: payload not allowed"); + ret; + } + type_unpack_one(self, pat, x.name, src_lang); + }, + .PatternAggregate(x) => { + var i: usize = 0; + while i < x.items.len { + var f = node(self, list_at(x.items, i)); + if f != null && is_pattern_field(f) { + var fname = ByteView.empty(); + var subp: usize = 0; + match f.body { + .PatternField(pf) => { + fname = pf.name; + subp = pf.pattern; + }, + _ => {}, + } + if subp != 0 { + diag(self, f.span, .TypeMismatch, "type unpack: field pattern not allowed"); + } else { + type_unpack_one(self, f, fname, src_lang); + } + } + i = i + 1; + } + }, + .PatternList(x) => { + var i: usize = 0; + while i < x.items.len { + type_unpack_walk(self, node(self, list_at(x.items, i)), src_lang); + i = i + 1; + } + }, + _ => {}, + } +} + +fn do_type_unpack(self: *Sema, decl: *Node, pat: *Node, type_expr_ref: usize) { + if type_expr_ref == 0 { + diag(self, decl.span, .TypeMismatch, "type unpack missing source type"); + ret; + } + var src_lang = infer(self, type_expr_ref); + if src_lang == 0 { ret; } + if !kind_is(self, src_lang, .Struct) { + diag(self, decl.span, .TypeMismatch, "type unpack source must be struct"); + ret; + } + type_unpack_walk(self, pat, src_lang); +} + +// ---- var/const 推断 ---- + +fn var_body(self: *Sema, n: *Node): usize { + var decl_tid: usize = 0; + var tref: usize = 0; + var iref: usize = 0; + var pat_ref: usize = 0; + match n.body { + .VarDecl(x) => { + tref = x.type_expr; + iref = x.init_expr; + pat_ref = x.pattern; + }, + .ConstDecl(x) => { + tref = x.type_expr; + iref = x.init_expr; + pat_ref = x.pattern; + }, + _ => {}, + } + if tref != 0 { decl_tid = infer(self, tref); } + + if iref == 0 { + diag(self, n.span, .VarUninitialized, "variable must be initialized"); + ret 0; + } + if decl_tid != 0 { + if coerce(self, iref, decl_tid) == 0 { ret 0; } + } else { + decl_tid = infer(self, iref); + if decl_tid == 0 || is_infer(self, decl_tid) { + diag(self, n.span, .CannotInfer, "cannot infer variable type"); + ret 0; + } + } + bind_pattern(self, pat_ref, decl_tid); + n.resolved_lang_tid = decl_tid; + ret decl_tid; +} + +fn for_elem_type(self: *Sema, lang_tid: usize): usize { + var e = lang_of(self, lang_tid); + if e == null { ret 0; } + ret elem_of_lang(e.*); +} + +// ---- 驱动 ---- + +fn run_module(self: *Sema, module_ref: usize) { + if self.ast == null || module_ref == 0 { ret; } + var m = Ast.node(self.ast, module_ref); + if m == null { ret; } + match m.body { + .ContainerMembers(x) => {}, + _ => { ret; }, + } + + var saved_scope = self.scope.current_scope; + var saved_agg = self.current_aggregate_lang; + + collect_names(self, module_ref); + collect_type_unpacks(self, module_ref); + + var container_lang: usize = 0; + if m.resolved_def_id != 0 { + container_lang = eval_decl(self, m.resolved_def_id); + } + self.current_aggregate_lang = container_lang; + + collect_types(self, module_ref); + collect_signatures(self); + ignore_usize(infer(self, module_ref)); + + self.current_aggregate_lang = saved_agg; + self.scope.current_scope = saved_scope; +} + +fn run(self: *Sema) { + if self.ast == null || self.ast.root == 0 { ret; } + var root = Ast.node(self.ast, self.ast.root); + if root == null { ret; } + var members: usize = 0; + match root.body { + .Root(r) => { members = r.members; }, + _ => { ret; }, + } + run_module(self, members); +} + +fn method_at(v: *Vec, i: usize): *LangMethod { + match Vec.get(v, i) { + .Ok(ptr) => { ret ptr as *LangMethod; }, + .Err(error) => { ret null; }, + } + ret null; +} + +fn ignore_usize(x: usize) {} +fn ignore_bool(x: bool) {} + +// ============================================================ +// infer / coerce:下面两块是主体(step 2 填入)。 +// ============================================================ + +fn int_bits(self: *Sema, lang_id: usize): usize { + var e = ir_entry(self, ir_of(self, lang_id)); + if e == null { ret 0; } + match e.node { + .Int(x) => { ret x.bits; }, + _ => { ret 0; }, + } + ret 0; +} + +fn float_bits(self: *Sema, lang_id: usize): usize { + var e = ir_entry(self, ir_of(self, lang_id)); + if e == null { ret 0; } + match e.node { + .Float(x) => { ret x.bits; }, + _ => { ret 0; }, + } + ret 0; +} + +fn lang_find_method(self: *Sema, lang_id: usize, name: ByteView): usize { + var e = lang_of(self, lang_id); + if e == null { ret 0; } + var i: usize = 0; + while i < e.methods.len { + var m = method_at(&e.methods, i); + if m != null && ByteView.eq(m.name, name) { ret m.def_id; } + i = i + 1; + } + ret 0; +} + +fn label_name(self: *Sema, ref: usize): ByteView { + if ref == 0 { ret ByteView.empty(); } + var ln = node(self, ref); + if ln == null { ret ByteView.empty(); } + match ln.body { + .Label(l) => { ret l.name; }, + _ => { ret ByteView.empty(); }, + } + ret ByteView.empty(); +} + +fn agg_variants_copy(self: *Sema, agg_ir: usize): Vec { + var out = ref_vec(); + var irn = ir_entry(self, agg_ir); + if irn == null { ret out; } + var v = ref_vec(); + match irn.node { + .Struct(a) => { v = a.variants; }, + .Union(a) => { v = a.variants; }, + .Enum(a) => { v = a.variants; }, + .Shape(a) => { v = a.variants; }, + _ => {}, + } + var i: usize = 0; + while i < v.len { + push_u(&out, variant_at(v, i)); + i = i + 1; + } + ret out; +} + +// ---- coerce ---- + +fn coerce(self: *Sema, ref: usize, expected: usize): usize { + if ref == 0 || expected == 0 { ret 0; } + var n = node(self, ref); + if n == null { ret 0; } + + var already = n.resolved_lang_tid; + if already == expected { ret expected; } + + if already != 0 { + var a_rebindable = is_infer(self, already) || + (kind_is(self, already, .Int) && int_bits(self, already) == 0) || + (kind_is(self, already, .Float) && float_bits(self, already) == 0); + if !a_rebindable { + if LangType.is_subtype(&self.types, already, expected) { + n.expected_lang_tid = expected; + ret expected; + } + diag_ty2(self, n.span, .TypeMismatch, "type mismatch: ", already, expected); + ret 0; + } + n.resolved_lang_tid = 0; + } + + var handled = false; + var result: usize = 0; + + match n.body { + .ExprUnary(x) => { + if !(punct_is(x.op, .And) || punct_is(x.op, .Mul)) { + handled = true; + result = coerce(self, x.operand, expected); + } + }, + .ExprBinary(x) => { + handled = true; + var op = x.op; + if punct_is(op, .Assign) || is_assign_arith(op) { + var lt = infer(self, x.left); + if lt == 0 { ret 0; } + if coerce(self, x.right, lt) == 0 { ret 0; } + result = lt; + } else if punct_is(op, .OrOr) || punct_is(op, .AndAnd) { + var bt = make_bool(self); + if coerce(self, x.left, bt) == 0 { ret 0; } + if coerce(self, x.right, bt) == 0 { ret 0; } + result = bt; + } else if is_cmp_op(op) { + var lt2 = infer(self, x.left); + var rt2 = infer(self, x.right); + if lt2 == 0 || rt2 == 0 { ret 0; } + var common = if lt2 == rt2 { lt2 } else { LangType.join(&self.types, lt2, rt2) }; + if common == 0 { + diag_ty2(self, n.span, .TypeMismatch, "comparison mismatch: ", lt2, rt2); + ret 0; + } + ignore_usize(coerce(self, x.left, common)); + ignore_usize(coerce(self, x.right, common)); + result = make_bool(self); + } else if is_addsub(op) { + var en = lang_of(self, expected); + if en != null && kind_is(self, expected, .Ptr) { + var left_tid = infer(self, x.left); + var ln = lang_of(self, left_tid); + if ln != null && kind_is(self, left_tid, .Ptr) { + var rt3 = infer(self, x.right); + if !kind_is(self, rt3, .Int) { + diag(self, n.span, .TypeMismatch, "pointer offset must be integer"); + ret 0; + } + if int_bits(self, rt3) == 0 { + var it = make_isize(self); + if coerce(self, x.right, it) == 0 { ret 0; } + } + result = expected; + } else { + if coerce(self, x.left, expected) == 0 { ret 0; } + if coerce(self, x.right, expected) == 0 { ret 0; } + result = expected; + } + } else { + if coerce(self, x.left, expected) == 0 { ret 0; } + if coerce(self, x.right, expected) == 0 { ret 0; } + result = expected; + } + } else { + if coerce(self, x.left, expected) == 0 { ret 0; } + if coerce(self, x.right, expected) == 0 { ret 0; } + result = expected; + } + }, + .ExprArrayLit(x) => { + var en = lang_of(self, expected); + if en != null && (kind_is(self, expected, .Array) || kind_is(self, expected, .Slice)) { + handled = true; + var et = elem_of_lang(en.*); + var i: usize = 0; + while i < x.elems.len { + if coerce(self, list_at(x.elems, i), et) == 0 { ret 0; } + i = i + 1; + } + result = expected; + } + }, + .ExprTuple(x) => { + var en = lang_of(self, expected); + if en != null && kind_is(self, expected, .Tuple) { + handled = true; + var elems = ref_vec(); + match en.extra { .Tuple(v) => { elems = v; }, _ => {} } + if x.items.len != elems.len { + diag(self, n.span, .TypeMismatch, "tuple arity mismatch"); + ret 0; + } + var i: usize = 0; + while i < x.items.len { + if coerce(self, list_at(x.items, i), list_at(elems, i)) == 0 { ret 0; } + i = i + 1; + } + result = expected; + } + }, + .ExprAggregateLit(x) => { + var en = lang_of(self, expected); + if en != null && (kind_is(self, expected, .Struct) || kind_is(self, expected, .Union) || + kind_is(self, expected, .Enum) || kind_is(self, expected, .Slice)) { + handled = true; + if x.name.len != 0 { + var sym = Symbol { .name = ByteView.empty(), .kind = .Error, .node = 0 }; + if !Scope.find(&self.scope, x.name, &sym) { + diag_name(self, n.span, .UndefinedSymbol, "unknown type `", x.name); + ret 0; + } + var d = def_node(self, sym.node); + if d == null || d.lang_id != expected { + diag(self, n.span, .TypeMismatch, "aggregate does not match expected type"); + ret 0; + } + } + var agg_ir = ir_of(self, expected); + var field_irs = agg_variants_copy(self, agg_ir); + if kind_is(self, expected, .Enum) { + if x.fields.len != 1 { + diag(self, n.span, .TypeMismatch, "enum literal must have one variant"); + ret 0; + } + var item = node(self, list_at(x.fields, 0)); + if item == null || !kind_is_body_struct_init(item) { + diag(self, n.span, .TypeMismatch, "invalid enum literal"); + ret 0; + } + var iname = ByteView.empty(); + var iexpr: usize = 0; + match item.body { .StructInitItem(si) => { iname = si.name; iexpr = si.expr; }, _ => {} } + var idx: usize = 0; + if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, iname, &idx) { + diag_name(self, item.span, .NoField, "no variant `", iname); + ret 0; + } + if idx >= field_irs.len { ret 0; } + var ft = lang_from_ir(self, list_at(field_irs, idx)); + if ft != 0 && iexpr != 0 { + if coerce(self, iexpr, ft) == 0 { ret 0; } + } + result = expected; + } else { + var i: usize = 0; + while i < x.fields.len { + var item = node(self, list_at(x.fields, i)); + if item != null && kind_is_body_struct_init(item) { + var iname = ByteView.empty(); + var iexpr: usize = 0; + match item.body { .StructInitItem(si) => { iname = si.name; iexpr = si.expr; }, _ => {} } + var idx: usize = 0; + if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, iname, &idx) { + diag_name(self, item.span, .NoField, "no field `", iname); + i = i + 1; + continue; + } + if idx < field_irs.len { + var ft = lang_from_ir(self, list_at(field_irs, idx)); + if ft != 0 && iexpr != 0 { + if coerce(self, iexpr, ft) == 0 { ret 0; } + } + } + } + i = i + 1; + } + result = expected; + } + } + }, + .ExprIf(x) => { + handled = true; + var bt = make_bool(self); + ignore_usize(coerce(self, x.cond, bt)); + if x.then_block != 0 { ignore_usize(coerce(self, x.then_block, expected)); } + if x.else_branch != 0 { ignore_usize(coerce(self, x.else_branch, expected)); } + result = expected; + }, + .ExprLoop(x) => { + handled = true; + var saved = self.current_loop_break_tid; + self.current_loop_break_tid = expected; + ignore_usize(infer(self, ref)); + self.current_loop_break_tid = saved; + result = expected; + }, + .ExprBlock(x) => { + ret block_body(self, n, expected); + }, + _ => {}, + } + + if handled { + if result == 0 { ret 0; } + n.resolved_lang_tid = result; + ret result; + } + + var from = infer(self, ref); + if from == 0 { ret 0; } + if from == expected { ret expected; } + + if kind_is(self, from, .Int) && int_bits(self, from) == 0 && kind_is(self, expected, .Int) { + if is_expr_int_lit(n) && int_bits(self, expected) != 0 { + var fits = true; + match n.body { + .ExprIntLit(v) => { + if !LangType.int_fits(&self.types, expected, v.value) { fits = false; } + }, + _ => {}, + } + if !fits { + diag(self, n.span, .FitsOverflow, "comptime value does not fit target type"); + ret 0; + } + } + n.resolved_lang_tid = expected; + ret expected; + } + if kind_is(self, from, .Float) && float_bits(self, from) == 0 && kind_is(self, expected, .Float) { + n.resolved_lang_tid = expected; + ret expected; + } + if LangType.is_subtype(&self.types, from, expected) { + n.expected_lang_tid = expected; + ret expected; + } + diag_ty2(self, n.span, .TypeMismatch, "type mismatch: ", from, expected); + ret 0; +} + +fn elem_of_lang(e: LangEntry): usize { + match e.extra { + .Elem(x) => { ret x; }, + .Array(a) => { ret a.elem; }, + _ => { ret 0; }, + } + ret 0; +} + +fn kind_is_body_struct_init(n: *Node): bool { + match n.body { .StructInitItem(x) => { ret true; }, _ => { ret false; } } + ret false; +} + +// ---- infer ---- + +fn infer(self: *Sema, ref: usize): usize { + if ref == 0 { ret 0; } + var n = node(self, ref); + if n == null { ret 0; } + resolve_attrs(self, n); + + match n.body { + .ExprIntLit(x) => { + var t = LangType.make_int(&self.types, 0, true); + n.resolved_lang_tid = t; + ret t; + }, + .ExprFloatLit(x) => { + var t = LangType.make_float(&self.types, 0); + n.resolved_lang_tid = t; + ret t; + }, + .ExprCharLit(x) => { + var t = LangType.make_int(&self.types, 8, false); + n.resolved_lang_tid = t; + ret t; + }, + .ExprStringLit(x) => { + var u8t = LangType.make_int(&self.types, 8, false); + var s = LangType.make_slice(&self.types, u8t); + n.resolved_lang_tid = s; + ret s; + }, + .ExprBoolLit(x) => { + var t = make_bool(self); + n.resolved_lang_tid = t; + ret t; + }, + .ExprNullLit => { + var nv = make_never(self); + var p = LangType.make_ptr(&self.types, nv); + n.resolved_lang_tid = p; + ret p; + }, + .ExprUndefinedLit => { + var t = make_never(self); + n.resolved_lang_tid = t; + ret t; + }, + .ExprIdent(x) => { + var sym = Symbol { .name = ByteView.empty(), .kind = .Error, .node = 0 }; + if !Scope.find(&self.scope, x.name, &sym) { + diag_name(self, n.span, .UndefinedSymbol, "undefined identifier `", x.name); + ret 0; + } + if sym.node == 0 { + diag_name(self, n.span, .UndefinedSymbol, "identifier has no def `", x.name); + ret 0; + } + n.resolved_def_id = sym.node; + var t = eval_decl(self, sym.node); + n.resolved_lang_tid = t; + ret t; + }, + .ExprUnary(x) => { + var op = infer(self, x.operand); + if op == 0 { ret 0; } + var tn = lang_of(self, op); + if tn == null { ret 0; } + if punct_is(x.op, .Sub) { + if !(kind_is(self, op, .Int) || kind_is(self, op, .Float)) { + diag(self, n.span, .TypeMismatch, "unary minus requires numeric operand"); + ret 0; + } + n.resolved_lang_tid = op; + ret op; + } + if punct_is(x.op, .BitNot) { + if !kind_is(self, op, .Int) { + diag(self, n.span, .TypeMismatch, "bitwise not requires integer operand"); + ret 0; + } + n.resolved_lang_tid = op; + ret op; + } + if punct_is(x.op, .Not) { + var bt = make_bool(self); + ignore_usize(coerce(self, x.operand, bt)); + n.resolved_lang_tid = bt; + ret bt; + } + if punct_is(x.op, .And) { + var operand = node(self, x.operand); + if operand != null && kind_is_body_ident(operand) { + var d = def_node(self, operand.resolved_def_id); + if d != null && @enum2int(d.kind) == @enum2int(DefKind.Fn) { + diag(self, n.span, .TypeMismatch, "cannot take address of a function"); + ret 0; + } + } + var p = LangType.make_ptr(&self.types, op); + n.resolved_lang_tid = p; + ret p; + } + if punct_is(x.op, .Mul) { + if !kind_is(self, op, .Ptr) { + diag(self, n.span, .TypeMismatch, "deref requires pointer operand"); + ret 0; + } + var elem = elem_of_lang(tn.*); + n.resolved_lang_tid = elem; + ret elem; + } + ret 0; + }, + .ExprBinary(x) => { + ret infer_binary(self, n, x.op, x.left, x.right); + }, + .ExprCall(x) => { + ret infer_call(self, n, x.callee, x.args); + }, + .ExprField(x) => { + ret infer_field(self, n, x.obj, x.name); + }, + .ExprIndex(x) => { + ret infer_index(self, n, x.obj, x.index); + }, + .ExprSlice(x) => { + ret infer_slice(self, n, x.obj, x.begin, x.end); + }, + .ExprCast(x) => { + ret infer_cast(self, n, x.expr, x.type_expr); + }, + .ExprTuple(x) => { + var es = ref_vec(); + var i: usize = 0; + while i < x.items.len { + push_u(&es, infer(self, list_at(x.items, i))); + i = i + 1; + } + var t = LangType.make_tuple(&self.types, es); + n.resolved_lang_tid = t; + ret t; + }, + .ExprArrayLit(x) => { + ret infer_array(self, n, x.elem_type, x.has_len, x.len, x.is_underscore_len, x.elems); + }, + .ExprAggregateLit(x) => { + ret infer_aggregate(self, n, x.name, x.fields); + }, + .ExprComptimeCall(x) => { + if ByteView.eq(x.name, ByteView.from_slice("import")) { + var t = sema_import(self, n, x.args); + n.resolved_lang_tid = t; + ret t; + } + if ByteView.eq(x.name, ByteView.from_slice("self")) { + if self.current_aggregate_lang == 0 { + diag(self, n.span, .UndefinedSymbol, "`@self` outside of aggregate"); + ret 0; + } + n.resolved_lang_tid = self.current_aggregate_lang; + ret self.current_aggregate_lang; + } + var in = IntrinOps.lookup_call(x.name); + if intrin_is(in, .None) { + diag_name(self, n.span, .UnknownIntrin, "unknown comptime call `", x.name); + ret 0; + } + var t = intrin_call(self, n, in, x.args); + n.resolved_lang_tid = t; + ret t; + }, + .ExprBlock(x) => { + ret block_body(self, n, 0); + }, + .ExprIf(x) => { + var bt = make_bool(self); + ignore_usize(coerce(self, x.cond, bt)); + var then_t = if x.then_block != 0 { infer(self, x.then_block) } else { 0 }; + var else_t = if x.else_branch != 0 { infer(self, x.else_branch) } else { 0 }; + var result: usize = 0; + if then_t != 0 && else_t != 0 { + result = if then_t == else_t { then_t } else { LangType.join(&self.types, then_t, else_t) }; + } else if then_t != 0 { + result = then_t; + } else { + result = else_t; + } + n.resolved_lang_tid = result; + ret result; + }, + .ExprIfvar(x) => { + if x.var_decl == 0 { ret 0; } + var vd = node(self, x.var_decl); + if vd == null { ret 0; } + push_scope(self); + ignore_usize(var_body(self, vd)); + if x.then_block != 0 { ignore_usize(infer(self, x.then_block)); } + pop_scope(self); + if x.else_branch != 0 { ignore_usize(infer(self, x.else_branch)); } + ret 0; + }, + .ExprWhile(x) => { + push_view(&self.label_stack, label_name(self, x.label)); + var bt = make_bool(self); + ignore_usize(coerce(self, x.cond, bt)); + if x.body != 0 { ignore_usize(infer(self, x.body)); } + if x.else_branch != 0 { ignore_usize(infer(self, x.else_branch)); } + pop_label(self); + ret 0; + }, + .ExprLoop(x) => { + push_view(&self.label_stack, label_name(self, x.label)); + if x.body != 0 { ignore_usize(infer(self, x.body)); } + pop_label(self); + ret 0; + }, + .ExprFor(x) => { + ret infer_for(self, n, x.label, x.iter_exprs, x.pattern, x.body, x.else_branch); + }, + .ExprMatch(x) => { + var matched = infer(self, x.matched_expr); + var i: usize = 0; + while i < x.arms.len { + var arm = node(self, list_at(x.arms, i)); + if arm != null { + var pat: usize = 0; + var body: usize = 0; + match arm.body { + .MatchArm(ma) => { + pat = ma.pattern; + body = ma.body; + }, + _ => {}, + } + push_scope(self); + bind_pattern(self, pat, matched); + if body != 0 { ignore_usize(infer(self, body)); } + pop_scope(self); + } + i = i + 1; + } + ret 0; + }, + .ExprFnType(x) => { + var ps = ref_vec(); + var i: usize = 0; + while i < x.params.len { + push_u(&ps, infer(self, list_at(x.params, i))); + i = i + 1; + } + var rt = infer(self, x.ret_type); + var fn_lang = LangType.make_fn(&self.types, rt, ps); + n.resolved_lang_tid = fn_lang; + ret fn_lang; + }, + .TypePath(x) => { + var b = LangType.lookup_builtin(&self.types, x.name); + if b != 0 { + n.resolved_lang_tid = b; + ret b; + } + var sym = Symbol { .name = ByteView.empty(), .kind = .Error, .node = 0 }; + if !Scope.find(&self.scope, x.name, &sym) { + diag_name(self, n.span, .UndefinedSymbol, "type not found `", x.name); + ret 0; + } + var t = eval_decl(self, sym.node); + n.resolved_lang_tid = t; + ret t; + }, + .TypePointer(x) => { + var p = infer(self, x.pointee); + var r = LangType.make_ptr(&self.types, p); + n.resolved_lang_tid = r; + ret r; + }, + .TypeArray(x) => { + var e = infer(self, x.elem_type); + var len: usize = 0; + if x.has_len { len = x.len as usize; } + var r = LangType.make_array(&self.types, e, len); + n.resolved_lang_tid = r; + ret r; + }, + .TypeSlice(x) => { + var e = infer(self, x.elem_type); + var r = LangType.make_slice(&self.types, e); + n.resolved_lang_tid = r; + ret r; + }, + .TypeFn(x) => { + var ps = ref_vec(); + var i: usize = 0; + while i < x.params.len { + push_u(&ps, infer(self, list_at(x.params, i))); + i = i + 1; + } + var rt = infer(self, x.ret_type); + var r = LangType.make_fn(&self.types, rt, ps); + n.resolved_lang_tid = r; + ret r; + }, + .TypeParen(x) => { + if x.types.len == 0 { + var u = make_unit(self); + n.resolved_lang_tid = u; + ret u; + } + if x.types.len == 1 { + var t = infer(self, list_at(x.types, 0)); + n.resolved_lang_tid = t; + ret t; + } + var es = ref_vec(); + var i: usize = 0; + while i < x.types.len { + push_u(&es, infer(self, list_at(x.types, i))); + i = i + 1; + } + var t = LangType.make_tuple(&self.types, es); + n.resolved_lang_tid = t; + ret t; + }, + .TypeAggregate(x) => { + ret build_aggregate(self, n, 0); + }, + .TypeComptimeCall(x) => { + if ByteView.eq(x.name, ByteView.from_slice("self")) { + if self.current_aggregate_lang == 0 { + diag(self, n.span, .UndefinedSymbol, "`@self` outside of aggregate"); + ret 0; + } + n.resolved_lang_tid = self.current_aggregate_lang; + ret self.current_aggregate_lang; + } + if ByteView.eq(x.name, ByteView.from_slice("import")) { + var t = sema_import(self, n, x.args); + n.resolved_lang_tid = t; + ret t; + } + ret 0; + }, + .ContainerMembers(x) => { + var i: usize = 0; + while i < x.declarations.len { + ignore_usize(infer(self, list_at(x.declarations, i))); + i = i + 1; + } + ret 0; + }, + .FnDecl(x) => { + ret infer_fn_decl(self, n, x.params, x.ret_type, x.body); + }, + .TypeDecl(x) => { + ret infer_type_decl(self, n, x.pattern, x.type_expr); + }, + .VarDecl(x) => { ret var_body(self, n); }, + .ConstDecl(x) => { ret var_body(self, n); }, + .MemberDecl(x) => { + if x.type_expr != 0 { ret infer(self, x.type_expr); } + ret 0; + }, + .ParamDecl(x) => { + if x.type_expr != 0 { ret infer(self, x.type_expr); } + ret 0; + }, + .StatementRet(x) => { + ret infer_ret(self, n, x.expr); + }, + .StatementBreak(x) => { + ret infer_break(self, n, x.label, x.expr); + }, + .StatementContinue(x) => { + ret infer_continue(self, n, x.label); + }, + .StatementDefer(x) => { + if x.body != 0 { ignore_usize(infer(self, x.body)); } + ret 0; + }, + .StatementComptime(x) => { + if x.body != 0 { ignore_usize(infer(self, x.body)); } + ret 0; + }, + .StatementDecl(x) => { + if x.decl != 0 { ignore_usize(infer(self, x.decl)); } + ret 0; + }, + .StatementExpr(x) => { + if x.expr != 0 { ignore_usize(infer(self, x.expr)); } + ret 0; + }, + .TopLevelBlock(x) => { + if x.block != 0 { ignore_usize(infer(self, x.block)); } + ret 0; + }, + _ => { ret 0; }, + } + ret 0; +} + +fn kind_is_body_ident(n: *Node): bool { + match n.body { .ExprIdent(x) => { ret true; }, _ => { ret false; } } + ret false; +} + +fn pop_label(self: *Sema) { + var out = ByteView.empty(); + match Vec.pop(&self.label_stack, &out as *u8) { + .Ok => {}, + .Err(error) => {}, + } +} + +fn infer_binary(self: *Sema, n: *Node, op: Punct, left: usize, right: usize): usize { + if punct_is(op, .Assign) || is_assign_arith(op) { + var lt = infer(self, left); + if lt == 0 { ret 0; } + ignore_usize(coerce(self, right, lt)); + n.resolved_lang_tid = lt; + ret lt; + } + if punct_is(op, .OrOr) || punct_is(op, .AndAnd) { + var bt = make_bool(self); + ignore_usize(coerce(self, left, bt)); + ignore_usize(coerce(self, right, bt)); + n.resolved_lang_tid = bt; + ret bt; + } + if is_cmp_op(op) { + var lt = infer(self, left); + var rt = infer(self, right); + if lt == 0 || rt == 0 { ret 0; } + var common = if lt == rt { lt } else { LangType.join(&self.types, lt, rt) }; + if common == 0 { + diag_ty2(self, n.span, .TypeMismatch, "comparison mismatch: ", lt, rt); + ret 0; + } + ignore_usize(coerce(self, left, common)); + ignore_usize(coerce(self, right, common)); + var b = make_bool(self); + n.resolved_lang_tid = b; + ret b; + } + if punct_is(op, .Range) { + ignore_usize(infer(self, left)); + ignore_usize(infer(self, right)); + var i64t = LangType.make_int(&self.types, 64, true); + var r = LangType.make_range(&self.types, i64t); + n.resolved_lang_tid = r; + ret r; + } + if is_shift_op(op) { + var lt = infer(self, left); + if lt == 0 { ret 0; } + if !kind_is(self, lt, .Int) { + diag(self, n.span, .TypeMismatch, "shift requires integer operands"); + ret 0; + } + ignore_usize(infer(self, right)); + n.resolved_lang_tid = lt; + ret lt; + } + var lt2 = infer(self, left); + var rt2 = infer(self, right); + if lt2 == 0 || rt2 == 0 { ret 0; } + if kind_is(self, lt2, .Ptr) && kind_is(self, rt2, .Int) && is_addsub(op) { + if int_bits(self, rt2) == 0 { + var it = make_isize(self); + ignore_usize(coerce(self, right, it)); + } + n.resolved_lang_tid = lt2; + ret lt2; + } + var common2 = if lt2 == rt2 { lt2 } else { LangType.join(&self.types, lt2, rt2) }; + if common2 == 0 { + diag_ty2(self, n.span, .TypeMismatch, "operand types cannot be joined: ", lt2, rt2); + ret 0; + } + ignore_usize(coerce(self, left, common2)); + ignore_usize(coerce(self, right, common2)); + n.resolved_lang_tid = common2; + ret common2; +} + +fn infer_call(self: *Sema, n: *Node, callee_ref: usize, args: Vec): usize { + var callee = node(self, callee_ref); + if callee != null && kind_is_body_ident(callee) { + var cname = ByteView.empty(); + match callee.body { .ExprIdent(e) => { cname = e.name; }, _ => {} } + var in = IntrinOps.lookup_call(cname); + if !intrin_is(in, .None) { + var t = intrin_call(self, n, in, args); + n.resolved_lang_tid = t; + ret t; + } + } + + var callee_tid = infer(self, callee_ref); + var fn_lang = lang_of(self, callee_tid); + if fn_lang == null || !kind_is(self, callee_tid, .Fn) { + diag(self, n.span, .NotAFunction, "callee is not a function"); + ret 0; + } + var params = ref_vec(); + var call_ret: usize = 0; + match fn_lang.extra { + .Fn(f) => { + params = f.params; + call_ret = f.ret_type; + }, + _ => {}, + } + var has_varargs = false; + var fixed = params.len; + if fixed > 0 { + var last = list_at(params, fixed - 1); + if kind_is(self, last, .Varargs) { + has_varargs = true; + fixed = fixed - 1; + } + } + var nargs = args.len; + if has_varargs { + if nargs < fixed { + diag(self, n.span, .ArgCountMismatch, "too few arguments"); + } + } else if nargs != fixed { + diag(self, n.span, .ArgCountMismatch, "argument count mismatch"); + } + + var i: usize = 0; + while i < nargs && i < fixed { + var pt = list_at(params, i); + if coerce(self, list_at(args, i), pt) == 0 { ret 0; } + i = i + 1; + } + if has_varargs { + var j = fixed; + while j < nargs { + if infer(self, list_at(args, j)) == 0 { ret 0; } + j = j + 1; + } + } + n.resolved_lang_tid = call_ret; + ret call_ret; +} + +fn infer_field(self: *Sema, n: *Node, obj_ref: usize, field_name: ByteView): usize { + var obj_tid = infer(self, obj_ref); + var obj = lang_of(self, obj_tid); + if obj == null { ret 0; } + + var objn = node(self, obj_ref); + if objn != null && kind_is_body_ident(objn) { + var od = def_node(self, objn.resolved_def_id); + if od != null && @enum2int(od.kind) == @enum2int(DefKind.Agg) && + kind_is(self, obj_tid, .Enum) { + var eir = ir_of(self, obj_tid); + var vidx: usize = 0; + if eir != 0 && TypeTable.agg_member_idx(&self.types.ir, eir, field_name, &vidx) { + n.resolved_lang_tid = obj_tid; + ret obj_tid; + } + } + } + + var in = IntrinOps.lookup_field(obj.kind, field_name); + if !intrin_is(in, .None) { + var t = intrin_field(self, n, in); + n.resolved_lang_tid = t; + ret t; + } + + var agg_lang = obj_tid; + if kind_is(self, obj_tid, .Ptr) { + agg_lang = elem_of_lang(obj.*); + } + var agg = lang_of(self, agg_lang); + if agg == null { + diag(self, n.span, .NoField, "field access on invalid type"); + ret 0; + } + + var agg_ir = ir_of(self, agg_lang); + if agg_ir != 0 { + var idx: usize = 0; + if TypeTable.agg_member_idx(&self.types.ir, agg_ir, field_name, &idx) { + var fir = agg_variant_ir(self, agg_ir, idx); + var ft = lang_from_ir(self, fir); + if ft == 0 { + diag(self, n.span, .NoField, "cannot resolve field type"); + ret 0; + } + n.resolved_lang_tid = ft; + ret ft; + } + } + + var did = lang_find_method(self, agg_lang, field_name); + if did != 0 { + n.resolved_def_id = did; + var t = eval_decl(self, did); + n.resolved_lang_tid = t; + ret t; + } + diag_name(self, n.span, .NoField, "no field `", field_name); + ret 0; +} + +fn infer_index(self: *Sema, n: *Node, obj_ref: usize, index_ref: usize): usize { + var obj_tid = infer(self, obj_ref); + ignore_usize(infer(self, index_ref)); + var obj = lang_of(self, obj_tid); + if obj == null { ret 0; } + var elem: usize = 0; + if kind_is(self, obj_tid, .Array) || kind_is(self, obj_tid, .Slice) || + kind_is(self, obj_tid, .Range) || kind_is(self, obj_tid, .Ptr) { + elem = elem_of_lang(obj.*); + } else if kind_is(self, obj_tid, .Tuple) { + var idx_node = node(self, index_ref); + if idx_node == null || !is_expr_int_lit(idx_node) { + diag(self, n.span, .TupleIndexOob, "tuple index must be integer literal"); + ret 0; + } + var iv: i64 = -1; + match idx_node.body { + .ExprIntLit(lit) => { + match BigInt.to_i64(&iv, lit.value) { .Ok => {}, .Err(e) => {} } + }, + _ => {}, + } + var elems = ref_vec(); + match obj.extra { .Tuple(v) => { elems = v; }, _ => {} } + if iv < 0 || (iv as usize) >= elems.len { + diag(self, n.span, .TupleIndexOob, "tuple index out of range"); + ret 0; + } + elem = list_at(elems, iv as usize); + } else { + diag(self, n.span, .IndexBaseInvalid, "index base must be array/slice/ptr/range/tuple"); + ret 0; + } + n.resolved_lang_tid = elem; + ret elem; +} + +fn infer_slice(self: *Sema, n: *Node, obj_ref: usize, begin_ref: usize, end_ref: usize): usize { + var obj_tid = infer(self, obj_ref); + if begin_ref != 0 { ignore_usize(infer(self, begin_ref)); } + if end_ref != 0 { ignore_usize(infer(self, end_ref)); } + var obj = lang_of(self, obj_tid); + if obj == null { ret 0; } + if !(kind_is(self, obj_tid, .Array) || kind_is(self, obj_tid, .Ptr) || + kind_is(self, obj_tid, .Slice)) { + diag(self, n.span, .IndexBaseInvalid, "slice base must be array/ptr/slice"); + ret 0; + } + var elem = elem_of_lang(obj.*); + var s = LangType.make_slice(&self.types, elem); + n.resolved_lang_tid = s; + ret s; +} + +fn infer_cast(self: *Sema, n: *Node, expr_ref: usize, type_ref: usize): usize { + var dst = infer(self, type_ref); + if dst == 0 { ret 0; } + var src = infer(self, expr_ref); + if src == 0 { ret 0; } + if (kind_is(self, src, .Int) && int_bits(self, src) == 0) || + (kind_is(self, src, .Float) && float_bits(self, src) == 0) { + var src_node = node(self, expr_ref); + if src_node != null && (is_expr_int_lit(src_node) || is_expr_float_lit(src_node)) { + src_node.resolved_lang_tid = dst; + } else if coerce(self, expr_ref, dst) == 0 { + ret 0; + } + n.resolved_lang_tid = dst; + ret dst; + } + var ft = ir_of(self, src); + var tt = ir_of(self, dst); + if ft == 0 || tt == 0 { + diag(self, n.span, .CastInvalid, "as: unresolved source or target type"); + ret 0; + } + var in = IntrinOps.lookup_cast(&self.types.ir, ft, tt); + if intrin_is(in, .None) { + diag(self, n.span, .CastInvalid, "as: invalid conversion"); + ret 0; + } + n.resolved_lang_tid = dst; + ret dst; +} + +fn is_expr_float_lit(n: *Node): bool { + match n.body { .ExprFloatLit(x) => { ret true; }, _ => { ret false; } } + ret false; +} + +fn infer_array(self: *Sema, n: *Node, elem_type_ref: usize, has_len: bool, len_decl: usize, + is_underscore_len: bool, elems: Vec): usize { + var elem_t: usize = 0; + if elem_type_ref != 0 { elem_t = infer(self, elem_type_ref); } + var nelems = elems.len; + var i: usize = 0; + while i < nelems { + var et = infer(self, list_at(elems, i)); + if et == 0 { ret 0; } + if elem_t == 0 { + elem_t = et; + } else { + var c = if elem_t == et { elem_t } else { LangType.join(&self.types, elem_t, et) }; + if c != 0 { elem_t = c; } + } + i = i + 1; + } + if elem_t == 0 { elem_t = make_infer(self); } + var len = nelems; + if !is_underscore_len && has_len { len = len_decl; } + var arr = LangType.make_array(&self.types, elem_t, len); + n.resolved_lang_tid = arr; + ret arr; +} + +fn infer_aggregate(self: *Sema, n: *Node, type_name: ByteView, fields: Vec): usize { + if type_name.len == 0 { + var inf = make_infer(self); + n.resolved_lang_tid = inf; + ret inf; + } + var sym = Symbol { .name = ByteView.empty(), .kind = .Error, .node = 0 }; + if !Scope.find(&self.scope, type_name, &sym) { + diag_name(self, n.span, .UndefinedSymbol, "unknown type `", type_name); + ret 0; + } + var d = def_node(self, sym.node); + if d == null || d.lang_id == 0 { + diag_name(self, n.span, .UndefinedSymbol, "type not resolved `", type_name); + ret 0; + } + var agg = d.lang_id; + if !(kind_is(self, agg, .Struct) || kind_is(self, agg, .Union) || kind_is(self, agg, .Enum)) { + diag_name(self, n.span, .TypeMismatch, "not a struct/union/enum `", type_name); + ret 0; + } + if kind_is(self, agg, .Enum) && fields.len != 1 { + diag(self, n.span, .TypeMismatch, "enum literal must have one variant"); + ret 0; + } + var agg_ir = ir_of(self, agg); + var field_irs = agg_variants_copy(self, agg_ir); + var i: usize = 0; + while i < fields.len { + var item = node(self, list_at(fields, i)); + if item != null && kind_is_body_struct_init(item) { + var iname = ByteView.empty(); + var iexpr: usize = 0; + match item.body { .StructInitItem(si) => { iname = si.name; iexpr = si.expr; }, _ => {} } + var idx: usize = 0; + if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, iname, &idx) { + diag_name(self, item.span, .NoField, "no field `", iname); + i = i + 1; + continue; + } + if idx < field_irs.len { + var ft = lang_from_ir(self, list_at(field_irs, idx)); + if ft != 0 && iexpr != 0 { ignore_usize(coerce(self, iexpr, ft)); } + } + } + i = i + 1; + } + n.resolved_lang_tid = agg; + ret agg; +} + +fn infer_for(self: *Sema, n: *Node, label_ref: usize, iter_exprs: Vec, pattern_ref: usize, + body_ref: usize, else_ref: usize): usize { + push_view(&self.label_stack, label_name(self, label_ref)); + push_scope(self); + + var nsrc = iter_exprs.len; + var pat = node(self, pattern_ref); + var is_list = pat != null && kind_is_body_pattern_list(pat); + if is_list { + var atoms = ref_vec(); + match pat.body { .PatternList(pl) => { atoms = pl.items; }, _ => {} } + if atoms.len != nsrc { + diag(self, pat.span, .TypeMismatch, "for: pattern count != source count"); + } + var i: usize = 0; + while i < nsrc && i < atoms.len { + var ct = infer(self, list_at(iter_exprs, i)); + bind_pattern(self, list_at(atoms, i), for_elem_type(self, ct)); + i = i + 1; + } + } else { + if nsrc > 1 { + diag(self, n.span, .TypeMismatch, "for: multiple sources require pattern list"); + } + var i: usize = 0; + while i < nsrc { + var ct = infer(self, list_at(iter_exprs, i)); + if i == 0 { bind_pattern(self, pattern_ref, for_elem_type(self, ct)); } + i = i + 1; + } + } + + if body_ref != 0 { ignore_usize(infer(self, body_ref)); } + if else_ref != 0 { ignore_usize(infer(self, else_ref)); } + + pop_scope(self); + pop_label(self); + ret 0; +} + +fn kind_is_body_pattern_list(n: *Node): bool { + match n.body { .PatternList(x) => { ret true; }, _ => { ret false; } } + ret false; +} + +fn infer_fn_decl(self: *Sema, n: *Node, params_ref: Vec, ret_ref: usize, body_ref: usize): usize { + if n.resolved_def_id == 0 { ret 0; } + var d = def_node(self, n.resolved_def_id); + if d == null { ret 0; } + var owner_def_id = d.owner_def; + var fn_tid = d.lang_id; + if fn_tid == 0 { fn_tid = eval_decl(self, n.resolved_def_id); } + var fn_ln = lang_of(self, fn_tid); + if fn_ln == null || !kind_is(self, fn_tid, .Fn) { ret 0; } + var ret_tid: usize = 0; + match fn_ln.extra { .Fn(f) => { ret_tid = f.ret_type; }, _ => {} } + if body_ref == 0 { ret fn_tid; } + + var saved_agg = self.current_aggregate_lang; + if owner_def_id != 0 { + var owner = def_node(self, owner_def_id); + if owner != null && owner.lang_id != 0 { + self.current_aggregate_lang = owner.lang_id; + } + } + + push_scope(self); + var i: usize = 0; + while i < params_ref.len { + var pref = list_at(params_ref, i); + var pd = node(self, pref); + if pd != null && is_param_decl(pd) { + var pname = ByteView.empty(); + var ptype: usize = 0; + match pd.body { .ParamDecl(p) => { pname = p.name; ptype = p.type_expr; }, _ => {} } + var psym = Symbol { .name = pname, .kind = .Var, .node = 0 }; + psym.node = LangType.alloc_def(&self.types, .Var, pname, pd.span, self.ast as *any, pref); + var p_tid = infer(self, ptype); + var pdef = def_node(self, psym.node); + if pdef != null { + pdef.lang_id = p_tid; + pdef.state = if p_tid != 0 { .Resolved } else { .Error }; + } + pd.resolved_def_id = psym.node; + if !Scope.insert(&self.scope, self.scope.current_scope, psym) { + diag_name(self, pd.span, .DuplicateSymbol, "duplicate symbol `", pname); + } + } + i = i + 1; + } + var old_ret = self.current_fn_ret_tid; + self.current_fn_ret_tid = ret_tid; + ignore_usize(infer(self, body_ref)); + self.current_fn_ret_tid = old_ret; + pop_scope(self); + self.current_aggregate_lang = saved_agg; + ret fn_tid; +} + +fn infer_type_decl(self: *Sema, n: *Node, pattern_ref: usize, type_ref: usize): usize { + var pat = node(self, pattern_ref); + if pat == null || !is_pattern_ident(pat) { ret 0; } + if type_ref == 0 { ret 0; } + var def: *DefNode = null; + if n.resolved_def_id != 0 { def = def_node(self, n.resolved_def_id); } + if def == null { ret 0; } + var lang = def.lang_id; + if lang == 0 { lang = eval_decl(self, n.resolved_def_id); } + if lang == 0 { ret 0; } + var te = node(self, type_ref); + if te != null && is_type_aggregate(te) { + var members = ref_vec(); + match te.body { .TypeAggregate(a) => { members = a.members; }, _ => {} } + var i: usize = 0; + while i < members.len { + var mref = list_at(members, i); + var mn = node(self, mref); + if mn != null && kind_is_body_fn_decl(mn) { ignore_usize(infer(self, mref)); } + i = i + 1; + } + } + ret lang; +} + +fn kind_is_body_fn_decl(n: *Node): bool { + match n.body { .FnDecl(x) => { ret true; }, _ => { ret false; } } + ret false; +} + +fn infer_ret(self: *Sema, n: *Node, expr_ref: usize): usize { + if expr_ref != 0 { + if self.current_fn_ret_tid != 0 { + ignore_usize(coerce(self, expr_ref, self.current_fn_ret_tid)); + } else { + ignore_usize(infer(self, expr_ref)); + } + } else if self.current_fn_ret_tid != 0 { + var u = make_unit(self); + if self.current_fn_ret_tid != u { + diag(self, n.span, .MustReturnValue, "function must return a value"); + } + } + ret 0; +} + +fn infer_break(self: *Sema, n: *Node, label_ref: usize, expr_ref: usize): usize { + var lbl = label_name(self, label_ref); + if self.label_stack.len == 0 { + diag(self, n.span, .BreakOutsideLoop, "`break` outside of loop"); + ret 0; + } + if lbl.len != 0 && !label_in_stack(self, lbl) { + diag_name(self, n.span, .UndefinedLabel, "undefined label `$", lbl); + } + if expr_ref != 0 { + if self.current_loop_break_tid != 0 { + ignore_usize(coerce(self, expr_ref, self.current_loop_break_tid)); + } else { + ignore_usize(infer(self, expr_ref)); + } + } + ret 0; +} + +fn infer_continue(self: *Sema, n: *Node, label_ref: usize): usize { + var lbl = label_name(self, label_ref); + if self.label_stack.len == 0 { + diag(self, n.span, .ContinueOutsideLoop, "`continue` outside of loop"); + ret 0; + } + if lbl.len != 0 && !label_in_stack(self, lbl) { + diag_name(self, n.span, .UndefinedLabel, "undefined label `$", lbl); + } + ret 0; +} + +fn label_in_stack(self: *Sema, lbl: ByteView): bool { + var i = self.label_stack.len; + while i > 0 { + var cur: ByteView = ByteView.empty(); + match Vec.get(&self.label_stack, i - 1) { + .Ok(ptr) => { cur = (ptr as *ByteView).*; }, + .Err(e) => {}, + } + if cur.len != 0 && ByteView.eq(cur, lbl) { ret true; } + i = i - 1; + } + ret false; +} + +// ---- dump(镜像 stage1 spl_sema_dump;供 splc1 --dump sema 人读)---- + +type SymPrint = struct { + key: ByteView, + name: ByteView, + node: usize, +} + +fn sym_at(v: *Vec, i: usize): *SymPrint { + match Vec.get(v, i) { + .Ok(ptr) => { ret ptr as *SymPrint; }, + .Err(error) => { ret null; }, + } + ret null; +} + +fn out_str(buf: *ByteBuffer, s: []u8): bool { + match ByteBuffer.append(buf, s) { + .Ok => { ret true; }, + .Err(error) => { ret false; }, + } + ret false; +} + +fn out_u(buf: *ByteBuffer, v: usize): bool { + match append_usize(buf, v, 10, false) { + .Ok => { ret true; }, + .Err(error) => { ret false; }, + } + ret false; +} + +fn dump(self: *Sema, out: *ByteBuffer): bool { + if !out_str(out, "Scopes:\n") { ret false; } + var i: usize = 1; + while i < self.scope.scopes.len { + var sn = Scope.node(&self.scope, i); + if sn == null { i = i + 1; continue; } + if !out_str(out, "scope[") { ret false; } + if !out_u(out, i) { ret false; } + if !out_str(out, "] parent=") { ret false; } + if !out_u(out, sn.parent) { ret false; } + if !out_str(out, "\n") { ret false; } + + // 收集后按符号名排序输出(与 stage1 一致,顺序与哈希布局解耦)。 + var entries = Vec.blank(@sizeof(SymPrint), @alignof(SymPrint)); + var sc = Map.slot_count(sn.symbols); + var j: usize = 0; + while j < sc { + if Map.slot_occupied(Map.slot_state(sn.symbols, j)) { + var kp = Map.key_at(&sn.symbols, j); + var vp = Map.val_at(&sn.symbols, j); + var sym = (vp as *Symbol).*; + var sp = SymPrint { + .key = (kp as *ByteView).*, + .name = sym.name, + .node = sym.node, + }; + match Vec.push(&entries, &sp as *u8) { + .Ok => {}, + .Err(error) => {}, + } + } + j = j + 1; + } + + // 插入排序(作用域内符号很少) + var a: usize = 1; + while a < entries.len { + var cur = sym_at(&entries, a).*; + var b = a; + while b > 0 { + var prev = sym_at(&entries, b - 1).*; + if ByteView.cmp(prev.key, cur.key) <= 0 { break; } + var pv = prev; + match Vec.set(&entries, b, &pv as *u8) { .Ok => {}, .Err(error) => {} } + b = b - 1; + } + var cv = cur; + match Vec.set(&entries, b, &cv as *u8) { .Ok => {}, .Err(error) => {} } + a = a + 1; + } + + var e: usize = 0; + while e < entries.len { + var sp2 = sym_at(&entries, e).*; + if !out_str(out, " ") { ret false; } + if !out_str(out, ByteView.as_slice(sp2.key)) { ret false; } + if !out_str(out, " -> def`") { ret false; } + if !out_str(out, ByteView.as_slice(sp2.name)) { ret false; } + if !out_str(out, "`#") { ret false; } + if !out_u(out, sp2.node) { ret false; } + if !out_str(out, "\n") { ret false; } + e = e + 1; + } + Vec.drop(&entries); + i = i + 1; + } + + if !out_str(out, "LangTypeTable:\n") { ret false; } + i = 1; + while i < self.types.lang_table.len { + if !out_str(out, " lang_id#") { ret false; } + if !out_u(out, i) { ret false; } + if !out_str(out, " ") { ret false; } + if !LangType.dump(&self.types, i, out) { ret false; } + if !out_str(out, "\n") { ret false; } + i = i + 1; + } + + if !out_str(out, "TypeTable:\n") { ret false; } + i = 1; + while i < self.types.ir.table.len { + if !out_str(out, " type_id#") { ret false; } + if !out_u(out, i) { ret false; } + if !out_str(out, " ") { ret false; } + if !TypeTable.pure_dump(&self.types.ir, i, out) { ret false; } + if !out_str(out, "\n") { ret false; } + i = i + 1; + } + + if !out_str(out, "DefTable:\n") { ret false; } + i = 1; + while i < self.types.defs.table.len { + if !out_str(out, " def#") { ret false; } + if !out_u(out, i) { ret false; } + if !out_str(out, " ") { ret false; } + if !DefTable.dump(&self.types.defs, i, out) { ret false; } + if !out_str(out, "\n") { ret false; } + i = i + 1; + } + + ret Attr.dump(&self.attr, out); +} + +// ---- 测试 ---- + +// 走完整前端:lex -> parse -> sema;返回 0 表示无(新)诊断。 +fn sema_source(text: []u8): i32 { + var sources = set_new(); + match set_add(&sources, ByteView.from_slice(""), ByteView.from_slice(text)) { + .Ok(idx) => {}, + .Err(error) => { ret 99; }, + } + + var diag = Diag.new(&sources); + + var text_view = ByteView.from_slice(text); + var tokens = Vec.blank(@sizeof(Token), @alignof(Token)); + match Lexer.lex(text_view, &diag, 0) { + .Ok(v) => { tokens = v; }, + .Err(error) => { + Vec.drop(&tokens); + Diag.drop(&diag); + set_drop(&sources); + ret 99; + }, + } + + var ast: Ast = undefined; + match Ast.new(&sources) { + .Ok(v) => { ast = v; }, + .Err(error) => { + Vec.drop(&tokens); + Diag.drop(&diag); + set_drop(&sources); + ret 99; + }, + } + + var p = Parser.new(&ast, tokens, &diag); + Parser.parse(&p); + if Diag.has_error(diag) || p.failed { + Ast.drop(&ast); + Vec.drop(&tokens); + Diag.drop(&diag); + set_drop(&sources); + ret 1; + } + + var sema: Sema = undefined; + match new(&ast, &diag) { + .Ok(v) => { sema = v; }, + .Err(error) => { + Ast.drop(&ast); + Vec.drop(&tokens); + Diag.drop(&diag); + set_drop(&sources); + ret 99; + }, + } + run(&sema); + var bad = Diag.has_error(diag); + drop(&sema); + Ast.drop(&ast); + Vec.drop(&tokens); + Diag.drop(&diag); + set_drop(&sources); + if bad { ret 2; } + ret 0; +} + +fn main() { + @assert(sema_source("fn main(): i32 { ret 0; }") == 0, "sema simple"); + @assert(sema_source("fn main(): i32 { var x: i32 = 1; ret x; }") == 0, "sema var"); + @assert(sema_source("fn main(): i32 { ret 0; }\nfn bad(): i32 { ret nope; }") != 0, + "sema undefined"); +} diff --git a/stage2/splc1.spl b/stage2/splc1.spl index 4bbf31a..7dd0045 100644 --- a/stage2/splc1.spl +++ b/stage2/splc1.spl @@ -1,8 +1,8 @@ // Expect retcode = 0 // splc1 (stage2 自举编译器入口)。 -// 当前仅实现 `--dump lex [out]`:读取源文件,词法分析,按与 stage1 -// splc0 完全一致的格式打印 token 列表,供 diff 测试逐字节比对。 +// 当前实现 `--dump [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 = * + 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 [out]"); - ret 1; + var arg1 = ByteView.from_cstr(argv[1]); + + if !ByteView.eq(arg1, ByteView.from_slice("--dump")) { + // 编译模式:splc1 + if argc < 3 { + Logger.fatal(&log, "usage: splc1 | splc1 --dump "); + 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 [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 [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; } diff --git a/stage2/sys/time.spl b/stage2/sys/time.spl new file mode 100644 index 0000000..f55f76e --- /dev/null +++ b/stage2/sys/time.spl @@ -0,0 +1,45 @@ +// 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 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"); +} diff --git a/stage2/sys/vm.spl b/stage2/sys/vm.spl new file mode 100644 index 0000000..71d49cb --- /dev/null +++ b/stage2/sys/vm.spl @@ -0,0 +1,13 @@ + +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, buffer: []u8): void; + +#extern(vm) fn vm_push(vm: *any, val: spl_vm_val_t): void; +// TODO + +fn main() {} diff --git a/stage2/type_table.spl b/stage2/type_table.spl new file mode 100644 index 0000000..5adf3f9 --- /dev/null +++ b/stage2/type_table.spl @@ -0,0 +1,917 @@ +// Expect retcode = 0 + +// IR 类型表(移植自 stage1/spl_type.c)。类型按结构 hash-consing(intern), +// id 0 为保留哨兵(Error)。节点借用式持有:名称是 ByteView(指向源/AST), +// 不复制字符串;聚合成员与函数参数的 Vec 由本表拥有,drop 时统一释放。 +// +// hash 只用于选桶;相等永远由结构比较决定。桶以「完整 hash -> 链头 id」的 +// Map 存储,节点自带 hash_next 串链(对应 stage1 的 spl_fold)。 + +type .{ .Error } = @import("utils/error.spl"); +type .{ .Vec } = @import("utils/vec.spl"); +type .{ .Map } = @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, +} + +// 与 stage1 spl_type_node_kind_t 一一对应;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, +} + +// ---- hash(FNV 风格;值不必与 stage1 相同,只要结构一致即可)---- + +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 { + match Vec.get(v, i) { + .Ok(ptr) => { ret ptr as *Member; }, + .Err(error) => { ret null; }, + } + ret null; +} + +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(对应 stage1 spl_type_node)。 +fn node(self: *TypeTable, id: usize): *TypeEntry { + if id == 0 || id >= self.table.len { ret null; } + match Vec.get(&self.table, id) { + .Ok(ptr) => { ret ptr as *TypeEntry; }, + .Err(error) => { ret null; }, + } + ret null; +} + +// ---- 生命周期 ---- + +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_usize, eq_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; +} + +// ---- intern ---- + +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; + match Map.put(&self.fold, &hash as *u8, &id as *u8) { + .Ok => {}, + .Err(error) => {}, + } +} + +// 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; +} + +// ---- builders ---- + +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; +} + +// ---- dump(镜像 stage1 spl_type_pure_dump)---- + +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, ""); + } + 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; + match Vec.pop(stack, &popped as *u8) { + .Ok => {}, + .Err(error) => {}, + } + 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; + match Vec.pop(stack, &dropped as *u8) { + .Ok => {}, + .Err(error) => {}, + } + 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(¶ms, &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(¶ms2, &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(¶ms, &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(); +} diff --git a/stage2/utils/byte_buffer.spl b/stage2/utils/byte_buffer.spl index a7bc8ff..9691a3b 100644 --- a/stage2/utils/byte_buffer.spl +++ b/stage2/utils/byte_buffer.spl @@ -24,6 +24,12 @@ fn new(): ByteBuffer { ret ByteBuffer { .storage = Vec.blank(1, 1) }; } +fn with_capacity(cap: usize): ByteBuffer { + var b = new(); + match reserve(&b, cap) { .Ok => {}, .Err(error) => {} } + ret b; +} + fn size(self: ByteBuffer): usize { ret self.storage.len; } diff --git a/stage2/utils/memory.spl b/stage2/utils/memory.spl index 31d5bc2..21f85b9 100644 --- a/stage2/utils/memory.spl +++ b/stage2/utils/memory.spl @@ -4,6 +4,10 @@ type .{ .Error, .Status, .make } = @import("error.spl"); +#extern(vm) fn vm_memcpy(dst: *any, src: *any, n: usize): *any; + +fn ignore_ptr(p: *any) {} + type SizeResult = enum { Ok: usize, Err: Error, @@ -69,6 +73,18 @@ fn move(dst: []u8, src: []u8): Status { } if dst_addr == src_addr { ret .Ok; } + // 非重叠且较大时走原生 memcpy(Vec 扩容复制是常见场景)。 + var non_overlap = false; + if dst_addr < src_addr { + if src_addr - dst_addr >= count { non_overlap = true; } + } else { + if dst_addr - src_addr >= count { non_overlap = true; } + } + if non_overlap && count >= 16 { + ignore_ptr(vm_memcpy(dst.ptr as *any, src.ptr as *any, count)); + ret .Ok; + } + if dst_addr < src_addr { var i: usize = 0; while i < count { diff --git a/stage2/utils/profile.spl b/stage2/utils/profile.spl new file mode 100644 index 0000000..0413060 --- /dev/null +++ b/stage2/utils/profile.spl @@ -0,0 +1,303 @@ +// Expect retcode = 0 + +// 全量热点/耗时记录器(配合 @file_name()/@line()/@col() 与 sys/time.spl)。 +// 每个调用点聚合:count、独占(self)与包含(total)的 steps/ns。 +// - total_* 含被调用者(嵌套会重复,跨站点求和会超过墙钟) +// - 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; + match Vec.push(&self.stack, &token as *u8) { .Ok => {}, .Err(error) => {} } + 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; + match Vec.pop(&self.stack, &popped as *u8) { .Ok => {}, .Err(error) => {} } + 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; + } + } +} + +// ---- dump / save ---- + +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 { + match Vec.push(&idx, &i as *u8) { .Ok => {}, .Err(error) => {} } + 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 { ignore_usize(v + 1); } +} + +fn ignore_usize(v: usize) {} + +fn main() { + test_profile(); +} diff --git a/stage2/utils/vec.spl b/stage2/utils/vec.spl index 2f62caf..9c98127 100644 --- a/stage2/utils/vec.spl +++ b/stage2/utils/vec.spl @@ -49,14 +49,17 @@ fn new(elem_size: usize, elem_align: usize): VecResult { ret .Ok(blank(elem_size, elem_align)); } -fn valid(self: Vec): bool { - if self.elem_size == 0 || !valid_alignment(self.elem_align) { ret false; } +// 按指针接收,避免每次调用复制整个 Vec;内联对齐检查,省一次函数调用。 +fn valid(self: *Vec): bool { + if self.elem_size == 0 { ret false; } + var a = self.elem_align; + if a == 0 || (a & (a - 1)) != 0 { ret false; } if self.len > self.cap { ret false; } if self.cap == 0 { ret self.raw == null && self.data == null && self.len == 0; } if self.raw == null || self.data == null { ret false; } - ret (self.data as usize & (self.elem_align - 1)) == 0; + ret (self.data as usize & (a - 1)) == 0; } fn status_error(kind: Kind): Status { @@ -108,9 +111,8 @@ fn allocate(capacity: usize, elem_size: usize, elem_align: usize): VecResult { }); } -// 保证至少可容纳 need 个元素。所有检查在原对象改变前完成。 -fn reserve(self: *Vec, need: usize): Status { - if !valid(self.*) { ret status_error(.InvalidArgument); } +// 与 reserve 相同,但假定调用方已校验 self(内部热路径用,省去重复 valid)。 +fn reserve_raw(self: *Vec, need: usize): Status { if need <= self.cap { ret .Ok; } var new_cap = grow_capacity(self.cap, 8, need); @@ -140,14 +142,25 @@ fn reserve(self: *Vec, need: usize): Status { ret .Ok; } +// 保证至少可容纳 need 个元素(外部入口,含校验)。 +fn reserve(self: *Vec, need: usize): Status { + if !valid(self) { ret status_error(.InvalidArgument); } + ret reserve_raw(self, need); +} + +// 预分配恰好 capacity 个元素(等价“with_capacity”)。 +fn with_capacity(elem_size: usize, elem_align: usize, capacity: usize): VecResult { + ret allocate(capacity, elem_size, elem_align); +} + fn get(self: *Vec, index: usize): PtrResult { - if !valid(self.*) { ret .Err(Error.make(.InvalidArgument)); } + if !valid(self) { ret .Err(Error.make(.InvalidArgument)); } if index >= self.len { ret .Err(Error.make(.OutOfRange)); } ret .Ok(self.data + index * self.elem_size); } fn set(self: *Vec, index: usize, elem: *u8): Status { - if !valid(self.*) || elem == null { ret status_error(.InvalidArgument); } + if !valid(self) || elem == null { ret status_error(.InvalidArgument); } if index >= self.len { ret status_error(.OutOfRange); } var dst = self.data + index * self.elem_size; ret move(dst[0..self.elem_size], elem[0..self.elem_size]); @@ -155,7 +168,7 @@ fn set(self: *Vec, index: usize, elem: *u8): Status { // 追加 count 个连续元素。elems 可以指向 self 当前已初始化的元素区。 fn append(self: *Vec, elems: *u8, count: usize): Status { - if !valid(self.*) { ret status_error(.InvalidArgument); } + if !valid(self) { ret status_error(.InvalidArgument); } if count == 0 { ret .Ok; } if elems == null { ret status_error(.InvalidArgument); } @@ -190,7 +203,7 @@ fn append(self: *Vec, elems: *u8, count: usize): Status { } } - match reserve(self, new_len) { + match reserve_raw(self, new_len) { .Ok => {}, .Err(error) => { ret .Err(error); }, } @@ -209,9 +222,23 @@ fn push(self: *Vec, elem: *u8): Status { ret append(self, elem, 1); } +// 热路径:假定 self 有效且已有容量时直接写入;容量不足才回退到 append。 +fn push_raw(self: *Vec, elem: *u8): bool { + if self.len < self.cap { + copy(self.data + self.len * self.elem_size, elem, self.elem_size); + self.len = self.len + 1; + ret true; + } + match append(self, elem, 1) { + .Ok => { ret true; }, + .Err(error) => { ret false; }, + } + ret false; +} + // 将末元素复制到 out,然后缩短数组。out 可以与元素区重叠。 fn pop(self: *Vec, out: *u8): Status { - if !valid(self.*) || out == null { ret status_error(.InvalidArgument); } + if !valid(self) || out == null { ret status_error(.InvalidArgument); } if self.len == 0 { ret status_error(.OutOfRange); } var source = self.data + (self.len - 1) * self.elem_size; match move(out[0..self.elem_size], source[0..self.elem_size]) { @@ -229,7 +256,7 @@ fn clear(self: *Vec) { // 成功时先释放 out 原有存储再提交副本;失败保持 out 不变。 // out 与 src 指向同一个 Vec 也允许。 fn clone(out: *Vec, src: Vec): Status { - if !valid(out.*) || !valid(src) { ret status_error(.InvalidArgument); } + if !valid(out) || !valid(&src) { ret status_error(.InvalidArgument); } if out.elem_size != src.elem_size || out.elem_align != src.elem_align { ret status_error(.InvalidArgument); }