Compare commits
2
Commits
e02b0c633a
...
51d408d524
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51d408d524 | ||
|
|
846f441e3a |
No files matched your search
@@ -109,6 +109,10 @@ PostfixExpr = {
|
||||
|
||||
ExprList = { (Expr ~ COMMA)* ~ Expr? }
|
||||
|
||||
// 注:类型即表达式**仅限 comptime 上下文**(`comptime { ... }`、`comptime expr`、
|
||||
// `comptime fn` 的函数体)内。在这些上下文中,PrimaryExpr 额外接受
|
||||
// `AggregateTypeExpr`(如 `struct { ... }`)以及 `type` 元类型;其它上下文
|
||||
// 中类型只允许出现在 TypeExpr 位置。
|
||||
PrimaryExpr = {
|
||||
FLOAT_LITERAL
|
||||
| INTEGER_LITERAL
|
||||
@@ -232,6 +236,7 @@ TypePrimary = {
|
||||
| KEYWORD_bool
|
||||
| KEYWORD_void
|
||||
| KEYWORD_ptr
|
||||
| KEYWORD_type // `type` 元类型(编译期类型的值类型)
|
||||
| FnTypeExpr
|
||||
| AggregateTypeExpr
|
||||
| TypeParenExpr
|
||||
|
||||
@@ -42,8 +42,69 @@ static const char *splc0_srcs[] = {
|
||||
"stage1/spl_ast2ir.c",
|
||||
"stage1/spl_layout.c",
|
||||
"stage1/spl_ir2vm.c",
|
||||
"stage1/ir_opt.c",
|
||||
"stage1/vm_opt.c",
|
||||
"stage0/spl_mcode.c",
|
||||
};
|
||||
|
||||
static const char* splc1_deps[] = {
|
||||
"stage2/sys/exit.spl",
|
||||
"stage2/sys/file.spl",
|
||||
"stage2/sys/mem.spl",
|
||||
"stage2/sys/spl_rt.h",
|
||||
"stage2/sys/spl_vm_embed.h",
|
||||
"stage2/sys/time.spl",
|
||||
"stage2/sys/vm.spl",
|
||||
"stage2/utils/algorithm.spl",
|
||||
"stage2/utils/arena.spl",
|
||||
"stage2/utils/ascii.spl",
|
||||
"stage2/utils/byte_buffer.spl",
|
||||
"stage2/utils/byte_view.spl",
|
||||
"stage2/utils/error.spl",
|
||||
"stage2/utils/fmt.spl",
|
||||
"stage2/utils/hash.spl",
|
||||
"stage2/utils/io.spl",
|
||||
"stage2/utils/json.spl",
|
||||
"stage2/utils/log.spl",
|
||||
"stage2/utils/lsp_frame.spl",
|
||||
"stage2/utils/map.spl",
|
||||
"stage2/utils/memory.spl",
|
||||
"stage2/utils/mp.spl",
|
||||
"stage2/utils/path.spl",
|
||||
"stage2/utils/profile.spl",
|
||||
"stage2/utils/set.spl",
|
||||
"stage2/utils/source.spl",
|
||||
"stage2/utils/str.spl",
|
||||
"stage2/utils/utf8.spl",
|
||||
"stage2/utils/value.spl",
|
||||
"stage2/utils/vec.spl",
|
||||
"stage2/ast.spl",
|
||||
"stage2/ast2ir.spl",
|
||||
"stage2/attr.spl",
|
||||
"stage2/def.spl",
|
||||
"stage2/diag.spl",
|
||||
"stage2/eval.spl",
|
||||
"stage2/intrin.spl",
|
||||
"stage2/ir.spl",
|
||||
"stage2/ir2c.spl",
|
||||
"stage2/ir2vm.spl",
|
||||
"stage2/ir_builder.spl",
|
||||
"stage2/lang_type.spl",
|
||||
"stage2/layout.spl",
|
||||
"stage2/lexer.spl",
|
||||
"stage2/mcode.spl",
|
||||
"stage2/parser.spl",
|
||||
"stage2/scope.spl",
|
||||
"stage2/sema.spl",
|
||||
"stage2/splc1.spl",
|
||||
"stage2/token.spl",
|
||||
"stage2/type_table.spl",
|
||||
};
|
||||
|
||||
static const char* splc2_deps[] = {
|
||||
|
||||
};
|
||||
|
||||
/* clang-format on */
|
||||
|
||||
static const char *obj_path(const char *src) {
|
||||
@@ -74,6 +135,8 @@ static const char *sir_path(const char *name) {
|
||||
return nob_temp_sprintf(BUILD_FOLDER "%s.sir", name);
|
||||
}
|
||||
|
||||
static const char *c_path(const char *name) { return nob_temp_sprintf(BUILD_FOLDER "%s.c", name); }
|
||||
|
||||
static bool sync_cflags_stamp(void) {
|
||||
Nob_String_View cflags = nob_sv_from_cstr(CFLAGS_STR);
|
||||
Nob_String_Builder old = {0};
|
||||
@@ -105,6 +168,7 @@ typedef struct build {
|
||||
BUILD_EXE,
|
||||
BUILD_EXE_WITH_OPTIMIZATION,
|
||||
BUILD_SIR,
|
||||
BUILD_NATIVE,
|
||||
} kind;
|
||||
union {
|
||||
struct {
|
||||
@@ -116,7 +180,15 @@ typedef struct build {
|
||||
struct {
|
||||
const char *src;
|
||||
const char *compiler;
|
||||
const char **deps;
|
||||
size_t deps_count;
|
||||
} sir;
|
||||
struct {
|
||||
const char *src;
|
||||
const char *compiler;
|
||||
const char **deps;
|
||||
size_t deps_count;
|
||||
} native;
|
||||
};
|
||||
} build_t;
|
||||
|
||||
@@ -199,6 +271,60 @@ defer:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *build_native(const char *name) {
|
||||
const char *exe = exe_path(name);
|
||||
const char *cli = build_target("spl_cli");
|
||||
const char *sir = build_target("splc1");
|
||||
if (!cli || !sir) {
|
||||
nob_log(NOB_ERROR, "failed to build prerequisites for `%s`", name);
|
||||
return NULL;
|
||||
}
|
||||
const char *cpath = BUILD_FOLDER "splc1.c";
|
||||
const char *inputs[] = {sir,
|
||||
"stage2/sys/spl_rt.h",
|
||||
"stage2/sys/spl_vm_embed.h",
|
||||
"stage2/sys/spl_vm_embed.c",
|
||||
"stage0/spl_vm.c",
|
||||
"stage0/spl_mcode.c",
|
||||
"stage0/spl_syscall.c",
|
||||
"stage0/spl_vm.h",
|
||||
"stage0/spl_mcode.h"};
|
||||
int needs = nob_needs_rebuild(exe, inputs, NOB_ARRAY_LEN(inputs));
|
||||
if (needs < 0)
|
||||
return NULL;
|
||||
if (needs == 0)
|
||||
return exe;
|
||||
|
||||
Nob_Cmd cmd = {0};
|
||||
if (nob_file_exists(exe)) {
|
||||
// 已有原生编译器:用原生自举重新生成 C(0.1s 级)
|
||||
nob_cmd_append(&cmd, exe, "--dump", "c", "stage2/splc1.spl", cpath);
|
||||
} else {
|
||||
// 冷启动:用 VM 版 splc1 生成 C
|
||||
nob_cmd_append(&cmd, cli, sir, "--dump", "c", "stage2/splc1.spl", cpath);
|
||||
}
|
||||
if (!nob_cmd_run(&cmd)) {
|
||||
nob_log(NOB_ERROR, "ir2c C generation failed");
|
||||
nob_cmd_free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
cmd.count = 0;
|
||||
nob_cc(&cmd);
|
||||
nob_cc_inputs(&cmd, cpath);
|
||||
nob_cc_inputs(&cmd, "stage2/sys/spl_vm_embed.c");
|
||||
nob_cc_inputs(&cmd, "stage0/spl_vm.c");
|
||||
nob_cc_inputs(&cmd, "stage0/spl_mcode.c");
|
||||
nob_cc_inputs(&cmd, "stage0/spl_syscall.c");
|
||||
nob_cc_output(&cmd, exe);
|
||||
if (!nob_cmd_run(&cmd)) {
|
||||
nob_log(NOB_ERROR, "native link failed");
|
||||
nob_cmd_free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
nob_cmd_free(cmd);
|
||||
return exe;
|
||||
}
|
||||
|
||||
static build_t targets[] = {
|
||||
{
|
||||
"spl_cli",
|
||||
@@ -235,17 +361,50 @@ static build_t targets[] = {
|
||||
{
|
||||
"splc1",
|
||||
BUILD_SIR,
|
||||
.sir = {.src = "stage2/splc1.spl", .compiler = "splc0"},
|
||||
.sir = {.src = "stage2/splc1.spl",
|
||||
.compiler = "splc0",
|
||||
.deps = splc1_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
|
||||
},
|
||||
{
|
||||
"splc1s",
|
||||
BUILD_SIR,
|
||||
.sir = {.src = "stage2/splc1.spl", .compiler = "splc1"},
|
||||
.sir = {.src = "stage2/splc1.spl",
|
||||
.compiler = "splc1",
|
||||
.deps = splc1_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
|
||||
},
|
||||
{
|
||||
"splc1r",
|
||||
BUILD_SIR,
|
||||
.sir = {.src = "stage2/splc1.spl", .compiler = "splc1s"},
|
||||
.sir = {.src = "stage2/splc1.spl",
|
||||
.compiler = "splc1s",
|
||||
.deps = splc1_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
|
||||
},
|
||||
{
|
||||
"splc1n",
|
||||
BUILD_NATIVE,
|
||||
.native = {.src = "stage2/splc1.spl",
|
||||
.compiler = "splc1",
|
||||
.deps = splc1_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
|
||||
},
|
||||
{
|
||||
"splc2",
|
||||
BUILD_SIR,
|
||||
.sir = {.src = "stage3/splc2.spl",
|
||||
.compiler = "splc1n",
|
||||
.deps = splc2_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc2_deps)},
|
||||
},
|
||||
{
|
||||
"splc2n",
|
||||
BUILD_NATIVE,
|
||||
.native = {.src = "stage3/splc2.spl",
|
||||
.compiler = "splc1n",
|
||||
.deps = splc2_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc2_deps)},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -274,6 +433,38 @@ const char *build_target(const char *name) {
|
||||
nob_cmd_free(cmd);
|
||||
return target_sir;
|
||||
}
|
||||
case BUILD_NATIVE: {
|
||||
Nob_Cmd cmd = {0};
|
||||
const char *target_exe = exe_path(name);
|
||||
const char *target_c = c_path(name);
|
||||
|
||||
// TODO need_rebuild
|
||||
int needs =
|
||||
nob_needs_rebuild(target_exe, target->native.deps, target->native.deps_count);
|
||||
if (needs < 0)
|
||||
return NULL;
|
||||
if (needs == 0)
|
||||
return target_exe;
|
||||
|
||||
nob_cmd_append(&cmd, "--dump", "c", target->native.src, target_c);
|
||||
if (!run_target(target->native.compiler, &cmd)) {
|
||||
nob_log(NOB_ERROR, "failed to build `%s`", name);
|
||||
nob_cmd_free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
cmd.count = 0;
|
||||
nob_cc(&cmd);
|
||||
nob_cmd_append(&cmd, "-I./stage2/sys", "-O2", "-D_CRT_SECURE_NO_WARNINGS");
|
||||
nob_cc_inputs(&cmd, target_c);
|
||||
nob_cc_output(&cmd, target_exe);
|
||||
if (!nob_cmd_run(&cmd)) {
|
||||
nob_log(NOB_ERROR, "failed to build `%s`", name);
|
||||
nob_cmd_free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
nob_cmd_free(cmd);
|
||||
return target_exe;
|
||||
}
|
||||
default:
|
||||
NOB_UNREACHABLE("build_t.kind");
|
||||
}
|
||||
@@ -285,6 +476,7 @@ const char *build_target(const char *name) {
|
||||
static run_t runs[] = {
|
||||
{"spl_cli", NULL}, {"vm_test", NULL}, {"splc0", NULL},
|
||||
{"splc1", "spl_cli"}, {"splc1r", "spl_cli"}, {"splc1s", "spl_cli"},
|
||||
{"splc1n", NULL}, {"splc2", "spl_cli"}, {"splc2n", NULL},
|
||||
};
|
||||
|
||||
bool run_target_dry(const char *name, Nob_Cmd *args, Nob_Cmd *result) {
|
||||
|
||||
@@ -33,6 +33,7 @@ typedef struct {
|
||||
int profile;
|
||||
int profile_interval;
|
||||
const char *profile_out;
|
||||
int stats;
|
||||
const char *entry;
|
||||
const char *path; /* file.sir */
|
||||
const char **args; /* spl 程序参数(file.sir 之后) */
|
||||
@@ -48,6 +49,7 @@ static void usage(void) {
|
||||
" --trace print every instruction\n"
|
||||
" --profile[=N] native sampling profiler (every N insns, default 10000)\n"
|
||||
" --profile-out <p> profile output prefix (default build/profile)\n"
|
||||
" --stats print executed instructions / call count at exit\n"
|
||||
" -h, --help show help\n");
|
||||
}
|
||||
|
||||
@@ -72,6 +74,8 @@ static int parse_args(int argc, const char **argv, cli_opts_t *o) {
|
||||
o->trace = 1;
|
||||
} else if (strcmp(a, "--disasm") == 0) {
|
||||
o->disasm = 1;
|
||||
} else if (strcmp(a, "--stats") == 0) {
|
||||
o->stats = 1;
|
||||
} else if (strcmp(a, "--profile") == 0) {
|
||||
o->profile = 1;
|
||||
} else if (strncmp(a, "--profile=", 10) == 0) {
|
||||
@@ -656,6 +660,8 @@ int main(int argc, const char **argv) {
|
||||
if (o.debug) {
|
||||
fprintf(stderr, "spl_cli: debug mode on\n");
|
||||
spl_vm_set_debug(&vm, 1);
|
||||
} else {
|
||||
spl_vm_set_debug(&vm, 0);
|
||||
}
|
||||
if (o.trace) {
|
||||
fprintf(stderr, "spl_cli: trace mode on\n");
|
||||
@@ -710,6 +716,11 @@ int main(int argc, const char **argv) {
|
||||
fprintf(stderr, "spl_cli: hit breakpoint (run with -g to debug)\n");
|
||||
}
|
||||
|
||||
if (o.stats) {
|
||||
fprintf(stderr, "spl_cli: stats steps=%llu calls=%llu\n", (unsigned long long)vm.steps,
|
||||
(unsigned long long)vm.ncalls);
|
||||
}
|
||||
|
||||
if (o.profile) {
|
||||
const char *prefix = o.profile_out ? o.profile_out : "build/profile";
|
||||
char folded[1024];
|
||||
|
||||
+40
-34
@@ -127,44 +127,20 @@ static inline void wr64(unsigned char **p, spl_vm_val_t v) {
|
||||
/* Round n up to next multiple of 8 */
|
||||
#define ALIGN8(n) (((n) + 7) & ~7)
|
||||
|
||||
int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
|
||||
FILE *f;
|
||||
unsigned char *data;
|
||||
long len;
|
||||
static int spl_prog_parse(const unsigned char *data, usize len, spl_prog_t *prog) {
|
||||
const unsigned char *p;
|
||||
spl_vm_val_t nfuncs, ninsns, nnatives, nstrs, ndata;
|
||||
|
||||
if (!fname || !prog)
|
||||
if (!data || !prog)
|
||||
return -1;
|
||||
|
||||
f = fopen(fname, "rb");
|
||||
if (!f)
|
||||
if (len < 48)
|
||||
return -1;
|
||||
fseek(f, 0, SEEK_END);
|
||||
len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (len < 48) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
data = (unsigned char *)malloc((size_t)len);
|
||||
if (!data) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
if (fread(data, 1, (size_t)len, f) != (size_t)len) {
|
||||
free(data);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
p = data;
|
||||
|
||||
/* magic */
|
||||
if (p[0] != 'S' || p[1] != 'P' || p[2] != 'L' || p[3] != 'B' || p[4] != 'I' || p[5] != 'N' ||
|
||||
p[6] != '\0' || p[7] != '\0') {
|
||||
free(data);
|
||||
return -1;
|
||||
}
|
||||
p += 8;
|
||||
@@ -186,7 +162,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
|
||||
|
||||
func.name = (char *)malloc((usize)nlen);
|
||||
if (!func.name) {
|
||||
free(data);
|
||||
return -1;
|
||||
}
|
||||
memcpy(func.name, p, (usize)nlen);
|
||||
@@ -202,7 +177,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
|
||||
/* ---- instructions ---- */
|
||||
for (spl_vm_val_t i = 0; i < ninsns; i++) {
|
||||
if ((size_t)(p - data) + 12 > (size_t)len) {
|
||||
free(data);
|
||||
return -1;
|
||||
}
|
||||
spl_vm_ins_t ins;
|
||||
@@ -221,7 +195,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
|
||||
|
||||
nat.name = (char *)malloc((usize)nlen);
|
||||
if (!nat.name) {
|
||||
free(data);
|
||||
return -1;
|
||||
}
|
||||
memcpy(nat.name, p, (usize)nlen);
|
||||
@@ -238,7 +211,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
|
||||
usize pad = ALIGN8((usize)slen) - (usize)slen;
|
||||
char *s = (char *)malloc((usize)slen + 1);
|
||||
if (!s) {
|
||||
free(data);
|
||||
return -1;
|
||||
}
|
||||
memcpy(s, p, (usize)slen);
|
||||
@@ -254,7 +226,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
|
||||
usize pad = ALIGN8(entry.size) - entry.size;
|
||||
entry.data = (unsigned char *)malloc(entry.size);
|
||||
if (!entry.data) {
|
||||
free(data);
|
||||
return -1;
|
||||
}
|
||||
memcpy(entry.data, p, entry.size);
|
||||
@@ -279,11 +250,46 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
|
||||
}
|
||||
}
|
||||
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spl_prog_load_from_buffer(const char *buffer, usize len, spl_prog_t *prog) {}
|
||||
int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
|
||||
FILE *f;
|
||||
unsigned char *data;
|
||||
long len;
|
||||
int rc;
|
||||
|
||||
if (!fname || !prog)
|
||||
return -1;
|
||||
f = fopen(fname, "rb");
|
||||
if (!f)
|
||||
return -1;
|
||||
fseek(f, 0, SEEK_END);
|
||||
len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (len < 48) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
data = (unsigned char *)malloc((size_t)len);
|
||||
if (!data) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
if (fread(data, 1, (size_t)len, f) != (size_t)len) {
|
||||
free(data);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
fclose(f);
|
||||
rc = spl_prog_parse(data, (usize)len, prog);
|
||||
free(data);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int spl_prog_load_from_buffer(const char *buffer, usize len, spl_prog_t *prog) {
|
||||
return spl_prog_parse((const unsigned char *)buffer, len, prog);
|
||||
}
|
||||
|
||||
int spl_prog_store_to_file(const char *fname, spl_prog_t *prog) {
|
||||
unsigned char *buf, *p;
|
||||
|
||||
@@ -103,6 +103,7 @@ typedef enum {
|
||||
#define X(opcode, name, argc, pop, push, desc) opcode,
|
||||
SPL_OPCODES(X)
|
||||
#undef X
|
||||
SPL_OPCODE_COUNT,
|
||||
} spl_vm_opcode_t;
|
||||
|
||||
/*
|
||||
@@ -191,7 +192,10 @@ void spl_prog_init(spl_prog_t *prog);
|
||||
void spl_prog_drop(spl_prog_t *prog);
|
||||
void spl_prog_dump(spl_prog_t *prog);
|
||||
|
||||
|
||||
|
||||
int spl_prog_load_from_file(const char *fname, spl_prog_t *prog);
|
||||
int spl_prog_load_from_buffer(const char *buffer, usize len, spl_prog_t *prog);
|
||||
int spl_prog_store_to_file(const char *fname, spl_prog_t *prog);
|
||||
|
||||
int spl_prog_add_instr(spl_prog_t *prog, uint8_t opcode, uint8_t type, spl_vm_val_t imm);
|
||||
|
||||
+691
-17
@@ -348,6 +348,10 @@ static spl_vm_val_t vm_timestamp(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
if (!_vm)
|
||||
return 0;
|
||||
return (spl_vm_val_t)_vm->steps;
|
||||
case 5:
|
||||
if (!_vm)
|
||||
return 0;
|
||||
return (spl_vm_val_t)_vm->ncalls;
|
||||
case 4:
|
||||
return has_time ? (spl_vm_val_t)((unsigned long long)ts.tv_sec * 1000000000ull +
|
||||
(unsigned long long)ts.tv_nsec)
|
||||
@@ -419,6 +423,33 @@ static spl_vm_val_t vm_load(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
return (spl_vm_val_t)(uintptr_t)prog;
|
||||
}
|
||||
|
||||
/* vm_load_from_buffer - 从内存加载字节码(comptime 内嵌 VM 用)。
|
||||
* 参数 1 是 slice 结构 { ptr, len } 的地址(聚合参数按指针传递)。 */
|
||||
static spl_vm_val_t vm_load_from_buffer(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_load_from_buffer", 3);
|
||||
spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0];
|
||||
const char *buf = (const char *)(uintptr_t)args[1];
|
||||
usize len = (usize)args[2];
|
||||
spl_prog_t *prog;
|
||||
if (!vm || !buf)
|
||||
return -1;
|
||||
prog = (spl_prog_t *)malloc(sizeof(spl_prog_t));
|
||||
if (!prog)
|
||||
return -1;
|
||||
if (spl_prog_load_from_buffer(buf, len, prog) != 0) {
|
||||
free(prog);
|
||||
return -1;
|
||||
}
|
||||
spl_syscall_register(prog);
|
||||
if (spl_vm_load_prog(vm, prog) != 0) {
|
||||
spl_prog_drop(prog);
|
||||
free(prog);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* vm_push - push a value onto the sub-VM's stack (for passing arguments) */
|
||||
static spl_vm_val_t vm_push(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
@@ -460,31 +491,36 @@ static spl_vm_val_t vm_call(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
fprintf(stderr, "vm_call: function '%s' not found\n", name);
|
||||
return -1;
|
||||
}
|
||||
if (vm->cp >= vm->config.max_call_depth - 1) {
|
||||
if (vm->cp >= vm->config.max_call_depth) {
|
||||
fprintf(stderr, "vm_call: call stack overflow\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Sentinel frame so the entry function's RET halts cleanly */
|
||||
if (vm->cp == 0) {
|
||||
vm->frames.data[vm->cp].saved_fp = 0;
|
||||
vm->frames.data[vm->cp].saved_ip = (uintptr_t)-1;
|
||||
vm->frames.data[vm->cp].nargs = 0;
|
||||
vm->cp++;
|
||||
}
|
||||
|
||||
/* Actual call frame (same logic as the CALL instruction) */
|
||||
vm->frames.data[vm->cp].saved_fp = vm->fp;
|
||||
vm->frames.data[vm->cp].saved_ip = vm->ip;
|
||||
vm->frames.data[vm->cp].nargs = call_nargs;
|
||||
vm->cp++;
|
||||
vm->fp = vm->sp - call_nargs;
|
||||
/* 入口调用:ip 置 -1 作为 sentinel,RET 时 halt 并把返回值写入 exit_code。
|
||||
* 实参须已由 vm_push 压栈(sp = nargs)。 */
|
||||
vm->ip = (uintptr_t)-1;
|
||||
vm->cp = 0;
|
||||
vm->fp = 0;
|
||||
vm->frames.data[0].saved_sp = vm->sp - call_nargs;
|
||||
vm->frames.data[0].saved_fp = vm->fp;
|
||||
vm->frames.data[0].saved_ip = vm->ip;
|
||||
vm->frames.data[0].nargs = call_nargs;
|
||||
vm->cp = 1;
|
||||
vm->ip = (uintptr_t)func->address;
|
||||
vm->fp = vm->sp - call_nargs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* vm_run - run a sub-VM to completion, returns exit_code */
|
||||
SYSCALL_1(vm_run, spl_vm_t *, spl_vm_run_until(a1, 0))
|
||||
/* vm_run - run a sub-VM to completion, returns exit_code(入口 RET 的值) */
|
||||
static spl_vm_val_t vm_run(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_run", 1);
|
||||
spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0];
|
||||
if (!vm)
|
||||
return -1;
|
||||
spl_vm_run_until(vm, 0);
|
||||
return (spl_vm_val_t)vm->exit_code;
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* Registration
|
||||
@@ -493,6 +529,623 @@ SYSCALL_1(vm_run, spl_vm_t *, spl_vm_run_until(a1, 0))
|
||||
* prog->natives against known syscall names and hooks up impl_fn.
|
||||
* ================================================================= */
|
||||
|
||||
/* =================================================================
|
||||
* Native std containers (spike: Vec valid/push_raw/get)
|
||||
*
|
||||
* 布局镜像 stage2/utils/vec.spl 的 Vec:
|
||||
* raw, data, len, cap, elem_size, elem_align (均为机器字)
|
||||
* 结构体按值传递/复制由 SPL 负责,native 只按指针操作。
|
||||
* ================================================================= */
|
||||
|
||||
typedef struct {
|
||||
uintptr_t raw;
|
||||
uintptr_t data;
|
||||
uintptr_t len;
|
||||
uintptr_t cap;
|
||||
uintptr_t elem_size;
|
||||
uintptr_t elem_align;
|
||||
} spl_vec_t;
|
||||
|
||||
static int spl_vec_is_valid(const spl_vec_t *v) {
|
||||
if (v->elem_size == 0)
|
||||
return 0;
|
||||
uintptr_t a = v->elem_align;
|
||||
if (a == 0 || (a & (a - 1)) != 0)
|
||||
return 0;
|
||||
if (v->len > v->cap)
|
||||
return 0;
|
||||
if (v->cap == 0)
|
||||
return v->raw == 0 && v->data == 0 && v->len == 0;
|
||||
if (v->raw == 0 || v->data == 0)
|
||||
return 0;
|
||||
return (v->data & (a - 1)) == 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_vec_valid(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_valid", 1);
|
||||
const spl_vec_t *v = (const spl_vec_t *)(uintptr_t)args[0];
|
||||
return (spl_vm_val_t)(v ? spl_vec_is_valid(v) : 0);
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_vec_push_raw(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_push_raw", 2);
|
||||
spl_vec_t *v = (spl_vec_t *)(uintptr_t)args[0];
|
||||
const void *elem = (const void *)(uintptr_t)args[1];
|
||||
if (!v || !elem)
|
||||
return 0;
|
||||
if (v->len < v->cap) {
|
||||
memcpy((void *)(v->data + v->len * v->elem_size), elem, v->elem_size);
|
||||
v->len++;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* out 接收元素指针;返回 0 成功,1 无效参数,2 越界。 */
|
||||
static int spl_vec_reserve_raw(spl_vec_t *v, uintptr_t need);
|
||||
|
||||
static spl_vm_val_t vm_vec_push(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_push", 2);
|
||||
spl_vec_t *v = (spl_vec_t *)(uintptr_t)args[0];
|
||||
const void *elem = (const void *)(uintptr_t)args[1];
|
||||
if (!v || !elem || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (v->len < v->cap) {
|
||||
memcpy((void *)(v->data + v->len * v->elem_size), elem, v->elem_size);
|
||||
v->len++;
|
||||
return 0;
|
||||
}
|
||||
int rc = spl_vec_reserve_raw(v, v->len + 1);
|
||||
if (rc)
|
||||
return rc;
|
||||
memmove((void *)(v->data + v->len * v->elem_size), elem, v->elem_size);
|
||||
v->len++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_vec_get(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_get", 3);
|
||||
const spl_vec_t *v = (const spl_vec_t *)(uintptr_t)args[0];
|
||||
uintptr_t index = (uintptr_t)args[1];
|
||||
uintptr_t *out = (uintptr_t *)(uintptr_t)args[2];
|
||||
if (!v || !out)
|
||||
return 1;
|
||||
if (!spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (index >= v->len)
|
||||
return 2;
|
||||
*out = v->data + index * v->elem_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 错误码统一:0 ok / 1 InvalidArgument / 2 OutOfRange / 3 Overflow / 4 InvalidAlignment */
|
||||
|
||||
static int spl_align_ok(uintptr_t a) { return a != 0 && (a & (a - 1)) == 0; }
|
||||
|
||||
static int spl_grow_capacity(uintptr_t current, uintptr_t min_cap, uintptr_t need,
|
||||
uintptr_t *out) {
|
||||
uintptr_t cap = current < min_cap ? min_cap : current;
|
||||
uintptr_t limit = ~(uintptr_t)0;
|
||||
while (cap < need) {
|
||||
if (cap > limit / 2) {
|
||||
cap = need;
|
||||
break;
|
||||
}
|
||||
cap = cap * 2;
|
||||
}
|
||||
*out = cap;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spl_vec_allocate(spl_vec_t *out, uintptr_t capacity, uintptr_t elem_size,
|
||||
uintptr_t elem_align) {
|
||||
if (elem_size == 0)
|
||||
return 1;
|
||||
if (!spl_align_ok(elem_align))
|
||||
return 4;
|
||||
out->raw = 0;
|
||||
out->data = 0;
|
||||
out->len = 0;
|
||||
out->cap = 0;
|
||||
out->elem_size = elem_size;
|
||||
out->elem_align = elem_align;
|
||||
if (capacity == 0)
|
||||
return 0;
|
||||
if (capacity > ~(uintptr_t)0 / elem_size)
|
||||
return 3;
|
||||
uintptr_t bytes = capacity * elem_size;
|
||||
uintptr_t mask = elem_align - 1;
|
||||
if (bytes > ~(uintptr_t)0 - mask)
|
||||
return 3;
|
||||
uintptr_t total = bytes + mask;
|
||||
void *raw = malloc(total);
|
||||
if (!raw)
|
||||
return 3;
|
||||
uintptr_t addr = (uintptr_t)raw;
|
||||
if (addr > ~(uintptr_t)0 - mask) {
|
||||
free(raw);
|
||||
return 3;
|
||||
}
|
||||
out->raw = addr;
|
||||
out->data = (addr + mask) & ~mask;
|
||||
out->cap = capacity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_vec_allocate(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_allocate", 4);
|
||||
spl_vec_t *out = (spl_vec_t *)(uintptr_t)args[0];
|
||||
if (!out)
|
||||
return 1;
|
||||
return (spl_vm_val_t)spl_vec_allocate(out, (uintptr_t)args[1], (uintptr_t)args[2],
|
||||
(uintptr_t)args[3]);
|
||||
}
|
||||
|
||||
static int spl_vec_reserve_raw(spl_vec_t *v, uintptr_t need) {
|
||||
if (need <= v->cap)
|
||||
return 0;
|
||||
uintptr_t new_cap = 0;
|
||||
spl_grow_capacity(v->cap, 8, need, &new_cap);
|
||||
spl_vec_t fresh;
|
||||
int rc = spl_vec_allocate(&fresh, new_cap, v->elem_size, v->elem_align);
|
||||
if (rc)
|
||||
return rc;
|
||||
uintptr_t used = 0;
|
||||
if (v->len != 0) {
|
||||
if (v->len > ~(uintptr_t)0 / v->elem_size) {
|
||||
free((void *)fresh.raw);
|
||||
return 3;
|
||||
}
|
||||
used = v->len * v->elem_size;
|
||||
}
|
||||
if (used != 0)
|
||||
memcpy((void *)fresh.data, (void *)v->data, used);
|
||||
fresh.len = v->len;
|
||||
uintptr_t old = v->raw;
|
||||
*v = fresh;
|
||||
if (old)
|
||||
free((void *)old);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_vec_reserve_raw(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_reserve_raw", 2);
|
||||
spl_vec_t *v = (spl_vec_t *)(uintptr_t)args[0];
|
||||
if (!v)
|
||||
return 1;
|
||||
return (spl_vm_val_t)spl_vec_reserve_raw(v, (uintptr_t)args[1]);
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_vec_append(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_append", 3);
|
||||
spl_vec_t *v = (spl_vec_t *)(uintptr_t)args[0];
|
||||
const void *elems = (const void *)(uintptr_t)args[1];
|
||||
uintptr_t count = (uintptr_t)args[2];
|
||||
if (!v || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (count == 0)
|
||||
return 0;
|
||||
if (!elems)
|
||||
return 1;
|
||||
if (count > ~(uintptr_t)0 / v->elem_size)
|
||||
return 3;
|
||||
uintptr_t copy_bytes = count * v->elem_size;
|
||||
if (v->len > ~(uintptr_t)0 - count)
|
||||
return 3;
|
||||
uintptr_t new_len = v->len + count;
|
||||
uintptr_t used = v->len * v->elem_size;
|
||||
int source_inside = 0;
|
||||
uintptr_t source_offset = 0;
|
||||
if (used != 0) {
|
||||
uintptr_t base = v->data;
|
||||
uintptr_t source = (uintptr_t)elems;
|
||||
if (source >= base && source - base < used) {
|
||||
source_offset = source - base;
|
||||
if (copy_bytes > used - source_offset)
|
||||
return 2;
|
||||
source_inside = 1;
|
||||
}
|
||||
}
|
||||
int rc = spl_vec_reserve_raw(v, new_len);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (source_inside)
|
||||
elems = (const void *)(v->data + source_offset);
|
||||
memmove((void *)(v->data + used), elems, copy_bytes);
|
||||
v->len = new_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_vec_set(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_set", 3);
|
||||
spl_vec_t *v = (spl_vec_t *)(uintptr_t)args[0];
|
||||
uintptr_t index = (uintptr_t)args[1];
|
||||
const void *elem = (const void *)(uintptr_t)args[2];
|
||||
if (!v || !elem || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (index >= v->len)
|
||||
return 2;
|
||||
memmove((void *)(v->data + index * v->elem_size), elem, v->elem_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_vec_pop(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_pop", 2);
|
||||
spl_vec_t *v = (spl_vec_t *)(uintptr_t)args[0];
|
||||
void *out = (void *)(uintptr_t)args[1];
|
||||
if (!v || !out || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (v->len == 0)
|
||||
return 2;
|
||||
memmove(out, (void *)(v->data + (v->len - 1) * v->elem_size), v->elem_size);
|
||||
v->len--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_vec_clone(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_clone", 2);
|
||||
spl_vec_t *out = (spl_vec_t *)(uintptr_t)args[0];
|
||||
const spl_vec_t *src = (const spl_vec_t *)(uintptr_t)args[1];
|
||||
if (!out || !src || !spl_vec_is_valid(out) || !spl_vec_is_valid(src))
|
||||
return 1;
|
||||
if (out->elem_size != src->elem_size || out->elem_align != src->elem_align)
|
||||
return 1;
|
||||
spl_vec_t fresh;
|
||||
fresh.raw = 0;
|
||||
fresh.data = 0;
|
||||
fresh.len = 0;
|
||||
fresh.cap = 0;
|
||||
fresh.elem_size = src->elem_size;
|
||||
fresh.elem_align = src->elem_align;
|
||||
int rc = spl_vec_reserve_raw(&fresh, src->len);
|
||||
if (rc)
|
||||
return rc;
|
||||
uintptr_t bytes = src->len * src->elem_size;
|
||||
if (bytes != 0)
|
||||
memcpy((void *)fresh.data, (void *)src->data, bytes);
|
||||
fresh.len = src->len;
|
||||
if (out->raw)
|
||||
free((void *)out->raw);
|
||||
*out = fresh;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_vec_drop(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_vec_drop", 1);
|
||||
spl_vec_t *v = (spl_vec_t *)(uintptr_t)args[0];
|
||||
if (!v)
|
||||
return 0;
|
||||
uintptr_t es = v->elem_size;
|
||||
uintptr_t ea = v->elem_align;
|
||||
if (v->raw)
|
||||
free((void *)v->raw);
|
||||
v->raw = 0;
|
||||
v->data = 0;
|
||||
v->len = 0;
|
||||
v->cap = 0;
|
||||
v->elem_size = es;
|
||||
v->elem_align = ea;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_memmove(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
CHECK_NARGS("vm_memmove", 3);
|
||||
void *dst = (void *)(uintptr_t)args[0];
|
||||
const void *src = (const void *)(uintptr_t)args[1];
|
||||
size_t size = (size_t)args[2];
|
||||
if (size == 0)
|
||||
return (spl_vm_val_t)(uintptr_t)dst;
|
||||
ADDRCHECK(_vm, dst, size, "vm_memmove(dst)");
|
||||
ADDRCHECK(_vm, src, size, "vm_memmove(src)");
|
||||
memmove(dst, src, size);
|
||||
return (spl_vm_val_t)(uintptr_t)dst;
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* Native Map(开放寻址 + 墓碑),布局镜像 stage2/utils/map.spl 的 Map。
|
||||
* hash_kind: 0 = usize;1 = ByteView(按内容);2 = 常量(仅测试用)。
|
||||
* ================================================================= */
|
||||
|
||||
typedef struct {
|
||||
spl_vec_t keys;
|
||||
spl_vec_t vals;
|
||||
spl_vec_t states;
|
||||
uintptr_t size;
|
||||
uintptr_t key_size;
|
||||
uintptr_t val_size;
|
||||
uintptr_t hash_kind;
|
||||
} spl_map_t;
|
||||
|
||||
static void spl_vec_release(spl_vec_t *v) {
|
||||
uintptr_t es = v->elem_size;
|
||||
uintptr_t ea = v->elem_align;
|
||||
if (v->raw)
|
||||
free((void *)v->raw);
|
||||
v->raw = 0;
|
||||
v->data = 0;
|
||||
v->len = 0;
|
||||
v->cap = 0;
|
||||
v->elem_size = es;
|
||||
v->elem_align = ea;
|
||||
}
|
||||
|
||||
static uintptr_t spl_hash_usize_value(uintptr_t value) {
|
||||
uintptr_t prime = 1099511628211ull;
|
||||
uintptr_t h = value;
|
||||
h = (h ^ (h >> 30)) * prime;
|
||||
h = h ^ (h >> 27);
|
||||
h = h * prime;
|
||||
h = h ^ (h >> 31);
|
||||
if (h == 0)
|
||||
return prime;
|
||||
return h;
|
||||
}
|
||||
|
||||
static uintptr_t spl_hash_bytes(const void *data, uintptr_t len) {
|
||||
const unsigned char *p = (const unsigned char *)data;
|
||||
uintptr_t h = 14695981039346656037ull;
|
||||
uintptr_t prime = 1099511628211ull;
|
||||
for (uintptr_t i = 0; i < len; i++)
|
||||
h = (h ^ (uintptr_t)p[i]) * prime;
|
||||
return h;
|
||||
}
|
||||
|
||||
static uintptr_t spl_map_hash(const spl_map_t *m, const void *key) {
|
||||
if (m->hash_kind == 0)
|
||||
return spl_hash_usize_value(*(const uintptr_t *)key);
|
||||
if (m->hash_kind == 2)
|
||||
return 3;
|
||||
const uintptr_t *v = (const uintptr_t *)key; /* ByteView { ptr, len } */
|
||||
return spl_hash_bytes((const void *)v[0], v[1]);
|
||||
}
|
||||
|
||||
static int spl_map_eq(const spl_map_t *m, const void *a, const void *b) {
|
||||
if (m->hash_kind == 0 || m->hash_kind == 2)
|
||||
return *(const uintptr_t *)a == *(const uintptr_t *)b;
|
||||
const uintptr_t *x = (const uintptr_t *)a;
|
||||
const uintptr_t *y = (const uintptr_t *)b;
|
||||
if (x[1] != y[1])
|
||||
return 0;
|
||||
if (x[1] == 0)
|
||||
return 1;
|
||||
return memcmp((const void *)x[0], (const void *)y[0], x[1]) == 0;
|
||||
}
|
||||
|
||||
static int spl_map_valid(const spl_map_t *m) {
|
||||
if (m->key_size == 0 || m->val_size == 0)
|
||||
return 0;
|
||||
if (m->keys.len != m->states.len || m->vals.len != m->states.len)
|
||||
return 0;
|
||||
if (m->size > m->states.len)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uintptr_t spl_map_find_slot(const spl_map_t *m, const void *key) {
|
||||
if (m->states.cap == 0)
|
||||
return ~(uintptr_t)0;
|
||||
uintptr_t mask = m->states.cap - 1;
|
||||
uintptr_t i = spl_map_hash(m, key) & mask;
|
||||
unsigned char *st = (unsigned char *)m->states.data;
|
||||
uintptr_t probes = 0;
|
||||
while (probes < m->states.cap && st[i] != 0) {
|
||||
if (st[i] == 1 && spl_map_eq(m, (const void *)(m->keys.data + i * m->key_size), key))
|
||||
return i;
|
||||
i = (i + 1) & mask;
|
||||
probes++;
|
||||
}
|
||||
return ~(uintptr_t)0;
|
||||
}
|
||||
|
||||
static void spl_map_place(spl_map_t *m, const void *key, const void *val) {
|
||||
uintptr_t mask = m->states.cap - 1;
|
||||
uintptr_t i = spl_map_hash(m, key) & mask;
|
||||
unsigned char *st = (unsigned char *)m->states.data;
|
||||
uintptr_t first_deleted = ~(uintptr_t)0;
|
||||
uintptr_t probes = 0;
|
||||
while (probes < m->states.cap && st[i] != 0) {
|
||||
if (st[i] == 2 && first_deleted == ~(uintptr_t)0)
|
||||
first_deleted = i;
|
||||
i = (i + 1) & mask;
|
||||
probes++;
|
||||
}
|
||||
if (first_deleted != ~(uintptr_t)0)
|
||||
i = first_deleted;
|
||||
memcpy((void *)(m->keys.data + i * m->key_size), key, m->key_size);
|
||||
memcpy((void *)(m->vals.data + i * m->val_size), val, m->val_size);
|
||||
st[i] = 1;
|
||||
m->size++;
|
||||
}
|
||||
|
||||
static int spl_map_reserve(spl_map_t *m, uintptr_t need) {
|
||||
if (!spl_map_valid(m))
|
||||
return 1;
|
||||
if (need <= m->states.cap)
|
||||
return 0;
|
||||
uintptr_t new_cap = 0;
|
||||
spl_grow_capacity(m->states.cap, 8, need, &new_cap);
|
||||
|
||||
spl_map_t fresh = *m;
|
||||
fresh.keys.raw = 0;
|
||||
fresh.keys.data = 0;
|
||||
fresh.keys.len = 0;
|
||||
fresh.keys.cap = 0;
|
||||
fresh.vals.raw = 0;
|
||||
fresh.vals.data = 0;
|
||||
fresh.vals.len = 0;
|
||||
fresh.vals.cap = 0;
|
||||
fresh.states.raw = 0;
|
||||
fresh.states.data = 0;
|
||||
fresh.states.len = 0;
|
||||
fresh.states.cap = 0;
|
||||
fresh.size = 0;
|
||||
|
||||
int rc = spl_vec_reserve_raw(&fresh.keys, new_cap);
|
||||
if (rc)
|
||||
goto fail;
|
||||
rc = spl_vec_reserve_raw(&fresh.vals, new_cap);
|
||||
if (rc)
|
||||
goto fail;
|
||||
rc = spl_vec_reserve_raw(&fresh.states, new_cap);
|
||||
if (rc)
|
||||
goto fail;
|
||||
fresh.keys.len = new_cap;
|
||||
fresh.vals.len = new_cap;
|
||||
fresh.states.len = new_cap;
|
||||
memset((void *)fresh.states.data, 0, new_cap);
|
||||
|
||||
for (uintptr_t i = 0; i < m->states.len; i++) {
|
||||
if (((unsigned char *)m->states.data)[i] == 1)
|
||||
spl_map_place(&fresh, (const void *)(m->keys.data + i * m->key_size),
|
||||
(const void *)(m->vals.data + i * m->val_size));
|
||||
}
|
||||
|
||||
spl_vec_release(&m->keys);
|
||||
spl_vec_release(&m->vals);
|
||||
spl_vec_release(&m->states);
|
||||
*m = fresh;
|
||||
return 0;
|
||||
fail:
|
||||
spl_vec_release(&fresh.keys);
|
||||
spl_vec_release(&fresh.vals);
|
||||
spl_vec_release(&fresh.states);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_bytes_eq(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_bytes_eq", 4);
|
||||
const void *a = (const void *)(uintptr_t)args[0];
|
||||
uintptr_t alen = (uintptr_t)args[1];
|
||||
const void *b = (const void *)(uintptr_t)args[2];
|
||||
uintptr_t blen = (uintptr_t)args[3];
|
||||
if (alen != blen)
|
||||
return 0;
|
||||
if (alen == 0)
|
||||
return 1;
|
||||
if (!a || !b)
|
||||
return 0;
|
||||
return memcmp(a, b, alen) == 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_map_valid(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_map_valid", 1);
|
||||
const spl_map_t *m = (const spl_map_t *)(uintptr_t)args[0];
|
||||
return (spl_vm_val_t)(m ? spl_map_valid(m) : 0);
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_map_reserve(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_map_reserve", 2);
|
||||
spl_map_t *m = (spl_map_t *)(uintptr_t)args[0];
|
||||
if (!m)
|
||||
return 1;
|
||||
return (spl_vm_val_t)spl_map_reserve(m, (uintptr_t)args[1]);
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_map_put(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_map_put", 3);
|
||||
spl_map_t *m = (spl_map_t *)(uintptr_t)args[0];
|
||||
const void *key = (const void *)(uintptr_t)args[1];
|
||||
const void *val = (const void *)(uintptr_t)args[2];
|
||||
if (!m || !key || !val || !spl_map_valid(m))
|
||||
return 1;
|
||||
uintptr_t existing = spl_map_find_slot(m, key);
|
||||
if (existing != ~(uintptr_t)0) {
|
||||
memcpy((void *)(m->vals.data + existing * m->val_size), val, m->val_size);
|
||||
return 0;
|
||||
}
|
||||
if (m->states.cap == 0) {
|
||||
int rc = spl_map_reserve(m, 8);
|
||||
if (rc)
|
||||
return rc;
|
||||
} else {
|
||||
if (m->size > ~(uintptr_t)0 - 1)
|
||||
return 3;
|
||||
uintptr_t next_size = m->size + 1;
|
||||
if (next_size > ~(uintptr_t)0 / 128)
|
||||
return 3;
|
||||
uintptr_t scaled = next_size * 128;
|
||||
if (scaled / m->states.cap >= 70) {
|
||||
if (m->states.cap > ~(uintptr_t)0 / 2)
|
||||
return 3;
|
||||
int rc = spl_map_reserve(m, m->states.cap * 2);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
spl_map_place(m, key, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_map_get(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_map_get", 3);
|
||||
spl_map_t *m = (spl_map_t *)(uintptr_t)args[0];
|
||||
const void *key = (const void *)(uintptr_t)args[1];
|
||||
void *out = (void *)(uintptr_t)args[2];
|
||||
if (!m || !key || !out || !spl_map_valid(m))
|
||||
return 1;
|
||||
uintptr_t i = spl_map_find_slot(m, key);
|
||||
if (i == ~(uintptr_t)0)
|
||||
return 1;
|
||||
memcpy(out, (void *)(m->vals.data + i * m->val_size), m->val_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_map_del(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_map_del", 2);
|
||||
spl_map_t *m = (spl_map_t *)(uintptr_t)args[0];
|
||||
const void *key = (const void *)(uintptr_t)args[1];
|
||||
if (!m || !key || !spl_map_valid(m))
|
||||
return 1;
|
||||
uintptr_t i = spl_map_find_slot(m, key);
|
||||
if (i == ~(uintptr_t)0)
|
||||
return 1;
|
||||
((unsigned char *)m->states.data)[i] = 2;
|
||||
m->size--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_map_clear(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_map_clear", 1);
|
||||
spl_map_t *m = (spl_map_t *)(uintptr_t)args[0];
|
||||
if (!m)
|
||||
return 0;
|
||||
if (m->states.data && m->states.len)
|
||||
memset((void *)m->states.data, 0, m->states.len);
|
||||
m->size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_vm_val_t vm_map_drop(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
|
||||
(void)_vm;
|
||||
CHECK_NARGS("vm_map_drop", 1);
|
||||
spl_map_t *m = (spl_map_t *)(uintptr_t)args[0];
|
||||
if (!m)
|
||||
return 0;
|
||||
spl_vec_release(&m->keys);
|
||||
spl_vec_release(&m->vals);
|
||||
spl_vec_release(&m->states);
|
||||
m->size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spl_syscall_register(spl_prog_t *prog) {
|
||||
static spl_vm_native_t table[] = {
|
||||
/* OS operations */
|
||||
@@ -516,12 +1169,33 @@ void spl_syscall_register(spl_prog_t *prog) {
|
||||
{"vm_memcpy", 0, vm_memcpy},
|
||||
{"vm_memcmp", 0, vm_memcmp},
|
||||
{"vm_printf", 0, vm_printf},
|
||||
{"vm_vec_valid", 0, vm_vec_valid},
|
||||
{"vm_vec_push_raw", 0, vm_vec_push_raw},
|
||||
{"vm_vec_push", 0, vm_vec_push},
|
||||
{"vm_vec_get", 0, vm_vec_get},
|
||||
{"vm_vec_allocate", 0, vm_vec_allocate},
|
||||
{"vm_vec_reserve_raw", 0, vm_vec_reserve_raw},
|
||||
{"vm_vec_append", 0, vm_vec_append},
|
||||
{"vm_vec_set", 0, vm_vec_set},
|
||||
{"vm_vec_pop", 0, vm_vec_pop},
|
||||
{"vm_vec_clone", 0, vm_vec_clone},
|
||||
{"vm_vec_drop", 0, vm_vec_drop},
|
||||
{"vm_memmove", 0, vm_memmove},
|
||||
{"vm_bytes_eq", 0, vm_bytes_eq},
|
||||
{"vm_map_valid", 0, vm_map_valid},
|
||||
{"vm_map_reserve", 0, vm_map_reserve},
|
||||
{"vm_map_put", 0, vm_map_put},
|
||||
{"vm_map_get", 0, vm_map_get},
|
||||
{"vm_map_del", 0, vm_map_del},
|
||||
{"vm_map_clear", 0, vm_map_clear},
|
||||
{"vm_map_drop", 0, vm_map_drop},
|
||||
|
||||
{"vm_timestamp", 0, vm_timestamp},
|
||||
/* VM operations (comptime) */
|
||||
{"vm_new", 0, vm_new},
|
||||
{"vm_drop", 0, vm_drop},
|
||||
{"vm_load", 0, vm_load},
|
||||
{"vm_load_from_buffer", 0, vm_load_from_buffer},
|
||||
{"vm_push", 0, vm_push},
|
||||
{"vm_call", 0, vm_call},
|
||||
{"vm_run", 0, vm_run},
|
||||
|
||||
+262
-172
@@ -11,6 +11,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
static void spl_vm_profile_free(spl_vm_t *vm);
|
||||
|
||||
@@ -67,7 +68,7 @@ static int spl_type_size(spl_vm_kind_t t) {
|
||||
case SPL_USIZE:
|
||||
case SPL_PTR:
|
||||
return sizeof(void *);
|
||||
case SPL_TYPE_COUNT:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
@@ -570,6 +571,7 @@ void spl_vm_init_ex(spl_vm_t *vm, int stack_size, int call_depth) {
|
||||
|
||||
vm->sp = vm->fp = vm->ip = vm->cp = vm->gp = 0;
|
||||
vm->steps = 0;
|
||||
vm->ncalls = 0;
|
||||
vm->profile = NULL;
|
||||
vm->profile_interval = 0;
|
||||
vm->prog = NULL;
|
||||
@@ -708,22 +710,29 @@ void spl_vm_set_debug(spl_vm_t *vm, int enabled) {
|
||||
static inline int spl_vm_call(spl_vm_t *vm, spl_vm_val_t addr, spl_vm_val_t nargs) {
|
||||
if (vm->cp >= vm->config.max_call_depth)
|
||||
VM_ERROR("call depth exceeded");
|
||||
// spl_vm_stackdump(vm, vm->sp);
|
||||
|
||||
uintptr_t saved_sp;
|
||||
if (vm->debug) {
|
||||
/* debug:在 fp-1 留金丝雀位,需要把实参右移一格。 */
|
||||
PUSH(SPL_STACK_CANARY);
|
||||
/* Shift args right by 1 to create gap for canary at fp-1 */
|
||||
for (usize i = vm->sp; i >= vm->sp - nargs; --i) {
|
||||
for (usize i = vm->sp; i >= vm->sp - nargs; --i)
|
||||
vm->stacks.data[i] = vm->stacks.data[i - 1];
|
||||
saved_sp = vm->sp - nargs - 1;
|
||||
} else {
|
||||
/* 非 debug:不做金丝雀搬移;实参在 sp-nargs..sp-1,fp 指向 arg0。 */
|
||||
saved_sp = vm->sp - nargs;
|
||||
}
|
||||
vm->frames.data[vm->cp].saved_sp = vm->sp - nargs - 1;
|
||||
vm->frames.data[vm->cp].saved_fp = vm->fp;
|
||||
vm->frames.data[vm->cp].saved_ip = vm->ip;
|
||||
vm->frames.data[vm->cp].nargs = nargs;
|
||||
|
||||
spl_callframe_t *fr = &vm->frames.data[vm->cp];
|
||||
fr->saved_sp = saved_sp;
|
||||
fr->saved_fp = vm->fp;
|
||||
fr->saved_ip = vm->ip;
|
||||
fr->nargs = nargs;
|
||||
vm->cp++;
|
||||
vm->ip = addr;
|
||||
vm->fp = vm->sp - nargs;
|
||||
if (vm->fp > 0)
|
||||
if (vm->debug && vm->fp > 0)
|
||||
STACK_CANARY(vm) = SPL_STACK_CANARY;
|
||||
// spl_vm_stackdump(vm, vm->sp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -800,6 +809,13 @@ static int func_index_by_ip(spl_prog_t *prog, spl_vm_val_t ip) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static uint64_t prof_now_ns(void) {
|
||||
struct timespec ts;
|
||||
if (timespec_get(&ts, TIME_UTC) != TIME_UTC)
|
||||
return 0;
|
||||
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
|
||||
}
|
||||
|
||||
static void spl_vm_sample(spl_vm_t *vm) {
|
||||
spl_prof_t *p = (spl_prof_t *)vm->profile;
|
||||
spl_prog_t *prog = vm->prog;
|
||||
@@ -808,7 +824,7 @@ static void spl_vm_sample(spl_vm_t *vm) {
|
||||
|
||||
char buf[8192];
|
||||
usize len = 0;
|
||||
for (isize i = 0; i < vm->cp; i++) {
|
||||
for (usize i = 0; i < vm->cp; i++) {
|
||||
intptr_t s = (intptr_t)vm->frames.data[i].saved_ip;
|
||||
if (s <= 0)
|
||||
continue;
|
||||
@@ -823,6 +839,17 @@ static void spl_vm_sample(spl_vm_t *vm) {
|
||||
}
|
||||
}
|
||||
int leaf = func_index_by_ip(prog, vm->ip > 0 ? vm->ip - 1 : 0);
|
||||
|
||||
/* time-slice:把两次采样之间的时间记到当前活跃函数(leaf)。 */
|
||||
uint64_t now = prof_now_ns();
|
||||
if (p->last_ns != 0 && now > p->last_ns) {
|
||||
uint64_t d = now - p->last_ns;
|
||||
p->total_ns += d;
|
||||
if (leaf >= 0 && (usize)leaf < vec_size(p->self_ns))
|
||||
p->self_ns.data[leaf] += d;
|
||||
}
|
||||
p->last_ns = now;
|
||||
|
||||
const char *ln = leaf >= 0 ? vec_at(prog->funcs, (usize)leaf).name : "?";
|
||||
size_t ll = strlen(ln);
|
||||
if (len + ll + 1 < sizeof(buf)) {
|
||||
@@ -853,10 +880,13 @@ void spl_vm_profile_enable(spl_vm_t *vm, usize interval) {
|
||||
memset(p, 0, sizeof(*p));
|
||||
map_init(p->stacks, MAP_HASH_STR, MAP_CMP_STR);
|
||||
vec_init(p->self_samples);
|
||||
vec_init(p->self_ns);
|
||||
if (vm->prog) {
|
||||
usize n = vec_size(vm->prog->funcs);
|
||||
for (usize i = 0; i < n; i++)
|
||||
for (usize i = 0; i < n; i++) {
|
||||
vec_push(p->self_samples, (usize)0);
|
||||
vec_push(p->self_ns, (uint64_t)0);
|
||||
}
|
||||
}
|
||||
vm->profile = p;
|
||||
vm->profile_interval = interval ? interval : 100000;
|
||||
@@ -869,6 +899,7 @@ static void spl_vm_profile_free(spl_vm_t *vm) {
|
||||
map_for(p->stacks, i) { free((void *)p->stacks.data[i].key); }
|
||||
map_free(p->stacks);
|
||||
vec_free(p->self_samples);
|
||||
vec_free(p->self_ns);
|
||||
free(p);
|
||||
vm->profile = NULL;
|
||||
}
|
||||
@@ -891,20 +922,67 @@ void spl_vm_profile_dump_table(spl_vm_t *vm, const char *path) {
|
||||
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");
|
||||
fprintf(f, "samples=%zu interval=%zu total_ns=%llu\n", p->samples, vm->profile_interval,
|
||||
(unsigned long long)p->total_ns);
|
||||
fprintf(f, "func\tself_samples\tpct_samples\tself_ns\tpct_ns\tns_per_step\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;
|
||||
uint64_t t = i < vec_size(p->self_ns) ? p->self_ns.data[i] : 0;
|
||||
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);
|
||||
double ps = p->samples ? (100.0 * (double)c / (double)p->samples) : 0.0;
|
||||
double pt = p->total_ns ? (100.0 * (double)t / (double)p->total_ns) : 0.0;
|
||||
double nps = c ? ((double)t / (double)c) : 0.0;
|
||||
fprintf(f, "%s\t%zu\t%.2f\t%llu\t%.2f\t%.1f\n", fn->name, c, ps, (unsigned long long)t, pt,
|
||||
nps);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
int spl_vm_run_once(spl_vm_t *vm) {
|
||||
/* 单步执行核心(static,便于内联进热循环)。 */
|
||||
/* computed-goto 调度:每个 opcode 直接跳到对应标签;limit=SIZE_MAX 表示不限步。 */
|
||||
#define DT(op) [op] = &&L_##op
|
||||
|
||||
#define FETCH() \
|
||||
do { \
|
||||
if (vm->ip >= (spl_vm_val_t)vec_size(prog->insns)) { \
|
||||
fprintf(stderr, "vm: ip=%zd out of bounds\n", vm->ip); \
|
||||
vm->exit_code = 1; \
|
||||
return -1; \
|
||||
} \
|
||||
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, \
|
||||
spl_vm_opcode_name(ins->opcode), spl_vm_type_kind_name(ins->type), ins->imm, \
|
||||
vm->sp, vm->fp); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define DISPATCH() \
|
||||
do { \
|
||||
if (vm->debug && vm->fp > 0 && STACK_CANARY(vm) != SPL_STACK_CANARY) { \
|
||||
snprintf(vm->error_msg, sizeof(vm->error_msg), \
|
||||
"STACK CANARY CORRUPTED at ip=%zd, fp=%zd\n", vm->ip - 1, vm->fp); \
|
||||
VM_ERROR(vm->error_msg); \
|
||||
} \
|
||||
if (limit && --limit == 0) \
|
||||
return 0; \
|
||||
if (ip_breakpoint_hit(vm)) { \
|
||||
if (vm->skip_bp) \
|
||||
vm->skip_bp = 0; \
|
||||
else \
|
||||
return 2; \
|
||||
} \
|
||||
FETCH(); \
|
||||
goto *vm_dispatch[ins->opcode]; \
|
||||
} while (0)
|
||||
|
||||
static int vm_loop(spl_vm_t *vm, size_t limit) {
|
||||
const spl_vm_ins_t *ins;
|
||||
spl_prog_t *prog;
|
||||
|
||||
@@ -912,73 +990,78 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
return -1;
|
||||
prog = vm->prog;
|
||||
|
||||
if (vm->ip >= vec_size(prog->insns)) {
|
||||
fprintf(stderr, "vm: ip=%zd out of bounds\n", vm->ip);
|
||||
vm->exit_code = 1;
|
||||
return -1;
|
||||
}
|
||||
static void *vm_dispatch[SPL_OPCODE_COUNT] = {
|
||||
DT(SPL_PUSH), DT(SPL_DUP), DT(SPL_DROP),
|
||||
DT(SPL_SWAP), DT(SPL_PICK), DT(SPL_ROT),
|
||||
DT(SPL_ADD), DT(SPL_SUB), DT(SPL_MUL),
|
||||
DT(SPL_DIV_S), DT(SPL_DIV_U), DT(SPL_REM_S),
|
||||
DT(SPL_REM_U), DT(SPL_NEG), DT(SPL_AND),
|
||||
DT(SPL_OR), DT(SPL_XOR), DT(SPL_NOT),
|
||||
DT(SPL_SHL), DT(SPL_SHR_U), DT(SPL_SHR_S),
|
||||
DT(SPL_EQ), DT(SPL_NE), DT(SPL_SLT),
|
||||
DT(SPL_SLE), DT(SPL_ULT), DT(SPL_ULE),
|
||||
DT(SPL_SGT), DT(SPL_SGE), DT(SPL_UGT),
|
||||
DT(SPL_UGE), DT(SPL_TRUNC), DT(SPL_SEXT),
|
||||
DT(SPL_ZEXT), DT(SPL_JMP), DT(SPL_BZ),
|
||||
DT(SPL_BNZ), DT(SPL_HALT), DT(SPL_CALL),
|
||||
DT(SPL_CALLI), DT(SPL_RET), DT(SPL_ALLOC),
|
||||
DT(SPL_LADDR), DT(SPL_GADDR), DT(SPL_LOAD),
|
||||
DT(SPL_STORE), DT(SPL_NCALL), DT(SPL_NLIB),
|
||||
DT(SPL_BK), DT(SPL_DBG), [SPL_ERROR] = &&L_default,
|
||||
};
|
||||
|
||||
/* 断点:即将执行的指令命中 ip 断点 -> 暂停(返回 2)。
|
||||
* skip_bp 置位时执行当前指令(continue 越过当前断点)。 */
|
||||
if (limit == 0)
|
||||
return 0;
|
||||
if (ip_breakpoint_hit(vm)) {
|
||||
if (vm->skip_bp) {
|
||||
if (vm->skip_bp)
|
||||
vm->skip_bp = 0;
|
||||
} else {
|
||||
else
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
FETCH();
|
||||
goto *vm_dispatch[ins->opcode];
|
||||
|
||||
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,
|
||||
spl_vm_opcode_name(ins->opcode), spl_vm_type_kind_name(ins->type), ins->imm, vm->sp,
|
||||
vm->fp);
|
||||
}
|
||||
|
||||
switch (ins->opcode) {
|
||||
|
||||
/* ========== Stack ========== */
|
||||
case SPL_PUSH:
|
||||
/* ========== Stack ========== */
|
||||
L_SPL_PUSH:
|
||||
PUSH(ins->imm);
|
||||
break;
|
||||
DISPATCH();
|
||||
|
||||
case SPL_DUP: {
|
||||
L_SPL_DUP:
|
||||
{
|
||||
if (vm->sp < 1)
|
||||
VM_ERROR("DUP: stack underflow");
|
||||
spl_vm_val_t _v = vm->stacks.data[vm->sp - 1];
|
||||
PUSH(_v);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_DROP:
|
||||
L_SPL_DROP:
|
||||
if (vm->sp < 1)
|
||||
VM_ERROR("DROP: stack underflow");
|
||||
vm->sp--;
|
||||
break;
|
||||
DISPATCH();
|
||||
|
||||
case SPL_SWAP: {
|
||||
L_SPL_SWAP:
|
||||
{
|
||||
if (vm->sp < 2)
|
||||
VM_ERROR("SWAP: stack underflow");
|
||||
spl_vm_val_t _t = vm->stacks.data[vm->sp - 1];
|
||||
vm->stacks.data[vm->sp - 1] = vm->stacks.data[vm->sp - 2];
|
||||
vm->stacks.data[vm->sp - 2] = _t;
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_PICK: {
|
||||
L_SPL_PICK:
|
||||
{
|
||||
isize _idx = ins->imm;
|
||||
if ((usize)_idx >= vm->sp)
|
||||
VM_ERROR("PICK: index out of range");
|
||||
PUSH(vm->stacks.data[vm->sp - 1 - _idx]);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_ROT: {
|
||||
L_SPL_ROT:
|
||||
{
|
||||
if (vm->sp < 3)
|
||||
VM_ERROR("ROT: stack underflow");
|
||||
spl_vm_val_t _a = vm->stacks.data[vm->sp - 3];
|
||||
@@ -987,34 +1070,35 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
vm->stacks.data[vm->sp - 3] = _b;
|
||||
vm->stacks.data[vm->sp - 2] = _c;
|
||||
vm->stacks.data[vm->sp - 1] = _a;
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
/* ========== Arithmetic ========== */
|
||||
case SPL_ADD:
|
||||
/* ========== Arithmetic ========== */
|
||||
L_SPL_ADD:
|
||||
ARITH_BINOP(+);
|
||||
break;
|
||||
case SPL_SUB:
|
||||
DISPATCH();
|
||||
L_SPL_SUB:
|
||||
ARITH_BINOP(-);
|
||||
break;
|
||||
case SPL_MUL:
|
||||
DISPATCH();
|
||||
L_SPL_MUL:
|
||||
ARITH_BINOP(*);
|
||||
break;
|
||||
DISPATCH();
|
||||
|
||||
case SPL_DIV_S:
|
||||
L_SPL_DIV_S:
|
||||
DIV_REM_S(/);
|
||||
break;
|
||||
case SPL_DIV_U:
|
||||
DISPATCH();
|
||||
L_SPL_DIV_U:
|
||||
DIV_REM_U(/);
|
||||
break;
|
||||
case SPL_REM_S:
|
||||
DISPATCH();
|
||||
L_SPL_REM_S:
|
||||
DIV_REM_S(%);
|
||||
break;
|
||||
case SPL_REM_U:
|
||||
DISPATCH();
|
||||
L_SPL_REM_U:
|
||||
DIV_REM_U(%);
|
||||
break;
|
||||
DISPATCH();
|
||||
|
||||
case SPL_NEG: {
|
||||
L_SPL_NEG:
|
||||
{
|
||||
spl_vm_val_t _a = POP();
|
||||
if (spl_is_float((spl_vm_kind_t)ins->type)) {
|
||||
double _d;
|
||||
@@ -1032,115 +1116,129 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
} else {
|
||||
PUSH(-(int64_t)_a);
|
||||
}
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
/* ========== Bitwise ========== */
|
||||
case SPL_AND: {
|
||||
/* ========== Bitwise ========== */
|
||||
L_SPL_AND:
|
||||
{
|
||||
spl_vm_val_t _b = POP(), _a = POP();
|
||||
PUSH(_a & _b);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
case SPL_OR: {
|
||||
L_SPL_OR:
|
||||
{
|
||||
spl_vm_val_t _b = POP(), _a = POP();
|
||||
PUSH(_a | _b);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
case SPL_XOR: {
|
||||
L_SPL_XOR:
|
||||
{
|
||||
spl_vm_val_t _b = POP(), _a = POP();
|
||||
PUSH(_a ^ _b);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
case SPL_NOT: {
|
||||
L_SPL_NOT:
|
||||
{
|
||||
PUSH(~POP());
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
case SPL_SHL: {
|
||||
L_SPL_SHL:
|
||||
{
|
||||
int _s = (int)(POP() & 63);
|
||||
PUSH(POP() << _s);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
case SPL_SHR_U: {
|
||||
L_SPL_SHR_U:
|
||||
{
|
||||
int _s = (int)(POP() & 63);
|
||||
PUSH(POP() >> _s);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
case SPL_SHR_S: {
|
||||
L_SPL_SHR_S:
|
||||
{
|
||||
int _s = (int)(POP() & 63);
|
||||
PUSH(POP() >> _s);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
/* ========== Comparison ========== */
|
||||
case SPL_EQ:
|
||||
/* ========== Comparison ========== */
|
||||
L_SPL_EQ:
|
||||
CMP_ALL(==);
|
||||
break;
|
||||
case SPL_NE:
|
||||
DISPATCH();
|
||||
L_SPL_NE:
|
||||
CMP_ALL(!=);
|
||||
break;
|
||||
case SPL_SLT:
|
||||
DISPATCH();
|
||||
L_SPL_SLT:
|
||||
CMP_S(<);
|
||||
break;
|
||||
case SPL_SLE:
|
||||
DISPATCH();
|
||||
L_SPL_SLE:
|
||||
CMP_S(<=);
|
||||
break;
|
||||
case SPL_SGT:
|
||||
DISPATCH();
|
||||
L_SPL_SGT:
|
||||
CMP_S(>);
|
||||
break;
|
||||
case SPL_SGE:
|
||||
DISPATCH();
|
||||
L_SPL_SGE:
|
||||
CMP_S(>=);
|
||||
break;
|
||||
case SPL_ULT:
|
||||
DISPATCH();
|
||||
L_SPL_ULT:
|
||||
CMP_U(<);
|
||||
break;
|
||||
case SPL_ULE:
|
||||
DISPATCH();
|
||||
L_SPL_ULE:
|
||||
CMP_U(<=);
|
||||
break;
|
||||
case SPL_UGT:
|
||||
DISPATCH();
|
||||
L_SPL_UGT:
|
||||
CMP_U(>);
|
||||
break;
|
||||
case SPL_UGE:
|
||||
DISPATCH();
|
||||
L_SPL_UGE:
|
||||
CMP_U(>=);
|
||||
break;
|
||||
DISPATCH();
|
||||
|
||||
/* ========== Control Flow (relative offset) ========== */
|
||||
case SPL_JMP:
|
||||
/* ========== Control Flow (relative offset) ========== */
|
||||
L_SPL_JMP:
|
||||
vm->ip = vm->ip + ins->imm;
|
||||
break;
|
||||
DISPATCH();
|
||||
|
||||
case SPL_BZ: {
|
||||
L_SPL_BZ:
|
||||
{
|
||||
if (POP() == 0)
|
||||
vm->ip = vm->ip + ins->imm;
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_BNZ: {
|
||||
L_SPL_BNZ:
|
||||
{
|
||||
if (POP() != 0)
|
||||
vm->ip = vm->ip + ins->imm;
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_CALL: {
|
||||
L_SPL_CALL:
|
||||
{
|
||||
spl_vm_val_t _nargs = ins->imm;
|
||||
spl_vm_val_t _addr = POP();
|
||||
vm->ncalls++;
|
||||
int _hit = fn_breakpoint_hit(vm, _addr);
|
||||
spl_vm_call(vm, _addr, _nargs);
|
||||
if (_hit)
|
||||
return 2; /* 已进入函数,暂停在函数入口 */
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_CALLI: {
|
||||
L_SPL_CALLI:
|
||||
{
|
||||
spl_vm_val_t _addr = POP();
|
||||
spl_vm_val_t _nargs = POP();
|
||||
vm->ncalls++;
|
||||
int _hit = fn_breakpoint_hit(vm, _addr);
|
||||
spl_vm_call(vm, _addr, _nargs);
|
||||
if (_hit)
|
||||
return 2;
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_RET: {
|
||||
L_SPL_RET:
|
||||
{
|
||||
spl_vm_val_t _retval = 0;
|
||||
if (ins->type != SPL_VOID)
|
||||
_retval = POP();
|
||||
@@ -1161,16 +1259,17 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
if (ins->type != SPL_VOID)
|
||||
PUSH(_retval);
|
||||
// spl_vm_stackdump(vm, vm->sp);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_HALT:
|
||||
L_SPL_HALT:
|
||||
if (ins->imm)
|
||||
vm->exit_code = (int)ins->imm;
|
||||
return 1;
|
||||
|
||||
/* ========== Stack / Frame Local Memory ========== */
|
||||
case SPL_ALLOC: {
|
||||
/* ========== Stack / Frame Local Memory ========== */
|
||||
L_SPL_ALLOC:
|
||||
{
|
||||
spl_vm_val_t _k = ins->imm;
|
||||
uintptr_t _new_sp = vm->sp + _k;
|
||||
if (_new_sp > vm->config.max_stack_depth)
|
||||
@@ -1178,14 +1277,15 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
for (uintptr_t _i = vm->sp; _i < _new_sp; _i++)
|
||||
vm->stacks.data[_i] = 0;
|
||||
vm->sp = _new_sp;
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_LADDR:
|
||||
L_SPL_LADDR:
|
||||
PUSH((spl_vm_val_t)((char *)(vm->stacks.data + vm->fp) + ins->imm));
|
||||
break;
|
||||
DISPATCH();
|
||||
|
||||
case SPL_GADDR: {
|
||||
L_SPL_GADDR:
|
||||
{
|
||||
spl_vm_val_t _idx = ins->imm;
|
||||
if (_idx == 0 || _idx >= vec_size(prog->gdata))
|
||||
VM_ERROR("GADDR: global data index out of range");
|
||||
@@ -1193,11 +1293,12 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
if (!gd->data)
|
||||
VM_ERROR("GADDR: null gdata entry");
|
||||
PUSH((spl_vm_val_t)(uintptr_t)gd->data);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
/* ========== Indirect Memory (load/store with types) ========== */
|
||||
case SPL_LOAD: {
|
||||
/* ========== Indirect Memory (load/store with types) ========== */
|
||||
L_SPL_LOAD:
|
||||
{
|
||||
void *_addr = (void *)POP();
|
||||
CHECK_ADDR(_addr, "LOAD");
|
||||
spl_vm_val_t _v = 0;
|
||||
@@ -1209,18 +1310,20 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
_v = (spl_vm_val_t)(((isize)(_v << _shift)) >> _shift);
|
||||
}
|
||||
PUSH(_v);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
case SPL_STORE: {
|
||||
L_SPL_STORE:
|
||||
{
|
||||
spl_vm_val_t _v = POP();
|
||||
void *_addr = (void *)POP();
|
||||
CHECK_ADDR(_addr, "STORE");
|
||||
memcpy(_addr, &_v, spl_type_size(ins->type));
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
/* ========== Type Conversion ========== */
|
||||
case SPL_TRUNC: {
|
||||
/* ========== Type Conversion ========== */
|
||||
L_SPL_TRUNC:
|
||||
{
|
||||
spl_vm_val_t _v = POP();
|
||||
intptr_t _bits = ins->imm;
|
||||
if (_bits < 1 || _bits > 64)
|
||||
@@ -1230,10 +1333,11 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
_v &= _mask;
|
||||
}
|
||||
PUSH(_v);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_SEXT: {
|
||||
L_SPL_SEXT:
|
||||
{
|
||||
spl_vm_val_t _v = POP();
|
||||
intptr_t _bits = ins->imm;
|
||||
if (_bits < 1 || _bits > 64)
|
||||
@@ -1246,10 +1350,11 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
_v |= ~_mask;
|
||||
}
|
||||
PUSH(_v);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_ZEXT: {
|
||||
L_SPL_ZEXT:
|
||||
{
|
||||
spl_vm_val_t _v = POP();
|
||||
intptr_t _bits = ins->imm;
|
||||
if (_bits < 1 || _bits > 64)
|
||||
@@ -1257,11 +1362,12 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
if (_bits < 64)
|
||||
_v &= ((spl_vm_val_t)1 << _bits) - 1;
|
||||
PUSH(_v);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
/* ========== Native Interface ========== */
|
||||
case SPL_NCALL: {
|
||||
/* ========== Native Interface ========== */
|
||||
L_SPL_NCALL:
|
||||
{
|
||||
intptr_t _nargs = ins->imm;
|
||||
spl_vm_val_t _nat_idx = POP();
|
||||
spl_vm_native_t *_nat;
|
||||
@@ -1287,10 +1393,11 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
}
|
||||
vm->sp -= _nargs;
|
||||
PUSH(_result);
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_NLIB: {
|
||||
L_SPL_NLIB:
|
||||
{
|
||||
spl_vm_val_t _si = ins->imm;
|
||||
const char *_lib;
|
||||
if (_si >= vec_size(prog->strtab) || !vec_at(prog->strtab, _si))
|
||||
@@ -1303,11 +1410,12 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
} else {
|
||||
PUSH(_handle);
|
||||
}
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
/* ========== Debug ========== */
|
||||
case SPL_DBG: {
|
||||
/* ========== Debug ========== */
|
||||
L_SPL_DBG:
|
||||
{
|
||||
fprintf(stderr, "---DGB: current ip %zu---\n", vm->ip);
|
||||
spl_vm_dump_instr(vm, vm->ip - 6);
|
||||
spl_vm_dump_instr(vm, vm->ip - 5);
|
||||
@@ -1320,44 +1428,26 @@ int spl_vm_run_once(spl_vm_t *vm) {
|
||||
spl_vm_backtrace(vm, vm->fp);
|
||||
spl_vm_stackdump(vm, vm->sp);
|
||||
fprintf(stderr, "---DGB END ---\n");
|
||||
break;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case SPL_BK: {
|
||||
L_SPL_BK:
|
||||
{
|
||||
return 2; /* breakpoint: pause execution */
|
||||
}
|
||||
|
||||
default:
|
||||
L_default:
|
||||
fprintf(stderr, "vm: unknown opcode %d at ip=%zd\n", ins->opcode, vm->ip - 1);
|
||||
vm->exit_code = 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* canary check in debug mode (canary is at fp-1, invisible to compiled code) */
|
||||
if (vm->debug && vm->fp > 0) {
|
||||
if (STACK_CANARY(vm) != SPL_STACK_CANARY) {
|
||||
snprintf(vm->error_msg, sizeof(vm->error_msg),
|
||||
"STACK CANARY CORRUPTED at ip=%zd, fp=%zd\n", vm->ip - 1, vm->fp);
|
||||
VM_ERROR(vm->error_msg);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spl_vm_run_once(spl_vm_t *vm) { return vm_loop(vm, 1); }
|
||||
|
||||
int spl_vm_run_until(spl_vm_t *vm, size_t step) {
|
||||
size_t _count = 0;
|
||||
int _ret;
|
||||
if (!vm)
|
||||
return -1;
|
||||
while (1) {
|
||||
if (step > 0 && _count >= step)
|
||||
return 0;
|
||||
_ret = spl_vm_run_once(vm);
|
||||
if (_ret != 0)
|
||||
return _ret;
|
||||
_count++;
|
||||
}
|
||||
return vm_loop(vm, step ? step : (size_t)-1);
|
||||
}
|
||||
|
||||
static const char *func_name_by_ip(spl_prog_t *prog, spl_vm_val_t ip) {
|
||||
|
||||
@@ -23,6 +23,9 @@ typedef VEC(spl_callframe_t) spl_frame_vec_t;
|
||||
typedef struct {
|
||||
MAP(const char *, usize) stacks;
|
||||
VEC(usize) self_samples;
|
||||
VEC(uint64_t) self_ns; /* 每函数 time-slice 累加(纳秒) */
|
||||
uint64_t last_ns;
|
||||
uint64_t total_ns;
|
||||
usize samples;
|
||||
} spl_prof_t;
|
||||
|
||||
@@ -35,6 +38,7 @@ typedef struct spl_vm {
|
||||
uintptr_t cp; // call pointer
|
||||
uintptr_t ip; // instr pointer
|
||||
uint64_t steps; // 已执行指令数(供性能分析参考)
|
||||
uint64_t ncalls; // 已执行 CALL/CALLI 次数(供性能分析参考)
|
||||
int exit_code;
|
||||
int trace; /* non-zero to print each instruction */
|
||||
int debug; /* non-zero to enable canary checks */
|
||||
|
||||
+612
@@ -0,0 +1,612 @@
|
||||
/* ir_opt.c - IR 层优化 pass
|
||||
*
|
||||
* 通用函数内联(任意大小/形状的 callee,不再限叶子):
|
||||
* - callee 体内允许 control.call(嵌套调用)、分支/循环、多个返回、unit 返回;
|
||||
* - 参数:克隆体内 Param 的“使用处”替换为调用实参 ref;
|
||||
* - 控制流:克隆体追加到调用者末尾,内部 br/jmp/label 整体 +shift;
|
||||
* - 返回:
|
||||
* * 有返回值:在调用者追加 ret 槽(MemAlloca)与常量 1;每个 Ret 改为
|
||||
* jmp 到“MemStore(槽, val) + jmp(join)”,join 处 MemLoad(槽) 供使用者;
|
||||
* * 无返回值(unit):每个 Ret 直接 jmp 到调用点之后。
|
||||
* - 调用点改为 jmp 到克隆体入口;其使用者改指到 join 的 MemLoad(或 0)。
|
||||
* - 防环:若 callee(传递)可达 caller,则拒绝内联,保证调用图保持无环、
|
||||
* 内联过程终止(直接/间接递归都不会被内联)。
|
||||
* - 迭代到不动点(无环调用图上轮数有上界)。
|
||||
* 默认关闭(splc0 --opt 驱动),且只内联显式 `#inline` 标记的 callee。 */
|
||||
|
||||
#include "ir_opt.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
spl_ir_t *ir;
|
||||
spl_ir_func_ref_t caller;
|
||||
spl_ir_func_ref_t callee;
|
||||
spl_ir_node_ref_t shift; /* new = old + shift */
|
||||
usize nparams;
|
||||
spl_ir_node_ref_t *param_ref;
|
||||
spl_ir_node_ref_t *param_arg;
|
||||
} inline_ctx_t;
|
||||
|
||||
static spl_ir_node_t *nd(spl_ir_t *ir, spl_ir_func_ref_t fid, spl_ir_node_ref_t ref) {
|
||||
return spl_ir_node(ir, fid, ref);
|
||||
}
|
||||
|
||||
/* ---- 引用重写(克隆体旧 ref -> 新 ref) ---- */
|
||||
|
||||
static spl_ir_node_ref_t rw(inline_ctx_t *c, spl_ir_node_ref_t r) {
|
||||
if (r == 0)
|
||||
return 0;
|
||||
for (usize i = 0; i < c->nparams; i++)
|
||||
if (c->param_ref[i] == r)
|
||||
return c->param_arg[i];
|
||||
return r + c->shift;
|
||||
}
|
||||
|
||||
static void rw_vec(inline_ctx_t *c, spl_ir_node_ref_vec_t *v) {
|
||||
for (usize i = 0; i < vec_size(*v); i++)
|
||||
vec_at(*v, i) = rw(c, vec_at(*v, i));
|
||||
}
|
||||
|
||||
static void rw_node(inline_ctx_t *c, spl_ir_node_t *n) {
|
||||
switch (n->kind) {
|
||||
case SPL_IR_ARITH_ADD: case SPL_IR_ARITH_SUB: case SPL_IR_ARITH_MUL:
|
||||
case SPL_IR_ARITH_DIV: case SPL_IR_ARITH_REM: case SPL_IR_ARITH_NEG:
|
||||
case SPL_IR_ARITH_ABS: case SPL_IR_ARITH_AND: case SPL_IR_ARITH_OR:
|
||||
case SPL_IR_ARITH_XOR: case SPL_IR_ARITH_SHL: case SPL_IR_ARITH_SHR:
|
||||
case SPL_IR_ARITH_NOT:
|
||||
n->arith.left = rw(c, n->arith.left);
|
||||
n->arith.right = rw(c, n->arith.right);
|
||||
break;
|
||||
case SPL_IR_CMP_EQ: case SPL_IR_CMP_NE: case SPL_IR_CMP_LT:
|
||||
case SPL_IR_CMP_LE: case SPL_IR_CMP_GT: case SPL_IR_CMP_GE:
|
||||
n->cmp.a = rw(c, n->cmp.a);
|
||||
n->cmp.b = rw(c, n->cmp.b);
|
||||
break;
|
||||
case SPL_IR_CAST_TRUNC: case SPL_IR_CAST_ZEXT: case SPL_IR_CAST_SEXT:
|
||||
case SPL_IR_CAST_FEXT: case SPL_IR_CAST_FTRUNC: case SPL_IR_CAST_BITCAST:
|
||||
case SPL_IR_CAST_PTR2INT: case SPL_IR_CAST_INT2PTR: case SPL_IR_CAST_BOOL2INT:
|
||||
case SPL_IR_CASE_INT2FLOAT: case SPL_IR_CASE_FLOAT2INT:
|
||||
n->cast.val = rw(c, n->cast.val);
|
||||
break;
|
||||
case SPL_IR_MEM_ALLOCA:
|
||||
n->mem_alloc.count = rw(c, n->mem_alloc.count);
|
||||
break;
|
||||
case SPL_IR_MEM_GLOBAL_ALLOC:
|
||||
n->mem_global_alloc.const_node = rw(c, n->mem_global_alloc.const_node);
|
||||
break;
|
||||
case SPL_IR_MEM_LOAD:
|
||||
n->mem_load.ptr = rw(c, n->mem_load.ptr);
|
||||
break;
|
||||
case SPL_IR_MEM_STORE:
|
||||
n->mem_store.ptr = rw(c, n->mem_store.ptr);
|
||||
n->mem_store.val = rw(c, n->mem_store.val);
|
||||
break;
|
||||
case SPL_IR_MEM_OFFSET:
|
||||
n->mem_offset.ptr = rw(c, n->mem_offset.ptr);
|
||||
n->mem_offset.offset = rw(c, n->mem_offset.offset);
|
||||
break;
|
||||
case SPL_IR_MEM_FIELD_PTR:
|
||||
n->mem_field_ptr.agg = rw(c, n->mem_field_ptr.agg);
|
||||
break;
|
||||
case SPL_IR_MEM_COPY:
|
||||
n->mem_copy.dst = rw(c, n->mem_copy.dst);
|
||||
n->mem_copy.src = rw(c, n->mem_copy.src);
|
||||
n->mem_copy.size = rw(c, n->mem_copy.size);
|
||||
break;
|
||||
case SPL_IR_MEM_SET:
|
||||
n->mem_set.dst = rw(c, n->mem_set.dst);
|
||||
n->mem_set.val = rw(c, n->mem_set.val);
|
||||
n->mem_set.size = rw(c, n->mem_set.size);
|
||||
break;
|
||||
case SPL_IR_TYPE_OFFSETOF:
|
||||
n->ir_offsetof.field_idx = rw(c, n->ir_offsetof.field_idx);
|
||||
break;
|
||||
case SPL_IR_AGG_CONSTRUCT:
|
||||
rw_vec(c, &n->agg_construct.fields);
|
||||
break;
|
||||
case SPL_IR_AGG_EXTRACT:
|
||||
n->agg_extract.val = rw(c, n->agg_extract.val);
|
||||
break;
|
||||
case SPL_IR_AGG_INSERT:
|
||||
n->agg_insert.agg = rw(c, n->agg_insert.agg);
|
||||
n->agg_insert.field = rw(c, n->agg_insert.field);
|
||||
break;
|
||||
case SPL_IR_CONTROL_SELECT:
|
||||
n->control_select.cond = rw(c, n->control_select.cond);
|
||||
n->control_select.true_val = rw(c, n->control_select.true_val);
|
||||
n->control_select.false_val = rw(c, n->control_select.false_val);
|
||||
break;
|
||||
case SPL_IR_CONTROL_BR:
|
||||
n->control_br.cond = rw(c, n->control_br.cond);
|
||||
n->control_br.true_label = rw(c, n->control_br.true_label);
|
||||
n->control_br.false_label = rw(c, n->control_br.false_label);
|
||||
break;
|
||||
case SPL_IR_CONTROL_JMP:
|
||||
n->control_jmp.label = rw(c, n->control_jmp.label);
|
||||
break;
|
||||
case SPL_IR_CONTROL_PARAM:
|
||||
n->control_param.idx = rw(c, n->control_param.idx);
|
||||
break;
|
||||
case SPL_IR_CONTROL_RET:
|
||||
n->control_ret.val = rw(c, n->control_ret.val);
|
||||
break;
|
||||
case SPL_IR_CONTROL_CALL:
|
||||
n->control_call.func = rw(c, n->control_call.func);
|
||||
rw_vec(c, &n->control_call.params);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 克隆 ---- */
|
||||
|
||||
static void clone_ref_vec(spl_ir_node_ref_vec_t *dst, const spl_ir_node_ref_vec_t *src) {
|
||||
dst->size = 0;
|
||||
dst->cap = 0;
|
||||
dst->data = NULL;
|
||||
usize n = vec_size(*src);
|
||||
if (n == 0)
|
||||
return;
|
||||
spl_ir_node_ref_t *p = (spl_ir_node_ref_t *)malloc(sizeof(spl_ir_node_ref_t) * n);
|
||||
if (!p)
|
||||
return;
|
||||
memcpy(p, src->data, sizeof(spl_ir_node_ref_t) * n);
|
||||
dst->data = p;
|
||||
dst->size = n;
|
||||
dst->cap = n;
|
||||
}
|
||||
|
||||
static void clone_node(spl_ir_t *ir, spl_ir_func_ref_t caller, spl_ir_node_ref_t dst_ref,
|
||||
const spl_ir_node_t *src) {
|
||||
spl_ir_node_t *dst = nd(ir, caller, dst_ref);
|
||||
memcpy(dst, src, sizeof(*dst));
|
||||
dst->attrs.data = NULL;
|
||||
dst->attrs.size = 0;
|
||||
dst->attrs.cap = 0;
|
||||
if (vec_size(src->attrs) != 0)
|
||||
clone_ref_vec((spl_ir_node_ref_vec_t *)&dst->attrs,
|
||||
(const spl_ir_node_ref_vec_t *)&src->attrs);
|
||||
if (src->kind == SPL_IR_AGG_CONSTRUCT)
|
||||
clone_ref_vec(&dst->agg_construct.fields, &src->agg_construct.fields);
|
||||
else if (src->kind == SPL_IR_CONTROL_CALL)
|
||||
clone_ref_vec(&dst->control_call.params, &src->control_call.params);
|
||||
}
|
||||
|
||||
/* ---- 调用图可达性(防环) ---- */
|
||||
|
||||
static spl_ir_func_ref_t call_callee(spl_ir_t *ir, spl_ir_func_ref_t fid, spl_ir_node_t *call) {
|
||||
spl_ir_node_t *fn = nd(ir, fid, call->control_call.func);
|
||||
if (!fn || fn->kind != SPL_IR_TYPE_CONST)
|
||||
return 0;
|
||||
return fn->type_const.fn;
|
||||
}
|
||||
|
||||
static bool reaches(spl_ir_t *ir, spl_ir_func_ref_t from, spl_ir_func_ref_t target,
|
||||
unsigned char *mark) {
|
||||
if (from == 0)
|
||||
return false;
|
||||
if (from == target)
|
||||
return true;
|
||||
if (mark[from])
|
||||
return false;
|
||||
mark[from] = 1;
|
||||
spl_ir_func_t *f = spl_ir_func(ir, from);
|
||||
if (!f)
|
||||
return false;
|
||||
usize n = vec_size(f->nodes);
|
||||
for (usize i = 1; i < n; i++) {
|
||||
spl_ir_node_t *x = nd(ir, from, i);
|
||||
if (x && x->kind == SPL_IR_CONTROL_CALL) {
|
||||
spl_ir_func_ref_t c = call_callee(ir, from, x);
|
||||
if (c != 0 && reaches(ir, c, target, mark))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 从 from 的直接被调者出发是否能到达 target(用于判断 from 自身是否递归/自可达)。 */
|
||||
static bool reaches_succ(spl_ir_t *ir, spl_ir_func_ref_t from, spl_ir_func_ref_t target,
|
||||
unsigned char *mark) {
|
||||
spl_ir_func_t *f = spl_ir_func(ir, from);
|
||||
if (!f)
|
||||
return false;
|
||||
usize n = vec_size(f->nodes);
|
||||
for (usize i = 1; i < n; i++) {
|
||||
spl_ir_node_t *x = nd(ir, from, i);
|
||||
if (x && x->kind == SPL_IR_CONTROL_CALL) {
|
||||
spl_ir_func_ref_t c = call_callee(ir, from, x);
|
||||
if (c != 0 && reaches(ir, c, target, mark))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ---- 内联一个调用点 ---- */
|
||||
|
||||
typedef struct {
|
||||
spl_ir_node_ref_t clone_ref;
|
||||
spl_ir_node_ref_t val;
|
||||
} ret_rec_t;
|
||||
|
||||
static bool param_index(spl_ir_t *ir, spl_ir_func_ref_t fid, spl_ir_node_t *p, usize *out) {
|
||||
spl_ir_node_t *idx = nd(ir, fid, p->control_param.idx);
|
||||
if (!idx || idx->kind != SPL_IR_TYPE_CONST)
|
||||
return false;
|
||||
*out = idx->type_const.int_lit;
|
||||
return true;
|
||||
}
|
||||
|
||||
static spl_ir_node_ref_t find_const_src(spl_ir_t *ir, spl_ir_func_ref_t fid) {
|
||||
spl_ir_func_t *f = spl_ir_func(ir, fid);
|
||||
if (!f)
|
||||
return 0;
|
||||
for (usize i = 1; i < vec_size(f->nodes); i++) {
|
||||
spl_ir_node_t *x = nd(ir, fid, i);
|
||||
if (x && x->kind == SPL_IR_TYPE_CONST)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool try_inline(spl_ir_t *ir, spl_ir_func_ref_t caller, spl_ir_node_ref_t ci,
|
||||
spl_ir_func_ref_t callee) {
|
||||
spl_ir_func_t *cf = spl_ir_func(ir, caller);
|
||||
spl_ir_func_t *calf = spl_ir_func(ir, callee);
|
||||
if (!cf || !calf || callee == caller)
|
||||
return false;
|
||||
|
||||
spl_ir_node_t *call = nd(ir, caller, ci);
|
||||
if (!call || call->kind != SPL_IR_CONTROL_CALL)
|
||||
return false;
|
||||
|
||||
usize orig_len = vec_size(cf->nodes);
|
||||
if (ci + 1 >= orig_len)
|
||||
return false;
|
||||
|
||||
usize nargs = vec_size(call->control_call.params);
|
||||
|
||||
/* 统计参数与返回,分配动态映射。 */
|
||||
usize cnodes = vec_size(calf->nodes);
|
||||
usize in_params = 0;
|
||||
usize in_rets = 0;
|
||||
for (usize i = 1; i < cnodes; i++) {
|
||||
spl_ir_node_t *x = nd(ir, callee, i);
|
||||
if (!x)
|
||||
return false;
|
||||
if (x->kind == SPL_IR_CONTROL_PARAM)
|
||||
in_params++;
|
||||
else if (x->kind == SPL_IR_CONTROL_RET)
|
||||
in_rets++;
|
||||
}
|
||||
if (in_rets == 0 || in_params != nargs)
|
||||
return false;
|
||||
|
||||
inline_ctx_t c;
|
||||
memset(&c, 0, sizeof(c));
|
||||
c.ir = ir;
|
||||
c.caller = caller;
|
||||
c.callee = callee;
|
||||
c.nparams = in_params;
|
||||
c.param_ref = (spl_ir_node_ref_t *)malloc(sizeof(spl_ir_node_ref_t) * (in_params ? in_params : 1));
|
||||
c.param_arg = (spl_ir_node_ref_t *)malloc(sizeof(spl_ir_node_ref_t) * (in_params ? in_params : 1));
|
||||
ret_rec_t *recs = (ret_rec_t *)malloc(sizeof(ret_rec_t) * (in_rets ? in_rets : 1));
|
||||
if (!c.param_ref || !c.param_arg || !recs) {
|
||||
free(c.param_ref);
|
||||
free(c.param_arg);
|
||||
free(recs);
|
||||
return false;
|
||||
}
|
||||
|
||||
spl_type_id_t ret_tid = call->control_call.tid;
|
||||
|
||||
/* 参数映射(旧 ref -> 实参 ref)。 */
|
||||
usize pi = 0;
|
||||
for (usize i = 1; i < cnodes; i++) {
|
||||
spl_ir_node_t *x = nd(ir, callee, i);
|
||||
if (x && x->kind == SPL_IR_CONTROL_PARAM) {
|
||||
usize idx = 0;
|
||||
if (!param_index(ir, callee, x, &idx) || idx >= nargs) {
|
||||
free(c.param_ref);
|
||||
free(c.param_arg);
|
||||
free(recs);
|
||||
return false;
|
||||
}
|
||||
c.param_ref[pi] = i;
|
||||
c.param_arg[pi] = vec_at(call->control_call.params, idx);
|
||||
pi++;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_val = false;
|
||||
for (usize i = 1; i < cnodes; i++) {
|
||||
spl_ir_node_t *x = nd(ir, callee, i);
|
||||
if (x && x->kind == SPL_IR_CONTROL_RET && x->control_ret.val != 0)
|
||||
has_val = true;
|
||||
}
|
||||
spl_ir_node_ref_t const_src = find_const_src(ir, callee);
|
||||
if (has_val && const_src == 0) {
|
||||
free(c.param_ref);
|
||||
free(c.param_arg);
|
||||
free(recs);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 释放调用点原 params(随后 ci 改为 jmp) */
|
||||
vec_free(call->control_call.params);
|
||||
|
||||
/* 克隆体入口前置:常量 1 + ret 槽 */
|
||||
spl_ir_node_ref_t body_entry = 0;
|
||||
spl_ir_node_ref_t slot_ref = 0;
|
||||
if (has_val) {
|
||||
spl_ir_node_ref_t c_ref = spl_ir_alloc_node(ir, caller);
|
||||
clone_node(ir, caller, c_ref, nd(ir, callee, const_src));
|
||||
spl_ir_node_t *cn = nd(ir, caller, c_ref);
|
||||
cn->kind = SPL_IR_TYPE_CONST;
|
||||
cn->type_const.int_lit = 1;
|
||||
|
||||
slot_ref = spl_ir_alloc_node(ir, caller);
|
||||
spl_ir_node_t *sn = nd(ir, caller, slot_ref);
|
||||
memset(sn, 0, sizeof(*sn));
|
||||
sn->kind = SPL_IR_MEM_ALLOCA;
|
||||
sn->mem_alloc.tid = ret_tid;
|
||||
sn->mem_alloc.count = c_ref;
|
||||
|
||||
body_entry = c_ref;
|
||||
}
|
||||
|
||||
c.shift = vec_size(cf->nodes) - 1;
|
||||
if (!has_val)
|
||||
body_entry = vec_size(cf->nodes);
|
||||
|
||||
/* 克隆体 */
|
||||
usize nrec = 0;
|
||||
for (usize i = 1; i < cnodes; i++) {
|
||||
spl_ir_node_ref_t dst_ref = spl_ir_alloc_node(ir, caller);
|
||||
if (dst_ref == 0) {
|
||||
free(c.param_ref);
|
||||
free(c.param_arg);
|
||||
free(recs);
|
||||
return false;
|
||||
}
|
||||
spl_ir_node_t *src = nd(ir, callee, i);
|
||||
clone_node(ir, caller, dst_ref, src);
|
||||
rw_node(&c, nd(ir, caller, dst_ref));
|
||||
spl_ir_node_t *x = nd(ir, caller, dst_ref);
|
||||
/* 克隆进来的 Param 使用处已替换为实参,节点本身不能留在调用者里
|
||||
* 改变其参数个数;退化为无害常量。 */
|
||||
if (src->kind == SPL_IR_CONTROL_PARAM) {
|
||||
x->kind = SPL_IR_TYPE_CONST;
|
||||
x->type_const.tid = 0;
|
||||
x->type_const.int_lit = 0;
|
||||
continue;
|
||||
}
|
||||
if (x->kind == SPL_IR_CONTROL_RET) {
|
||||
recs[nrec].clone_ref = dst_ref;
|
||||
recs[nrec].val = x->control_ret.val;
|
||||
nrec++;
|
||||
x->kind = SPL_IR_CONTROL_JMP;
|
||||
x->control_jmp.label = 0; /* 稍后回填 */
|
||||
}
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t ret_new = 0;
|
||||
if (has_val) {
|
||||
spl_ir_node_ref_t join = spl_ir_alloc_node(ir, caller);
|
||||
spl_ir_node_t *jn = nd(ir, caller, join);
|
||||
memset(jn, 0, sizeof(*jn));
|
||||
jn->kind = SPL_IR_MEM_LOAD;
|
||||
jn->mem_load.tid = ret_tid;
|
||||
jn->mem_load.ptr = slot_ref;
|
||||
|
||||
/* join 之后跳回调用点的后继(原代码此处靠 fallthrough)。 */
|
||||
spl_ir_node_ref_t jc = spl_ir_alloc_node(ir, caller);
|
||||
spl_ir_node_t *jcn = nd(ir, caller, jc);
|
||||
memset(jcn, 0, sizeof(*jcn));
|
||||
jcn->kind = SPL_IR_CONTROL_JMP;
|
||||
jcn->control_jmp.label = ci + 1;
|
||||
|
||||
for (usize k = 0; k < nrec; k++) {
|
||||
spl_ir_node_ref_t st = spl_ir_alloc_node(ir, caller);
|
||||
spl_ir_node_t *stn = nd(ir, caller, st);
|
||||
memset(stn, 0, sizeof(*stn));
|
||||
stn->kind = SPL_IR_MEM_STORE;
|
||||
stn->mem_store.tid = ret_tid;
|
||||
stn->mem_store.ptr = slot_ref;
|
||||
stn->mem_store.val = recs[k].val;
|
||||
|
||||
spl_ir_node_ref_t jr = spl_ir_alloc_node(ir, caller);
|
||||
spl_ir_node_t *jrn = nd(ir, caller, jr);
|
||||
memset(jrn, 0, sizeof(*jrn));
|
||||
jrn->kind = SPL_IR_CONTROL_JMP;
|
||||
jrn->control_jmp.label = join;
|
||||
|
||||
spl_ir_node_t *rn = nd(ir, caller, recs[k].clone_ref);
|
||||
rn->control_jmp.label = st;
|
||||
}
|
||||
ret_new = join;
|
||||
} else {
|
||||
for (usize k = 0; k < nrec; k++) {
|
||||
spl_ir_node_t *rn = nd(ir, caller, recs[k].clone_ref);
|
||||
rn->control_jmp.label = ci + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 调用点 -> jmp 到克隆体入口 */
|
||||
call = nd(ir, caller, ci);
|
||||
call->kind = SPL_IR_CONTROL_JMP;
|
||||
call->control_jmp.label = body_entry;
|
||||
|
||||
/* 使用者替换:只把等于 ci 的引用改为 ret_new(不动其它 ref) */
|
||||
for (usize j = 1; j < orig_len; j++) {
|
||||
spl_ir_node_t *x = nd(ir, caller, j);
|
||||
if (!x)
|
||||
continue;
|
||||
switch (x->kind) {
|
||||
case SPL_IR_ARITH_ADD: case SPL_IR_ARITH_SUB: case SPL_IR_ARITH_MUL:
|
||||
case SPL_IR_ARITH_DIV: case SPL_IR_ARITH_REM: case SPL_IR_ARITH_NEG:
|
||||
case SPL_IR_ARITH_ABS: case SPL_IR_ARITH_AND: case SPL_IR_ARITH_OR:
|
||||
case SPL_IR_ARITH_XOR: case SPL_IR_ARITH_SHL: case SPL_IR_ARITH_SHR:
|
||||
case SPL_IR_ARITH_NOT:
|
||||
if (x->arith.left == ci) x->arith.left = ret_new;
|
||||
if (x->arith.right == ci) x->arith.right = ret_new;
|
||||
break;
|
||||
case SPL_IR_CMP_EQ: case SPL_IR_CMP_NE: case SPL_IR_CMP_LT:
|
||||
case SPL_IR_CMP_LE: case SPL_IR_CMP_GT: case SPL_IR_CMP_GE:
|
||||
if (x->cmp.a == ci) x->cmp.a = ret_new;
|
||||
if (x->cmp.b == ci) x->cmp.b = ret_new;
|
||||
break;
|
||||
case SPL_IR_CAST_TRUNC: case SPL_IR_CAST_ZEXT: case SPL_IR_CAST_SEXT:
|
||||
case SPL_IR_CAST_FEXT: case SPL_IR_CAST_FTRUNC: case SPL_IR_CAST_BITCAST:
|
||||
case SPL_IR_CAST_PTR2INT: case SPL_IR_CAST_INT2PTR: case SPL_IR_CAST_BOOL2INT:
|
||||
case SPL_IR_CASE_INT2FLOAT: case SPL_IR_CASE_FLOAT2INT:
|
||||
if (x->cast.val == ci) x->cast.val = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_ALLOCA:
|
||||
if (x->mem_alloc.count == ci) x->mem_alloc.count = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_GLOBAL_ALLOC:
|
||||
if (x->mem_global_alloc.const_node == ci) x->mem_global_alloc.const_node = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_LOAD:
|
||||
if (x->mem_load.ptr == ci) x->mem_load.ptr = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_STORE:
|
||||
if (x->mem_store.ptr == ci) x->mem_store.ptr = ret_new;
|
||||
if (x->mem_store.val == ci) x->mem_store.val = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_OFFSET:
|
||||
if (x->mem_offset.ptr == ci) x->mem_offset.ptr = ret_new;
|
||||
if (x->mem_offset.offset == ci) x->mem_offset.offset = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_FIELD_PTR:
|
||||
if (x->mem_field_ptr.agg == ci) x->mem_field_ptr.agg = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_COPY:
|
||||
if (x->mem_copy.dst == ci) x->mem_copy.dst = ret_new;
|
||||
if (x->mem_copy.src == ci) x->mem_copy.src = ret_new;
|
||||
if (x->mem_copy.size == ci) x->mem_copy.size = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_SET:
|
||||
if (x->mem_set.dst == ci) x->mem_set.dst = ret_new;
|
||||
if (x->mem_set.val == ci) x->mem_set.val = ret_new;
|
||||
if (x->mem_set.size == ci) x->mem_set.size = ret_new;
|
||||
break;
|
||||
case SPL_IR_TYPE_OFFSETOF:
|
||||
if (x->ir_offsetof.field_idx == ci) x->ir_offsetof.field_idx = ret_new;
|
||||
break;
|
||||
case SPL_IR_AGG_CONSTRUCT:
|
||||
for (usize k = 0; k < vec_size(x->agg_construct.fields); k++)
|
||||
if (vec_at(x->agg_construct.fields, k) == ci)
|
||||
vec_at(x->agg_construct.fields, k) = ret_new;
|
||||
break;
|
||||
case SPL_IR_AGG_EXTRACT:
|
||||
if (x->agg_extract.val == ci) x->agg_extract.val = ret_new;
|
||||
break;
|
||||
case SPL_IR_AGG_INSERT:
|
||||
if (x->agg_insert.agg == ci) x->agg_insert.agg = ret_new;
|
||||
if (x->agg_insert.field == ci) x->agg_insert.field = ret_new;
|
||||
break;
|
||||
case SPL_IR_CONTROL_SELECT:
|
||||
if (x->control_select.cond == ci) x->control_select.cond = ret_new;
|
||||
if (x->control_select.true_val == ci) x->control_select.true_val = ret_new;
|
||||
if (x->control_select.false_val == ci) x->control_select.false_val = ret_new;
|
||||
break;
|
||||
case SPL_IR_CONTROL_BR:
|
||||
if (x->control_br.cond == ci) x->control_br.cond = ret_new;
|
||||
break;
|
||||
case SPL_IR_CONTROL_CALL:
|
||||
if (x->control_call.func == ci) x->control_call.func = ret_new;
|
||||
for (usize k = 0; k < vec_size(x->control_call.params); k++)
|
||||
if (vec_at(x->control_call.params, k) == ci)
|
||||
vec_at(x->control_call.params, k) = ret_new;
|
||||
break;
|
||||
case SPL_IR_CONTROL_PARAM:
|
||||
if (x->control_param.idx == ci) x->control_param.idx = ret_new;
|
||||
break;
|
||||
case SPL_IR_CONTROL_RET:
|
||||
if (x->control_ret.val == ci) x->control_ret.val = ret_new;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(c.param_ref);
|
||||
free(c.param_arg);
|
||||
free(recs);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool func_has_attr(const spl_attr_t *attr, const spl_ir_func_t *f, const char *name);
|
||||
|
||||
static int opt_function(spl_ir_t *ir, spl_ir_func_ref_t fid, const spl_attr_t *attr,
|
||||
unsigned char *mark, usize nfuncs) {
|
||||
spl_ir_func_t *f = spl_ir_func(ir, fid);
|
||||
if (!f)
|
||||
return 0;
|
||||
int done = 0;
|
||||
usize orig = vec_size(f->nodes);
|
||||
for (usize ci = 1; ci < orig; ci++) {
|
||||
spl_ir_node_t *x = nd(ir, fid, ci);
|
||||
if (!x || x->kind != SPL_IR_CONTROL_CALL)
|
||||
continue;
|
||||
spl_ir_func_ref_t callee = call_callee(ir, fid, x);
|
||||
if (callee == 0 || callee == fid || callee >= nfuncs)
|
||||
continue;
|
||||
spl_ir_func_t *calf = spl_ir_func(ir, callee);
|
||||
if (!func_has_attr(attr, calf, "inline") || func_has_attr(attr, calf, "noinline"))
|
||||
continue;
|
||||
/* extern 是占位体,绝不能内联。 */
|
||||
if (func_has_attr(attr, calf, "extern"))
|
||||
continue;
|
||||
/* 防环:callee 传递可达 caller 时拒绝(覆盖直接/间接互递归)。 */
|
||||
memset(mark, 0, nfuncs);
|
||||
if (reaches(ir, callee, fid, mark))
|
||||
continue;
|
||||
/* 防爆炸:callee 自身递归时拒绝(否则不动点会反复内联其内部调用)。 */
|
||||
memset(mark, 0, nfuncs);
|
||||
if (reaches_succ(ir, callee, callee, mark))
|
||||
continue;
|
||||
if (try_inline(ir, fid, ci, callee))
|
||||
done++;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
/* 是否带有指定名字的属性/tag。 */
|
||||
static bool func_has_attr(const spl_attr_t *attr, const spl_ir_func_t *f, const char *name) {
|
||||
if (!attr || !f)
|
||||
return false;
|
||||
for (usize i = 0; i < vec_size(f->attrs); ++i) {
|
||||
spl_attr_node_t *a = spl_attr_node((spl_attr_t *)attr, vec_at(f->attrs, i));
|
||||
if (!a || !a->info)
|
||||
continue;
|
||||
if (strcmp(a->info->name, name) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void spl_ir_opt(spl_ir_t *ir, const spl_attr_t *attr) {
|
||||
if (!ir)
|
||||
return;
|
||||
usize nfuncs = vec_size(ir->funcs);
|
||||
unsigned char *mark = (unsigned char *)malloc(nfuncs ? nfuncs : 1);
|
||||
if (!mark)
|
||||
return;
|
||||
/* 无环调用图上,最多 nfuncs 轮即可到不动点。 */
|
||||
for (usize pass = 0; pass <= nfuncs; pass++) {
|
||||
int total = 0;
|
||||
usize n = vec_size(ir->funcs);
|
||||
for (spl_ir_func_ref_t fid = 1; fid < n && fid < nfuncs; fid++)
|
||||
total += opt_function(ir, fid, attr, mark, nfuncs);
|
||||
if (total == 0)
|
||||
break;
|
||||
}
|
||||
free(mark);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef __IR_OPT_H__
|
||||
#define __IR_OPT_H__
|
||||
|
||||
/* IR 层优化 pass:输入输出都是 spl_ir_t(节点 arena),纯函数式、可开关。
|
||||
* 由 splc0 --opt 在 ast2ir 之后、ir2vm 之前调用。 */
|
||||
|
||||
#include "spl_ir.h"
|
||||
|
||||
void spl_ir_opt(spl_ir_t *ir, const spl_attr_t *attr);
|
||||
|
||||
#endif /* __IR_OPT_H__ */
|
||||
+319
-34
@@ -344,6 +344,13 @@ typedef struct {
|
||||
frame_layout_t frame;
|
||||
VEC(usize) node_ip;
|
||||
fix_vec_t *fixups;
|
||||
/* 块内栈化(opt) */
|
||||
int opt;
|
||||
uint8_t *stk_loc; /* per node: 1 = 值当前在操作数栈上 */
|
||||
usize *stk_pos; /* 在虚拟栈中的位置 */
|
||||
usize *use_left; /* 剩余使用次数 */
|
||||
usize *at_pos; /* 每个栈位置对应的 node ref */
|
||||
usize tracked; /* 当前栈上被跟踪的值个数 */
|
||||
} cgen_t;
|
||||
|
||||
static void emit(cgen_t *cg, uint8_t op, uint8_t ty, spl_vm_val_t imm) {
|
||||
@@ -378,6 +385,240 @@ static void cg_store_at(cgen_t *cg, usize addr, uint8_t k) {
|
||||
CG_STORE(cg, k);
|
||||
}
|
||||
|
||||
/* ---------- 块内栈化(opt)辅助 ---------- */
|
||||
|
||||
static uint8_t node_result_kind(cgen_t *cg, usize ref) {
|
||||
const spl_ir_node_t *n = &vec_at(cg->irf->nodes, ref);
|
||||
return vm_kind_of(cg->ctx, ir_result_tid(cg->ctx, n));
|
||||
}
|
||||
|
||||
static bool node_stackable(cgen_t *cg, const spl_ir_node_t *n) {
|
||||
switch (n->kind) {
|
||||
case SPL_IR_ARITH_ADD:
|
||||
case SPL_IR_ARITH_SUB:
|
||||
case SPL_IR_ARITH_MUL:
|
||||
case SPL_IR_ARITH_DIV:
|
||||
case SPL_IR_ARITH_REM:
|
||||
case SPL_IR_ARITH_NEG:
|
||||
case SPL_IR_ARITH_AND:
|
||||
case SPL_IR_ARITH_OR:
|
||||
case SPL_IR_ARITH_XOR:
|
||||
case SPL_IR_ARITH_SHL:
|
||||
case SPL_IR_ARITH_SHR:
|
||||
case SPL_IR_ARITH_NOT:
|
||||
case SPL_IR_CMP_EQ:
|
||||
case SPL_IR_CMP_NE:
|
||||
case SPL_IR_CMP_LT:
|
||||
case SPL_IR_CMP_LE:
|
||||
case SPL_IR_CMP_GT:
|
||||
case SPL_IR_CMP_GE:
|
||||
case SPL_IR_CAST_TRUNC:
|
||||
case SPL_IR_CAST_ZEXT:
|
||||
case SPL_IR_CAST_SEXT:
|
||||
case SPL_IR_CAST_FEXT:
|
||||
case SPL_IR_CAST_FTRUNC:
|
||||
case SPL_IR_CAST_BITCAST:
|
||||
case SPL_IR_CAST_PTR2INT:
|
||||
case SPL_IR_CAST_INT2PTR:
|
||||
case SPL_IR_CAST_BOOL2INT:
|
||||
case SPL_IR_CASE_INT2FLOAT:
|
||||
case SPL_IR_CASE_FLOAT2INT:
|
||||
case SPL_IR_TYPE_CONST: {
|
||||
spl_type_node_t *tn = spl_type_node(cg->ctx->type, n->type_const.tid);
|
||||
return tn && tn->kind != SPL_TYPE_UNIT;
|
||||
}
|
||||
case SPL_IR_TYPE_BITSIZEOF:
|
||||
case SPL_IR_TYPE_SIZEOF:
|
||||
case SPL_IR_TYPE_ALIGNOF:
|
||||
case SPL_IR_TYPE_OFFSETOF:
|
||||
case SPL_IR_TYPE_FIELD_COUNT:
|
||||
case SPL_IR_MEM_OFFSET:
|
||||
case SPL_IR_MEM_FIELD_PTR:
|
||||
return true;
|
||||
case SPL_IR_MEM_LOAD:
|
||||
return !ir_is_aggregate(cg->ctx, n->mem_load.tid);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* 该节点必须使用槽(直接按槽偏移读写 / 控制转移 / 聚合数据),生成前需 flush。 */
|
||||
static bool node_needs_slots(cgen_t *cg, const spl_ir_node_t *n) {
|
||||
switch (n->kind) {
|
||||
case SPL_IR_MEM_ALLOCA:
|
||||
case SPL_IR_MEM_GLOBAL_ALLOC:
|
||||
case SPL_IR_MEM_COPY:
|
||||
case SPL_IR_MEM_SET:
|
||||
case SPL_IR_AGG_CONSTRUCT:
|
||||
case SPL_IR_AGG_EXTRACT:
|
||||
case SPL_IR_AGG_INSERT:
|
||||
case SPL_IR_CONTROL_SELECT:
|
||||
case SPL_IR_CONTROL_BR:
|
||||
case SPL_IR_CONTROL_JMP:
|
||||
case SPL_IR_CONTROL_RET:
|
||||
case SPL_IR_CONTROL_UNREACHABLE:
|
||||
case SPL_IR_CONTROL_TRAP:
|
||||
case SPL_IR_DBG_PANIC:
|
||||
case SPL_IR_DBG_BREAKPOINT:
|
||||
return true;
|
||||
case SPL_IR_MEM_LOAD:
|
||||
return ir_is_aggregate(cg->ctx, n->mem_load.tid);
|
||||
case SPL_IR_MEM_STORE:
|
||||
return ir_is_aggregate(cg->ctx, n->mem_store.tid);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* 取操作数到 TOS:若在栈上就 PICK,否则从槽 load。j = 本节点已压入的操作数个数。 */
|
||||
static void cg_operand(cgen_t *cg, usize ref, uint8_t k, usize j) {
|
||||
if (cg->opt) {
|
||||
if (cg->use_left[ref] > 0)
|
||||
cg->use_left[ref]--;
|
||||
if (cg->stk_loc[ref]) {
|
||||
/* 始终复制一份到 TOS,绝不消费栈上的“被跟踪原值”,否则位置模型失效。 */
|
||||
usize dist = cg->tracked + j - 1 - cg->stk_pos[ref];
|
||||
emit(cg, SPL_PICK, SPL_VOID, dist);
|
||||
return;
|
||||
}
|
||||
}
|
||||
cg_load_slot(cg, ref, k);
|
||||
}
|
||||
|
||||
/* 节点结果:若仍会被使用则留在栈上,否则 DROP。 */
|
||||
static void cg_result(cgen_t *cg, usize ref, uint8_t k) {
|
||||
if (cg->opt) {
|
||||
if (cg->use_left[ref] > 0) {
|
||||
cg->stk_loc[ref] = 1;
|
||||
cg->stk_pos[ref] = cg->tracked;
|
||||
cg->at_pos[cg->tracked] = ref;
|
||||
cg->tracked++;
|
||||
} else {
|
||||
emit(cg, SPL_DROP, SPL_VOID, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
cg_store_slot(cg, ref, k);
|
||||
}
|
||||
|
||||
/* 把栈上仍存活的值溢出回各自槽(TOS 起),死值 DROP;清空跟踪。 */
|
||||
static void cg_flush(cgen_t *cg) {
|
||||
if (!cg->opt)
|
||||
return;
|
||||
while (cg->tracked > 0) {
|
||||
cg->tracked--;
|
||||
usize r = cg->at_pos[cg->tracked];
|
||||
cg->stk_loc[r] = 0;
|
||||
if (cg->use_left[r] > 0) {
|
||||
uint8_t k = node_result_kind(cg, r);
|
||||
cg_store_slot(cg, r, k);
|
||||
} else {
|
||||
emit(cg, SPL_DROP, SPL_VOID, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 统计被引用次数(所有节点载荷中的 node ref)。 */
|
||||
static void count_ref(cgen_t *cg, usize r) {
|
||||
if (r != 0 && r < vec_size(cg->irf->nodes))
|
||||
cg->use_left[r]++;
|
||||
}
|
||||
static void count_refs_vec(cgen_t *cg, const spl_ir_node_ref_vec_t *v) {
|
||||
for (usize i = 0; i < vec_size(*v); i++)
|
||||
count_ref(cg, vec_at(*v, i));
|
||||
}
|
||||
static void count_refs(cgen_t *cg, const spl_ir_node_t *n) {
|
||||
switch (n->kind) {
|
||||
case SPL_IR_ARITH_ADD: case SPL_IR_ARITH_SUB: case SPL_IR_ARITH_MUL:
|
||||
case SPL_IR_ARITH_DIV: case SPL_IR_ARITH_REM: case SPL_IR_ARITH_NEG:
|
||||
case SPL_IR_ARITH_ABS: case SPL_IR_ARITH_AND: case SPL_IR_ARITH_OR:
|
||||
case SPL_IR_ARITH_XOR: case SPL_IR_ARITH_SHL: case SPL_IR_ARITH_SHR:
|
||||
case SPL_IR_ARITH_NOT:
|
||||
count_ref(cg, n->arith.left);
|
||||
count_ref(cg, n->arith.right);
|
||||
break;
|
||||
case SPL_IR_CMP_EQ: case SPL_IR_CMP_NE: case SPL_IR_CMP_LT:
|
||||
case SPL_IR_CMP_LE: case SPL_IR_CMP_GT: case SPL_IR_CMP_GE:
|
||||
count_ref(cg, n->cmp.a);
|
||||
count_ref(cg, n->cmp.b);
|
||||
break;
|
||||
case SPL_IR_CAST_TRUNC: case SPL_IR_CAST_ZEXT: case SPL_IR_CAST_SEXT:
|
||||
case SPL_IR_CAST_FEXT: case SPL_IR_CAST_FTRUNC: case SPL_IR_CAST_BITCAST:
|
||||
case SPL_IR_CAST_PTR2INT: case SPL_IR_CAST_INT2PTR: case SPL_IR_CAST_BOOL2INT:
|
||||
case SPL_IR_CASE_INT2FLOAT: case SPL_IR_CASE_FLOAT2INT:
|
||||
count_ref(cg, n->cast.val);
|
||||
break;
|
||||
case SPL_IR_MEM_ALLOCA:
|
||||
count_ref(cg, n->mem_alloc.count);
|
||||
break;
|
||||
case SPL_IR_MEM_GLOBAL_ALLOC:
|
||||
count_ref(cg, n->mem_global_alloc.const_node);
|
||||
break;
|
||||
case SPL_IR_MEM_LOAD:
|
||||
count_ref(cg, n->mem_load.ptr);
|
||||
break;
|
||||
case SPL_IR_MEM_STORE:
|
||||
count_ref(cg, n->mem_store.ptr);
|
||||
count_ref(cg, n->mem_store.val);
|
||||
break;
|
||||
case SPL_IR_MEM_OFFSET:
|
||||
count_ref(cg, n->mem_offset.ptr);
|
||||
count_ref(cg, n->mem_offset.offset);
|
||||
break;
|
||||
case SPL_IR_MEM_FIELD_PTR:
|
||||
count_ref(cg, n->mem_field_ptr.agg);
|
||||
break;
|
||||
case SPL_IR_MEM_COPY:
|
||||
count_ref(cg, n->mem_copy.dst);
|
||||
count_ref(cg, n->mem_copy.src);
|
||||
count_ref(cg, n->mem_copy.size);
|
||||
break;
|
||||
case SPL_IR_MEM_SET:
|
||||
count_ref(cg, n->mem_set.dst);
|
||||
count_ref(cg, n->mem_set.val);
|
||||
count_ref(cg, n->mem_set.size);
|
||||
break;
|
||||
case SPL_IR_TYPE_OFFSETOF:
|
||||
count_ref(cg, n->ir_offsetof.field_idx);
|
||||
break;
|
||||
case SPL_IR_AGG_CONSTRUCT:
|
||||
count_refs_vec(cg, &n->agg_construct.fields);
|
||||
break;
|
||||
case SPL_IR_AGG_EXTRACT:
|
||||
count_ref(cg, n->agg_extract.val);
|
||||
break;
|
||||
case SPL_IR_AGG_INSERT:
|
||||
count_ref(cg, n->agg_insert.agg);
|
||||
count_ref(cg, n->agg_insert.field);
|
||||
break;
|
||||
case SPL_IR_CONTROL_SELECT:
|
||||
count_ref(cg, n->control_select.cond);
|
||||
count_ref(cg, n->control_select.true_val);
|
||||
count_ref(cg, n->control_select.false_val);
|
||||
break;
|
||||
case SPL_IR_CONTROL_BR:
|
||||
count_ref(cg, n->control_br.cond);
|
||||
count_ref(cg, n->control_br.true_label);
|
||||
count_ref(cg, n->control_br.false_label);
|
||||
break;
|
||||
case SPL_IR_CONTROL_JMP:
|
||||
count_ref(cg, n->control_jmp.label);
|
||||
break;
|
||||
case SPL_IR_CONTROL_CALL:
|
||||
count_ref(cg, n->control_call.func);
|
||||
count_refs_vec(cg, &n->control_call.params);
|
||||
break;
|
||||
case SPL_IR_CONTROL_PARAM:
|
||||
count_ref(cg, n->control_param.idx);
|
||||
break;
|
||||
case SPL_IR_CONTROL_RET:
|
||||
count_ref(cg, n->control_ret.val);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
bool indirect;
|
||||
usize off;
|
||||
@@ -439,25 +680,25 @@ static void cg_add_fix(cgen_t *cg, usize ins_idx, fix_kind_t kind, spl_ir_node_r
|
||||
|
||||
static void cg_binop(cgen_t *cg, usize ref, const spl_ir_node_t *n, uint8_t op) {
|
||||
uint8_t k = vm_kind_of(cg->ctx, n->arith.tid);
|
||||
cg_load_slot(cg, n->arith.left, k);
|
||||
cg_load_slot(cg, n->arith.right, k);
|
||||
cg_operand(cg, n->arith.left, k, 0);
|
||||
cg_operand(cg, n->arith.right, k, 1);
|
||||
CG_OP(cg, op, k);
|
||||
cg_store_slot(cg, ref, k);
|
||||
cg_result(cg, ref, k);
|
||||
}
|
||||
|
||||
static void cg_unop(cgen_t *cg, usize ref, const spl_ir_node_t *n, uint8_t op) {
|
||||
uint8_t k = vm_kind_of(cg->ctx, n->arith.tid);
|
||||
cg_load_slot(cg, n->arith.left, k);
|
||||
cg_operand(cg, n->arith.left, k, 0);
|
||||
CG_OP(cg, op, k);
|
||||
cg_store_slot(cg, ref, k);
|
||||
cg_result(cg, ref, k);
|
||||
}
|
||||
|
||||
static void cg_cmp(cgen_t *cg, usize ref, const spl_ir_node_t *n, uint8_t op) {
|
||||
uint8_t k = vm_kind_of(cg->ctx, n->cmp.tid);
|
||||
cg_load_slot(cg, n->cmp.a, k);
|
||||
cg_load_slot(cg, n->cmp.b, k);
|
||||
cg_operand(cg, n->cmp.a, k, 0);
|
||||
cg_operand(cg, n->cmp.b, k, 1);
|
||||
CG_OP(cg, op, k);
|
||||
cg_store_slot(cg, ref, SPL_BOOL);
|
||||
cg_result(cg, ref, SPL_BOOL);
|
||||
}
|
||||
|
||||
static void cg_const(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
@@ -479,7 +720,7 @@ static void cg_const(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
CG_PUSH(cg, SPL_PTR, 0);
|
||||
cg_add_fix(cg, idx, FIX_FNADDR, 0, n->type_const.fn);
|
||||
}
|
||||
cg_store_slot(cg, ref, SPL_PTR);
|
||||
cg_result(cg, ref, SPL_PTR);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -507,7 +748,7 @@ static void cg_const(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
break;
|
||||
}
|
||||
CG_PUSH(cg, k, imm);
|
||||
cg_store_slot(cg, ref, k);
|
||||
cg_result(cg, ref, k);
|
||||
}
|
||||
|
||||
static void cg_cast(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
@@ -520,7 +761,7 @@ static void cg_cast(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
uint8_t fk = vm_kind_of(ctx, n->cast.from_tid);
|
||||
uint8_t tk = vm_kind_of(ctx, n->cast.to_tid);
|
||||
|
||||
cg_load_slot(cg, n->cast.val, fk);
|
||||
cg_operand(cg, n->cast.val, fk, 0);
|
||||
|
||||
if (ft->kind == SPL_TYPE_INT && tt->kind == SPL_TYPE_INT) {
|
||||
usize fb = ft->int_type.bits;
|
||||
@@ -533,7 +774,7 @@ static void cg_cast(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
emit(cg, SPL_ZEXT, SPL_VOID, 1);
|
||||
}
|
||||
|
||||
cg_store_slot(cg, ref, tk);
|
||||
cg_result(cg, ref, tk);
|
||||
}
|
||||
|
||||
static void cg_mem(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
@@ -571,9 +812,9 @@ static void cg_mem(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
cg_store_slot(cg, ref, SPL_PTR);
|
||||
} else {
|
||||
uint8_t k = vm_kind_of(ctx, tid);
|
||||
cg_load_slot(cg, n->mem_load.ptr, SPL_PTR);
|
||||
cg_operand(cg, n->mem_load.ptr, SPL_PTR, 0);
|
||||
CG_LOAD(cg, k);
|
||||
cg_store_slot(cg, ref, k);
|
||||
cg_result(cg, ref, k);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -585,8 +826,8 @@ static void cg_mem(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
ptr_slot(vec_at(cg->frame.slots, n->mem_store.ptr).result_off), tid);
|
||||
} else {
|
||||
uint8_t k = vm_kind_of(ctx, tid);
|
||||
cg_load_slot(cg, n->mem_store.ptr, SPL_PTR);
|
||||
cg_load_slot(cg, n->mem_store.val, k);
|
||||
cg_operand(cg, n->mem_store.ptr, SPL_PTR, 0);
|
||||
cg_operand(cg, n->mem_store.val, k, 1);
|
||||
CG_STORE(cg, k);
|
||||
}
|
||||
break;
|
||||
@@ -596,24 +837,24 @@ static void cg_mem(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
usize elem_size = ir_type_size(ctx, n->mem_offset.tid);
|
||||
if (elem_size == 0)
|
||||
elem_size = 1;
|
||||
cg_load_slot(cg, n->mem_offset.ptr, SPL_PTR);
|
||||
cg_load_slot(cg, n->mem_offset.offset, SPL_U64);
|
||||
cg_operand(cg, n->mem_offset.ptr, SPL_PTR, 0);
|
||||
cg_operand(cg, n->mem_offset.offset, SPL_U64, 1);
|
||||
CG_PUSH(cg, SPL_U64, elem_size);
|
||||
CG_OP(cg, SPL_MUL, SPL_U64);
|
||||
CG_OP(cg, SPL_ADD, SPL_PTR);
|
||||
cg_store_slot(cg, ref, SPL_PTR);
|
||||
cg_result(cg, ref, SPL_PTR);
|
||||
break;
|
||||
}
|
||||
|
||||
case SPL_IR_MEM_FIELD_PTR: {
|
||||
spl_type_id_t agg_tid = n->mem_field_ptr.tid;
|
||||
usize off = ir_agg_field_off(ctx, agg_tid, n->mem_field_ptr.field_idx);
|
||||
cg_load_slot(cg, n->mem_field_ptr.agg, SPL_PTR);
|
||||
cg_operand(cg, n->mem_field_ptr.agg, SPL_PTR, 0);
|
||||
if (off) {
|
||||
CG_PUSH(cg, SPL_U64, off);
|
||||
CG_OP(cg, SPL_ADD, SPL_PTR);
|
||||
}
|
||||
cg_store_slot(cg, ref, SPL_PTR);
|
||||
cg_result(cg, ref, SPL_PTR);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -742,7 +983,7 @@ static void cg_typeop(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
return;
|
||||
}
|
||||
CG_PUSH(cg, SPL_U64, v);
|
||||
cg_store_slot(cg, ref, SPL_U64);
|
||||
cg_result(cg, ref, SPL_U64);
|
||||
}
|
||||
|
||||
static void cg_control(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
@@ -829,44 +1070,47 @@ static void cg_control(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
const spl_ir_node_t *pn = &vec_at(cg->irf->nodes, pref);
|
||||
spl_type_id_t ptid = ir_result_tid(ctx, pn);
|
||||
uint8_t pk = vm_kind_of(ctx, ptid);
|
||||
cg_load_slot(cg, pref, pk);
|
||||
cg_operand(cg, pref, pk, i);
|
||||
}
|
||||
CG_PUSH(cg, SPL_U64, (spl_vm_val_t)nat_idx);
|
||||
emit(cg, SPL_NCALL, SPL_VOID, (spl_vm_val_t)nargs);
|
||||
|
||||
uint8_t rk = vm_kind_of(ctx, n->control_call.tid);
|
||||
if (rk == SPL_VOID)
|
||||
emit(cg, SPL_DROP, SPL_VOID, 0); /* NCALL 总是 push 一个结果 */
|
||||
emit(cg, SPL_DROP, SPL_VOID, 0); /* NCALL always push result */
|
||||
else
|
||||
cg_store_slot(cg, ref, rk);
|
||||
cg_result(cg, ref, rk);
|
||||
} else {
|
||||
spl_type_id_t ret_t = n->control_call.tid;
|
||||
bool ret_is_sret = ir_is_aggregate(ctx, ret_t);
|
||||
usize sret_off = 0;
|
||||
usize j = 0;
|
||||
|
||||
if (ret_is_sret) {
|
||||
sret_off = vec_at(cg->frame.slots, ref).data_off;
|
||||
CG_LADDR(cg, sret_off);
|
||||
j = 1;
|
||||
}
|
||||
for (usize i = 0; i < nargs; ++i) {
|
||||
spl_ir_node_ref_t pref = vec_at(n->control_call.params, i);
|
||||
const spl_ir_node_t *pn = &vec_at(cg->irf->nodes, pref);
|
||||
spl_type_id_t ptid = ir_result_tid(ctx, pn);
|
||||
uint8_t pk = vm_kind_of(ctx, ptid);
|
||||
cg_load_slot(cg, pref, pk);
|
||||
cg_operand(cg, pref, pk, j);
|
||||
j++;
|
||||
}
|
||||
cg_load_slot(cg, fref, SPL_PTR);
|
||||
cg_operand(cg, fref, SPL_PTR, j);
|
||||
spl_vm_val_t actual_nargs = nargs + (ret_is_sret ? 1 : 0);
|
||||
emit(cg, SPL_CALL, SPL_VOID, actual_nargs);
|
||||
|
||||
if (ret_is_sret) {
|
||||
/* 结果槽保存 sret 指针,后续 agg_extract 把它当聚合首地址 */
|
||||
/* result slot saves sret pointer */
|
||||
CG_LADDR(cg, sret_off);
|
||||
cg_store_slot(cg, ref, SPL_PTR);
|
||||
cg_result(cg, ref, SPL_PTR);
|
||||
} else {
|
||||
uint8_t rk = vm_kind_of(ctx, ret_t);
|
||||
if (rk != SPL_VOID)
|
||||
cg_store_slot(cg, ref, rk);
|
||||
cg_result(cg, ref, rk);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -933,10 +1177,10 @@ static void cg_node(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
case SPL_IR_ARITH_NOT: {
|
||||
spl_type_node_t *tn = spl_type_node(cg->ctx->type, n->arith.tid);
|
||||
if (tn && tn->kind == SPL_TYPE_BOOL) {
|
||||
cg_load_slot(cg, n->arith.left, SPL_BOOL);
|
||||
cg_operand(cg, n->arith.left, SPL_BOOL, 0);
|
||||
CG_PUSH(cg, SPL_BOOL, 0);
|
||||
CG_OP(cg, SPL_EQ, SPL_BOOL);
|
||||
cg_store_slot(cg, ref, SPL_BOOL);
|
||||
cg_result(cg, ref, SPL_BOOL);
|
||||
} else {
|
||||
cg_unop(cg, ref, n, SPL_NOT);
|
||||
}
|
||||
@@ -1098,9 +1342,49 @@ static usize translate_function(spl_ir2vm_t *ctx, spl_ir_func_ref_t fid, fix_vec
|
||||
for (usize i = 0; i < nnodes; ++i)
|
||||
vec_push(cg.node_ip, 0);
|
||||
|
||||
unsigned char *leader = NULL;
|
||||
cg.opt = ctx->opt;
|
||||
if (ctx->opt) {
|
||||
cg.stk_loc = (uint8_t *)calloc(nnodes, 1);
|
||||
cg.stk_pos = (usize *)calloc(nnodes, sizeof(usize));
|
||||
cg.use_left = (usize *)calloc(nnodes, sizeof(usize));
|
||||
cg.at_pos = (usize *)calloc(nnodes, sizeof(usize));
|
||||
leader = (unsigned char *)calloc(nnodes, 1);
|
||||
cg.tracked = 0;
|
||||
for (usize i = 1; i < nnodes; ++i)
|
||||
count_refs(&cg, &vec_at(irf->nodes, i));
|
||||
if (nnodes > 1)
|
||||
leader[1] = 1;
|
||||
for (usize i = 1; i < nnodes; ++i) {
|
||||
const spl_ir_node_t *n = &vec_at(irf->nodes, i);
|
||||
if (n->kind == SPL_IR_CONTROL_BR) {
|
||||
if (n->control_br.true_label < nnodes)
|
||||
leader[n->control_br.true_label] = 1;
|
||||
if (n->control_br.false_label < nnodes)
|
||||
leader[n->control_br.false_label] = 1;
|
||||
} else if (n->kind == SPL_IR_CONTROL_JMP) {
|
||||
if (n->control_jmp.label < nnodes)
|
||||
leader[n->control_jmp.label] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (usize i = 1; i < nnodes; ++i) {
|
||||
const spl_ir_node_t *n = &vec_at(irf->nodes, i);
|
||||
if (cg.opt) {
|
||||
if (leader[i] || node_needs_slots(&cg, n))
|
||||
cg_flush(&cg);
|
||||
}
|
||||
vec_at(cg.node_ip, i) = ip_of(&cg);
|
||||
cg_node(&cg, i, &vec_at(irf->nodes, i));
|
||||
cg_node(&cg, i, n);
|
||||
}
|
||||
if (cg.opt) {
|
||||
cg_flush(&cg);
|
||||
free(cg.stk_loc);
|
||||
free(cg.stk_pos);
|
||||
free(cg.use_left);
|
||||
free(cg.at_pos);
|
||||
free(leader);
|
||||
}
|
||||
|
||||
for (usize i = 0; i < vec_size(local_fixups); ++i) {
|
||||
@@ -1124,12 +1408,13 @@ static usize spl_map_hash(usize k) { return MAP_HASH_INT(k); }
|
||||
static int spl_map_cmp(usize a, usize b) { return MAP_CMP_INT(a, b); }
|
||||
|
||||
void spl_ir2vm_init(spl_ir2vm_t *ctx, const spl_ir_t *ir, const spl_type_t *type,
|
||||
spl_layout_t *layout, const spl_attr_t *attr) {
|
||||
spl_layout_t *layout, const spl_attr_t *attr, int opt) {
|
||||
Assert(ir != NULL && type != NULL && attr != NULL);
|
||||
ctx->ir = ir;
|
||||
ctx->type = type;
|
||||
ctx->attr = attr;
|
||||
ctx->layout = layout;
|
||||
ctx->opt = opt;
|
||||
map_init(ctx->extern_native, spl_map_hash, spl_map_cmp);
|
||||
spl_prog_init(&ctx->prog);
|
||||
|
||||
|
||||
+2
-1
@@ -25,10 +25,11 @@ typedef struct {
|
||||
spl_layout_t *layout; /* 借用 splc0 的 layout(size/align/offset 唯一来源) */
|
||||
|
||||
spl_diag_t *diag;
|
||||
int opt; /* 非 0:块内操作数栈化(表达式临时值留栈,块边界溢出回槽) */
|
||||
} spl_ir2vm_t;
|
||||
|
||||
void spl_ir2vm_init(spl_ir2vm_t *ctx, const spl_ir_t *ir, const spl_type_t *type,
|
||||
spl_layout_t *layout, const spl_attr_t *attr);
|
||||
spl_layout_t *layout, const spl_attr_t *attr, int opt);
|
||||
void spl_ir2vm_drop(spl_ir2vm_t *ctx);
|
||||
|
||||
int spl_ir2vm_run_func(spl_ir2vm_t *ctx, spl_ir_func_ref_t ref);
|
||||
|
||||
+11
-10
@@ -764,8 +764,7 @@ static void sema_collect_signatures(spl_sema_t *sema) {
|
||||
spl_def_node_t *d = &vec_at(sema->type->def_table, i);
|
||||
/* Nested imports share def_table, but each module has its own scope.
|
||||
* Resolve other modules' signatures only when processing their AST. */
|
||||
if (d->owner_ast == sema->ast && d->kind == SPL_DEF_FN &&
|
||||
d->state == SPL_DECL_UNRESOLVED)
|
||||
if (d->owner_ast == sema->ast && d->kind == SPL_DEF_FN && d->state == SPL_DECL_UNRESOLVED)
|
||||
sema_eval_decl(sema, i);
|
||||
}
|
||||
}
|
||||
@@ -830,14 +829,14 @@ static spl_lang_type_id_t sema_infer_type_context(spl_sema_t *sema, spl_ast_node
|
||||
spl_symbol_t symbol;
|
||||
if (!spl_scope_find(sema->scope, n->ident.name, &symbol) ||
|
||||
symbol.kind != SPL_SYMBOL_KIND_TYPE) {
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_TYPE_MISMATCH, &n->dbg,
|
||||
"`%s` is not a type", n->ident.name);
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_TYPE_MISMATCH, &n->dbg, "`%s` is not a type",
|
||||
n->ident.name);
|
||||
return 0;
|
||||
}
|
||||
spl_def_node_t *def = spl_def_node(sema->type, symbol.node);
|
||||
if (!sema_def_is_type(def)) {
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_TYPE_MISMATCH, &n->dbg,
|
||||
"`%s` is not a type", n->ident.name);
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_TYPE_MISMATCH, &n->dbg, "`%s` is not a type",
|
||||
n->ident.name);
|
||||
return 0;
|
||||
}
|
||||
n->resolved_def_id = symbol.node;
|
||||
@@ -880,16 +879,15 @@ static spl_lang_type_id_t sema_infer_type_context(spl_sema_t *sema, spl_ast_node
|
||||
spl_lang_type_id_vec_t elements;
|
||||
vec_init(elements);
|
||||
for (usize i = 0; i < vec_size(n->tuple.elems); ++i) {
|
||||
spl_lang_type_id_t element =
|
||||
sema_infer_type_context(sema, vec_at(n->tuple.elems, i));
|
||||
spl_lang_type_id_t element = sema_infer_type_context(sema, vec_at(n->tuple.elems, i));
|
||||
if (!element) {
|
||||
vec_free(elements);
|
||||
return 0;
|
||||
}
|
||||
vec_push(elements, element);
|
||||
}
|
||||
spl_lang_type_id_t result = spl_lang_make_tuple(
|
||||
sema->type, vec_unsafe_get_data(elements), vec_size(elements));
|
||||
spl_lang_type_id_t result =
|
||||
spl_lang_make_tuple(sema->type, vec_unsafe_get_data(elements), vec_size(elements));
|
||||
vec_free(elements);
|
||||
n->resolved_lang_tid = result;
|
||||
return result;
|
||||
@@ -2866,6 +2864,7 @@ 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);
|
||||
Assert(scope != NULL);
|
||||
printf("scope[%zu] parent=%zu\n", i, scope->parent);
|
||||
|
||||
/* 按符号名排序输出:与哈希表内部布局解耦,保证确定顺序。 */
|
||||
@@ -2873,6 +2872,7 @@ void spl_sema_dump(spl_sema_t *sema) {
|
||||
spl_sema_sym_t *arr = nsyms ? malloc(nsyms * sizeof(*arr)) : NULL;
|
||||
usize k = 0;
|
||||
map_for(scope->symbols, j) {
|
||||
Assert(arr != NULL);
|
||||
arr[k].key = unsafe_map_at(scope->symbols, j).key;
|
||||
arr[k].sym = unsafe_map_at(scope->symbols, j).val;
|
||||
k++;
|
||||
@@ -2880,6 +2880,7 @@ void spl_sema_dump(spl_sema_t *sema) {
|
||||
if (arr && nsyms > 1)
|
||||
qsort(arr, nsyms, sizeof(*arr), spl_sema_sym_cmp);
|
||||
for (k = 0; k < nsyms; ++k) {
|
||||
Assert(arr != NULL);
|
||||
printf(" %s -> def`%s`#%zu\n", arr[k].key, arr[k].sym.name, arr[k].sym.node);
|
||||
}
|
||||
free(arr);
|
||||
|
||||
+27
-4
@@ -10,6 +10,9 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ir_opt.h"
|
||||
#include "vm_opt.h"
|
||||
|
||||
#include "spl_ast.h"
|
||||
#include "spl_ast2ir.h"
|
||||
#include "spl_diag.h"
|
||||
@@ -217,7 +220,7 @@ static spl_ast_t *driver_load(void *user, const char *from_fname, const char *pa
|
||||
}
|
||||
|
||||
static int compile_spl(const char *src, const char *fname, const char *outpath, int gen_debug,
|
||||
dump_t dump) {
|
||||
dump_t dump, int opt) {
|
||||
(void)gen_debug;
|
||||
|
||||
spl_diag_t diag;
|
||||
@@ -326,6 +329,10 @@ static int compile_spl(const char *src, const char *fname, const char *outpath,
|
||||
rc = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* IR 层优化 pass(默认关闭,--opt 开启)。 */
|
||||
if (opt)
|
||||
spl_ir_opt(&ir_builder.ir, &attr);
|
||||
|
||||
if (dump == DUMP_IR) {
|
||||
spl_ir_dump(&ir_builder.ir, &lang_type.ir, &attr);
|
||||
goto cleanup;
|
||||
@@ -334,7 +341,7 @@ static int compile_spl(const char *src, const char *fname, const char *outpath,
|
||||
/* 类型已全部就绪:解析 layout,供 ir2vm 的 size/align/offset 使用。 */
|
||||
spl_layout_resolve(&layout, &lang_type.ir);
|
||||
|
||||
spl_ir2vm_init(&ir2vm, &a2ir.ir->ir, &lang_type.ir, &layout, &attr);
|
||||
spl_ir2vm_init(&ir2vm, &a2ir.ir->ir, &lang_type.ir, &layout, &attr, opt);
|
||||
ir2vm_ok = 1;
|
||||
spl_diag_set_stage(&diag, SPL_STAGE_IR2VM);
|
||||
ir2vm.diag = &diag;
|
||||
@@ -348,6 +355,10 @@ static int compile_spl(const char *src, const char *fname, const char *outpath,
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* ir2vm 之后的字节码优化 pass(默认关闭,--opt 开启)。 */
|
||||
if (opt)
|
||||
spl_prog_opt(&ir2vm.prog);
|
||||
|
||||
spl_prog_store_to_file(outpath, &ir2vm.prog);
|
||||
rc = 0;
|
||||
|
||||
@@ -397,6 +408,11 @@ int main(int argc, char **argv) {
|
||||
LOG_INFO("splc0 --dump <flags> <file> dump: lex,ast,sema,ir,vm,none");
|
||||
return 0;
|
||||
}
|
||||
int opt = 0;
|
||||
for (int ai = 1; ai < argc; ai++)
|
||||
if (strcmp(argv[ai], "--opt") == 0)
|
||||
opt = 1;
|
||||
|
||||
int argi = 1;
|
||||
if (argi >= argc) {
|
||||
LOG_FATAL("Usage: splc0 [--dump <flags>] <in> [out]");
|
||||
@@ -435,19 +451,26 @@ int main(int argc, char **argv) {
|
||||
return 1;
|
||||
}
|
||||
int gen_debug = 0;
|
||||
while (argi < argc - 1) {
|
||||
if (strcmp(argv[argi], "-g") == 0) {
|
||||
gen_debug = 1;
|
||||
argi++;
|
||||
} else if (strcmp(argv[argi], "--opt") == 0) {
|
||||
opt = 1;
|
||||
argi++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (argc < argi + 2) {
|
||||
LOG_FATAL("Usage: splc0 <in> <out> [-g] or splc0 --dump <flags> <file>\n");
|
||||
LOG_FATAL("Usage: splc0 <in> <out> [-g] [--opt] or splc0 --dump <flags> <file>\n");
|
||||
return 1;
|
||||
}
|
||||
long len;
|
||||
char *src = read_file(argv[argi], &len);
|
||||
if (!src)
|
||||
return 1;
|
||||
int rc = compile_spl(src, argv[argi], argv[argi + 1], gen_debug, dump);
|
||||
int rc = compile_spl(src, argv[argi], argv[argi + 1], gen_debug, dump, opt);
|
||||
free(src);
|
||||
return rc;
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/* vm_opt.c - 字节码(VM 层)优化 pass
|
||||
*
|
||||
* 从 stage0/spl_mcode.c 迁出:对已生成的 spl_prog_t 做 prog->prog 优化。
|
||||
* 默认不启用(由 splc0 --opt 驱动)。 */
|
||||
|
||||
#include "vm_opt.h"
|
||||
|
||||
#include "../stage0/include/core_map.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/* 消除“store 后立刻 load 同一槽位”的往返。
|
||||
*
|
||||
* 模式(5 条):laddr X; swap; store k; laddr X; load k
|
||||
* 变换(3 条):laddr X; pick 1; store k
|
||||
* [.., v] -> laddr X => [.., v, X]
|
||||
* -> pick 1 => [.., v, X, v]
|
||||
* -> store k => [.., v] (存 v 到 X,同时栈上保留 v)
|
||||
* 与原来 store 后再 load 的效果等价,但去掉 2 条指令与一次内存读。
|
||||
*
|
||||
* 会真的删除指令,因此必须重定位:相对跳转、函数表 address、
|
||||
* 以及作为函数指针的 `push ptr <addr>`。仅当被删/被改写的指令不是
|
||||
* 跳转目标或函数入口时才变换(已收集 mark)。 */
|
||||
static usize peep_hash_int(usize k) { return MAP_HASH_INT(k); }
|
||||
static int peep_cmp_int(usize a, usize b) { return MAP_CMP_INT(a, b); }
|
||||
|
||||
void spl_prog_peephole(spl_prog_t *prog) {
|
||||
if (!prog)
|
||||
return;
|
||||
usize n = vec_size(prog->insns);
|
||||
if (n < 5)
|
||||
return;
|
||||
spl_vm_ins_t *ins = prog->insns.data;
|
||||
|
||||
unsigned char *mark = (unsigned char *)calloc(n, 1); /* 跳转目标/函数入口,受保护 */
|
||||
unsigned char *rem = (unsigned char *)calloc(n, 1);
|
||||
if (!mark || !rem) {
|
||||
free(mark);
|
||||
free(rem);
|
||||
return;
|
||||
}
|
||||
|
||||
for (usize i = 0; i < n; i++) {
|
||||
spl_vm_ins_t *x = &ins[i];
|
||||
if (x->opcode == SPL_JMP || x->opcode == SPL_BZ || x->opcode == SPL_BNZ) {
|
||||
isize t = (isize)i + 1 + (isize)x->imm;
|
||||
if (t >= 0 && (usize)t < n)
|
||||
mark[(usize)t] = 1;
|
||||
}
|
||||
}
|
||||
vec_for(prog->funcs, fi) {
|
||||
spl_vm_func_t *f = &vec_at(prog->funcs, fi);
|
||||
if (f->address < n)
|
||||
mark[f->address] = 1;
|
||||
}
|
||||
|
||||
for (usize i = 0; i + 4 < n; i++) {
|
||||
spl_vm_ins_t *a = &ins[i];
|
||||
spl_vm_ins_t *b = &ins[i + 1];
|
||||
spl_vm_ins_t *c = &ins[i + 2];
|
||||
spl_vm_ins_t *d = &ins[i + 3];
|
||||
spl_vm_ins_t *e = &ins[i + 4];
|
||||
if (a->opcode == SPL_LADDR && b->opcode == SPL_SWAP && c->opcode == SPL_STORE &&
|
||||
d->opcode == SPL_LADDR && e->opcode == SPL_LOAD && a->imm == d->imm &&
|
||||
c->type == e->type) {
|
||||
if (mark[i + 1] || mark[i + 3] || mark[i + 4])
|
||||
continue;
|
||||
b->opcode = SPL_PICK;
|
||||
b->type = SPL_VOID;
|
||||
b->imm = 1;
|
||||
rem[i + 3] = 1;
|
||||
rem[i + 4] = 1;
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
|
||||
usize kept = 0;
|
||||
for (usize i = 0; i < n; i++)
|
||||
if (!rem[i])
|
||||
kept++;
|
||||
if (kept == n) {
|
||||
free(mark);
|
||||
free(rem);
|
||||
return;
|
||||
}
|
||||
|
||||
spl_vm_ins_t *fresh = (spl_vm_ins_t *)malloc(sizeof(spl_vm_ins_t) * (kept ? kept : 1));
|
||||
usize *map = (usize *)malloc(sizeof(usize) * (n + 1)); /* old index -> new index */
|
||||
usize *old_of_new = (usize *)malloc(sizeof(usize) * (kept ? kept : 1));
|
||||
if (!fresh || !map || !old_of_new) {
|
||||
free(mark);
|
||||
free(rem);
|
||||
free(fresh);
|
||||
free(map);
|
||||
free(old_of_new);
|
||||
return;
|
||||
}
|
||||
|
||||
usize out = 0;
|
||||
for (usize i = 0; i < n; i++) {
|
||||
if (!rem[i]) {
|
||||
map[i] = out;
|
||||
old_of_new[out] = i;
|
||||
fresh[out] = ins[i];
|
||||
out++;
|
||||
}
|
||||
}
|
||||
map[n] = kept;
|
||||
for (usize i = n; i-- > 0;)
|
||||
if (rem[i])
|
||||
map[i] = map[i + 1];
|
||||
|
||||
/* 函数地址 old->new */
|
||||
MAP(usize, usize) addr_map;
|
||||
map_init(addr_map, peep_hash_int, peep_cmp_int);
|
||||
vec_for(prog->funcs, fi) {
|
||||
spl_vm_func_t *f = &vec_at(prog->funcs, fi);
|
||||
usize old_a = f->address;
|
||||
if (old_a < n) {
|
||||
usize new_a = map[old_a];
|
||||
map_put(addr_map, old_a, new_a);
|
||||
f->address = new_a;
|
||||
}
|
||||
}
|
||||
|
||||
/* 修正相对跳转与函数指针常量 */
|
||||
for (usize j = 0; j < kept; j++) {
|
||||
spl_vm_ins_t *x = &fresh[j];
|
||||
if (x->opcode == SPL_JMP || x->opcode == SPL_BZ || x->opcode == SPL_BNZ) {
|
||||
isize t = (isize)old_of_new[j] + 1 + (isize)x->imm;
|
||||
if (t >= 0 && (usize)t <= n) {
|
||||
usize nt = map[(usize)t];
|
||||
x->imm = (spl_vm_val_t)(isize)((isize)nt - (isize)(j + 1));
|
||||
}
|
||||
} else if (x->opcode == SPL_PUSH && x->type == SPL_PTR && x->imm != 0) {
|
||||
usize nv = 0;
|
||||
if (map_get(addr_map, (usize)x->imm, &nv))
|
||||
x->imm = nv;
|
||||
}
|
||||
}
|
||||
map_free(addr_map);
|
||||
|
||||
/* 指令删除后函数长度变化,重算 ninsns(函数按地址升序排列)。 */
|
||||
{
|
||||
usize nf = vec_size(prog->funcs);
|
||||
for (usize i = 0; i < nf; i++) {
|
||||
spl_vm_func_t *f = &vec_at(prog->funcs, i);
|
||||
usize next = (i + 1 < nf) ? vec_at(prog->funcs, i + 1).address : kept;
|
||||
f->ninsns = next > f->address ? (spl_vm_val_t)(next - f->address) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
free(prog->insns.data);
|
||||
prog->insns.data = fresh;
|
||||
prog->insns.size = kept;
|
||||
prog->insns.cap = kept;
|
||||
|
||||
free(mark);
|
||||
free(rem);
|
||||
free(map);
|
||||
free(old_of_new);
|
||||
}
|
||||
|
||||
/* 优化 pass 流水线(纯 prog->prog):当前 P1,后续可追加。 */
|
||||
void spl_prog_opt(spl_prog_t *prog) { spl_prog_peephole(prog); }
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef __VM_OPT_H__
|
||||
#define __VM_OPT_H__
|
||||
|
||||
/* 字节码(VM 层)优化 pass:输入输出都是 spl_prog_t,纯函数式、可开关。 */
|
||||
|
||||
#include "../stage0/spl_mcode.h"
|
||||
|
||||
void spl_prog_peephole(spl_prog_t *prog);
|
||||
void spl_prog_opt(spl_prog_t *prog);
|
||||
|
||||
#endif /* __VM_OPT_H__ */
|
||||
+8
-5
@@ -121,6 +121,8 @@ type Body = enum {
|
||||
PatternList: struct { items: Vec },
|
||||
PatternAggregate: struct { items: Vec },
|
||||
PatternField: struct { name: ByteView, pattern: usize },
|
||||
ExprComptime: struct { body: usize },
|
||||
ExprComptimeInit: struct { name: ByteView, args: Vec, fields: Vec },
|
||||
}
|
||||
|
||||
fn kind_name(body: Body): []u8 {
|
||||
@@ -143,6 +145,7 @@ fn kind_name(body: Body): []u8 {
|
||||
.StatementContinue(_) => { ret "statement_continue"; },
|
||||
.StatementDefer(_) => { ret "statement_defer"; },
|
||||
.StatementComptime(_) => { ret "statement_comptime"; },
|
||||
.ExprComptime(_) => { ret "expr_comptime"; },
|
||||
.StatementDecl(_) => { ret "statement_decl"; },
|
||||
.StatementExpr(_) => { ret "statement_expr"; },
|
||||
.AggregateMember(_) => { ret "aggregate_member"; },
|
||||
@@ -168,6 +171,7 @@ fn kind_name(body: Body): []u8 {
|
||||
.ExprArrayLit(_) => { ret "array_literal"; },
|
||||
.ExprAggregateLit(_) => { ret "aggregate_literal"; },
|
||||
.ExprComptimeCall(_) => { ret "comptime_call"; },
|
||||
.ExprComptimeInit(_) => { ret "comptime_init"; },
|
||||
.ExprBlock(_) => { ret "block"; },
|
||||
.ExprIf(_) => { ret "if_expr"; },
|
||||
.ExprIfvar(_) => { ret "ifvar_expr"; },
|
||||
@@ -272,11 +276,8 @@ fn drop(self: *Ast) {
|
||||
}
|
||||
|
||||
fn node(self: *Ast, ref: usize): *Node {
|
||||
match Vec.get(&self.nodes, ref) {
|
||||
.Ok(ptr) => { ret ptr as *Node; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
if ref >= self.nodes.len { ret null; }
|
||||
ret (self.nodes.data as *Node) + ref;
|
||||
}
|
||||
|
||||
// 追加节点,返回其引用(失败返回 0)。
|
||||
@@ -466,6 +467,7 @@ fn dump_node(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool):
|
||||
.StatementContinue(x) => { ok = dump_opt(ast, buf, stack, x.label, true); },
|
||||
.StatementDefer(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||
.StatementComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||
.ExprComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||
.StatementDecl(d) => { ok = dump_opt(ast, buf, stack, d.decl, true); },
|
||||
.StatementExpr(e) => { ok = dump_opt(ast, buf, stack, e.expr, true); },
|
||||
.AggregateMember(d) => {
|
||||
@@ -517,6 +519,7 @@ fn dump_node(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool):
|
||||
},
|
||||
.ExprAggregateLit(a) => { ok = dump_list(ast, buf, stack, a.fields); },
|
||||
.ExprComptimeCall(c) => { ok = dump_list(ast, buf, stack, c.args); },
|
||||
.ExprComptimeInit(x) => { ok = dump_list(ast, buf, stack, x.fields); },
|
||||
.ExprBlock(b) => {
|
||||
ok = dump_list(ast, buf, stack, b.stmts) &&
|
||||
dump_opt(ast, buf, stack, b.tail_expr, true);
|
||||
|
||||
+4
-4
@@ -6,7 +6,7 @@
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .Map } = @import("utils/map.spl");
|
||||
type .{ .Map, .hash_kind_usize } = @import("utils/map.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .hash_usize, .eq_usize } = @import("utils/hash.spl");
|
||||
@@ -54,8 +54,8 @@ 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;
|
||||
if i >= v.len { ret 0; }
|
||||
ret ((v.data as *usize) + i).*;
|
||||
}
|
||||
|
||||
fn push_u(v: *Vec, x: usize) {
|
||||
@@ -2069,7 +2069,7 @@ fn new(builder: *IrBuilder, sema: *Sema, diag: *Diag): Ast2Ir {
|
||||
.ir = builder,
|
||||
.ast = sema.ast,
|
||||
.def2ir_map = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize),
|
||||
hash_usize, eq_usize),
|
||||
hash_kind_usize()),
|
||||
.loop_stack = Vec.blank(@sizeof(LoopCtx), @alignof(LoopCtx)),
|
||||
.defer_stack = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.cur_ret_lang = 0,
|
||||
|
||||
+1
-5
@@ -132,11 +132,7 @@ fn drop(self: *Attr) {
|
||||
|
||||
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;
|
||||
ret (self.table.data as *AttrNode) + id;
|
||||
}
|
||||
|
||||
fn info_at(self: *Attr, index: usize): *AttrInfo {
|
||||
|
||||
+1
-5
@@ -87,11 +87,7 @@ fn drop(self: *DefTable) {
|
||||
// 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;
|
||||
ret (self.table.data as *DefNode) + id;
|
||||
}
|
||||
|
||||
fn count(self: *DefTable): usize {
|
||||
|
||||
+1
-4
@@ -161,10 +161,7 @@ fn table_count(): usize {
|
||||
|
||||
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 ByteView { .ptr = name.ptr + 1, .len = name.len - 1 };
|
||||
}
|
||||
ret name;
|
||||
}
|
||||
|
||||
+7
-31
@@ -167,26 +167,16 @@ fn drop_body(body: IrBody) {
|
||||
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 f = (self.funcs.data as *IrFunc) + i;
|
||||
var j: usize = 0;
|
||||
while j < f.nodes.len {
|
||||
match Vec.get(&f.nodes, j) {
|
||||
.Ok(np) => {
|
||||
var e = np as *IrEntry;
|
||||
var e = (f.nodes.data as *IrEntry) + j;
|
||||
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);
|
||||
@@ -207,36 +197,22 @@ fn alloc_fn(self: *Ir): usize {
|
||||
|
||||
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 f = (self.funcs.data as *IrFunc) + fn_id;
|
||||
var n = new_entry();
|
||||
match Vec.push(&f.nodes, &n as *u8) { .Ok => {}, .Err(error) => { ret 0; } }
|
||||
ret f.nodes.len - 1;
|
||||
},
|
||||
.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;
|
||||
ret (self.funcs.data as *IrFunc) + fn_id;
|
||||
}
|
||||
|
||||
fn node(self: *Ir, fn_id: usize, node_id: usize): *IrEntry {
|
||||
var f = func(self, fn_id);
|
||||
if f == null { ret null; }
|
||||
if node_id == 0 || node_id >= f.nodes.len { ret null; }
|
||||
match Vec.get(&f.nodes, node_id) {
|
||||
.Ok(ptr) => { ret ptr as *IrEntry; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
ret (f.nodes.data as *IrEntry) + node_id;
|
||||
}
|
||||
|
||||
fn kind_name(kind: IrKind): []u8 {
|
||||
@@ -345,8 +321,8 @@ fn dump_attrs(out: *ByteBuffer, attr: *Attr, ids: Vec): bool {
|
||||
}
|
||||
|
||||
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;
|
||||
if i >= v.len { ret 0; }
|
||||
ret ((v.data as *usize) + i).*;
|
||||
}
|
||||
|
||||
fn dump_body(out: *ByteBuffer, ty: *TypeTable, e: *IrEntry): bool {
|
||||
|
||||
+1304
File diff suppressed because it is too large.
Load diff
+12
-2
@@ -362,7 +362,7 @@ fn frame_build(fl: *FrameLayout, ctx: *Ir2Vm, fid: usize) {
|
||||
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) => {} }
|
||||
ignore_bool(Vec.push_raw(&fl.slots, &blank as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
@@ -421,7 +421,17 @@ 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));
|
||||
var prog = &cg.ctx.prog;
|
||||
var i = prog.insns.len;
|
||||
if i < prog.insns.cap {
|
||||
var p = (prog.insns.data as *VmIns) + i;
|
||||
p.opcode = op;
|
||||
p.ty = ty;
|
||||
p.imm = imm;
|
||||
prog.insns.len = i + 1;
|
||||
ret;
|
||||
}
|
||||
ignore_usize(Mcode.add_instr(prog, op, ty, imm));
|
||||
}
|
||||
|
||||
fn ip_of(cg: *Cgen): usize {
|
||||
|
||||
@@ -14,8 +14,8 @@ 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;
|
||||
if i >= v.len { ret 0; }
|
||||
ret ((v.data as *usize) + i).*;
|
||||
}
|
||||
|
||||
// ---- builder ----
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .Map } = @import("utils/map.spl");
|
||||
type .{ .Map, .hash_kind_usize } = @import("utils/map.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .append_usize } = @import("utils/fmt.spl");
|
||||
@@ -183,7 +183,7 @@ fn new(): LangTypeResult {
|
||||
.ir = ir,
|
||||
.lang_table = lang_table,
|
||||
.fold = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize),
|
||||
hash_usize, eq_usize),
|
||||
hash_kind_usize()),
|
||||
.defs = defs,
|
||||
.owned_names = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)),
|
||||
};
|
||||
@@ -218,11 +218,7 @@ fn drop(self: *LangType) {
|
||||
// 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;
|
||||
ret (self.lang_table.data as *LangEntry) + id;
|
||||
}
|
||||
|
||||
fn ir_type(self: *LangType, lang_id: usize): usize {
|
||||
|
||||
+1
-5
@@ -44,11 +44,7 @@ fn new(): LayoutResult {
|
||||
|
||||
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;
|
||||
ret (self.infos.data as *LayoutInfo) + id;
|
||||
}
|
||||
|
||||
// 未解析或越界返回 null(对应 stage1 spl_layout_info_of)。
|
||||
|
||||
+2
-5
@@ -92,11 +92,8 @@ fn has3(self: *Lexer, ch1: u8, ch2: u8): bool {
|
||||
}
|
||||
|
||||
fn sub(self: *Lexer, start: usize, end: usize): ByteView {
|
||||
match ByteView.slice(self.src, start, end) {
|
||||
.Ok(v) => { ret v; },
|
||||
.Err(error) => { ret ByteView.empty(); },
|
||||
}
|
||||
ret ByteView.empty();
|
||||
if start >= end { ret ByteView.empty(); }
|
||||
ret ByteView { .ptr = self.src.ptr + start, .len = end - start };
|
||||
}
|
||||
|
||||
// 产出 token 并把 pos/col 推进到 end。Vec.push 失败视为致命(同 vec.make_vec 风格)。
|
||||
|
||||
+71
-26
@@ -265,6 +265,15 @@ fn drop(self: *Prog) {
|
||||
// ---- 构建 ----
|
||||
|
||||
fn add_instr(self: *Prog, op: Op, ty: VmKind, imm: usize): usize {
|
||||
var i = self.insns.len;
|
||||
if i < self.insns.cap {
|
||||
var p = (self.insns.data as *VmIns) + i;
|
||||
p.opcode = op;
|
||||
p.ty = ty;
|
||||
p.imm = imm;
|
||||
self.insns.len = i + 1;
|
||||
ret i + 1;
|
||||
}
|
||||
var ins = VmIns { .opcode = op, .ty = ty, .imm = imm };
|
||||
if !Vec.push_raw(&self.insns, &ins as *u8) { ret 0; }
|
||||
ret self.insns.len;
|
||||
@@ -462,11 +471,8 @@ fn dump(self: *Prog, out: *ByteBuffer): bool {
|
||||
}
|
||||
|
||||
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 };
|
||||
if i >= self.insns.len { ret VmIns { .opcode = .Error, .ty = .Void, .imm = 0 }; }
|
||||
ret ((self.insns.data as *VmIns) + i).*;
|
||||
}
|
||||
|
||||
fn func_at(self: *Prog, i: usize): *VmFunc {
|
||||
@@ -491,41 +497,43 @@ fn align8(n: usize): usize {
|
||||
ret (n + 7) & (~(7 as usize));
|
||||
}
|
||||
|
||||
// 说明:以下 put_* 直接写入已预留容量的 ByteBuffer 存储(store 已按精确总大小 reserve),
|
||||
// 跳过 ByteBuffer.append → Vec.append 的校验/拷贝路径。若容量不足会越界,故 store 先校验容量。
|
||||
|
||||
fn put_u8(out: *ByteBuffer, b: u8): bool {
|
||||
match ByteBuffer.append_byte(out, b) { .Ok => { ret true; }, .Err(error) => { ret false; } }
|
||||
ret false;
|
||||
out.storage.data[out.storage.len] = b;
|
||||
out.storage.len = out.storage.len + 1;
|
||||
ret true;
|
||||
}
|
||||
|
||||
// 直接按小端整型写(x86 小端;VM 的 STORE 用 memcpy,未对齐也安全)。
|
||||
fn put_u16(out: *ByteBuffer, v: usize): bool {
|
||||
if !put_u8(out, (v & 255) as u8) { ret false; }
|
||||
ret put_u8(out, ((v >> 8) & 255) as u8);
|
||||
var d = out.storage.data + out.storage.len;
|
||||
(d as *u16).* = v as u16;
|
||||
out.storage.len = out.storage.len + 2;
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn put_u64(out: *ByteBuffer, v: usize): bool {
|
||||
var i: usize = 0;
|
||||
while i < 8 {
|
||||
if !put_u8(out, ((v >> (i * 8)) & 255) as u8) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
var d = out.storage.data + out.storage.len;
|
||||
(d as *u64).* = v as u64;
|
||||
out.storage.len = out.storage.len + 8;
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn put_zeros(out: *ByteBuffer, n: usize): bool {
|
||||
var i: usize = 0;
|
||||
while i < n {
|
||||
if !put_u8(out, 0) { ret false; }
|
||||
i = i + 1;
|
||||
if n != 0 {
|
||||
ignore_ptr(Mem.set((out.storage.data + out.storage.len) as *any, 0, n));
|
||||
out.storage.len = out.storage.len + n;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn put_view(out: *ByteBuffer, v: ByteView): bool {
|
||||
if v.len == 0 { ret true; }
|
||||
match ByteBuffer.append(out, ByteView.as_slice(v)) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
Mem.copy((out.storage.data + out.storage.len) as *any, v.ptr as *any, v.len);
|
||||
out.storage.len = out.storage.len + v.len;
|
||||
ret true;
|
||||
}
|
||||
|
||||
// 写入「带结尾 NUL、再按 8 对齐」的名称字段(对应 C 侧 nlen = strlen+1)。
|
||||
@@ -541,6 +549,41 @@ fn store(self: *Prog, path: ByteView): Status {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
|
||||
// 精确预估输出大小并一次性预留,避免大文件逐字节增长导致的重复拷贝。
|
||||
var total: usize = 48;
|
||||
var fi: usize = 0;
|
||||
while fi < self.funcs.len {
|
||||
var f = func_at(self, fi);
|
||||
if f != null {
|
||||
var nlen = f.name.len + 1;
|
||||
total = total + 8 + align8(nlen) + 32;
|
||||
}
|
||||
fi = fi + 1;
|
||||
}
|
||||
total = total + self.insns.len * 12;
|
||||
fi = 0;
|
||||
while fi < self.natives.len {
|
||||
var n = native_at(self, fi);
|
||||
if n != null {
|
||||
var nlen = n.name.len + 1;
|
||||
total = total + 8 + align8(nlen) + 8;
|
||||
}
|
||||
fi = fi + 1;
|
||||
}
|
||||
fi = 0;
|
||||
while fi < self.strtab.len {
|
||||
total = total + 8 + align8(str_at(self, fi).len);
|
||||
fi = fi + 1;
|
||||
}
|
||||
fi = 1;
|
||||
while fi < self.gdata.len {
|
||||
var e = gdata_at(self, fi);
|
||||
if e != null { total = total + 8 + align8(e.storage.len); }
|
||||
fi = fi + 1;
|
||||
}
|
||||
match ByteBuffer.reserve(&out, total) { .Ok => {}, .Err(error) => {} }
|
||||
if out.storage.cap < total || out.storage.data == null { ret .Err(make(.Io)); }
|
||||
|
||||
if !put_view(&out, ByteView.from_slice("SPLBIN")) { ret .Err(make(.Io)); }
|
||||
if !put_zeros(&out, 2) { ret .Err(make(.Io)); }
|
||||
|
||||
@@ -572,9 +615,11 @@ fn store(self: *Prog, path: ByteView): Status {
|
||||
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)); }
|
||||
var d = out.storage.data + out.storage.len;
|
||||
(d as *u16).* = @enum2int(ins.opcode) as u16;
|
||||
((d + 2) as *u16).* = @enum2int(ins.ty) as u16;
|
||||
((d + 4) as *u64).* = ins.imm as u64;
|
||||
out.storage.len = out.storage.len + 12;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
|
||||
+186
-16
@@ -25,6 +25,7 @@ failed: bool,
|
||||
stop_agg: i32,
|
||||
error_count: usize,
|
||||
file: usize, // 当前源文件在 Sources 中的下标
|
||||
comptime_depth: usize, // >0 表示处于 comptime 上下文(类型可作为表达式)
|
||||
|
||||
fn new(ast: *Ast, toks: Vec, diag: *Diag): Parser {
|
||||
ret Parser {
|
||||
@@ -36,17 +37,15 @@ fn new(ast: *Ast, toks: Vec, diag: *Diag): Parser {
|
||||
.stop_agg = 0,
|
||||
.error_count = 0,
|
||||
.file = 0,
|
||||
.comptime_depth = 0,
|
||||
};
|
||||
}
|
||||
|
||||
// ---- token 访问 ----
|
||||
|
||||
fn token_ptr(self: *Parser, index: usize): *Token {
|
||||
match Vec.get(&self.toks, index) {
|
||||
.Ok(ptr) => { ret ptr as *Token; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
if index >= self.toks.len { ret null; }
|
||||
ret (self.toks.data as *Token) + index;
|
||||
}
|
||||
|
||||
fn is_trivia(kind: TokenKind): bool {
|
||||
@@ -61,16 +60,32 @@ fn is_trivia(kind: TokenKind): bool {
|
||||
// 跳过 trivia;返回当前 token(含 EOF)。越界返回 null。
|
||||
fn peek(self: *Parser): *Token {
|
||||
var n = self.toks.len;
|
||||
var base = self.toks.data as *Token;
|
||||
var i = self.pos;
|
||||
while i < n {
|
||||
var t = token_ptr(self, i);
|
||||
if t == null { break; }
|
||||
if base == null { break; }
|
||||
var t = base + i;
|
||||
if !is_trivia(t.kind) { break; }
|
||||
i = i + 1;
|
||||
}
|
||||
self.pos = i;
|
||||
if i >= n { ret null; }
|
||||
ret token_ptr(self, i);
|
||||
if i >= n || base == null { ret null; }
|
||||
ret base + i;
|
||||
}
|
||||
|
||||
// 跳过 trivia,返回当前 token 之后的下一个非 trivia token(不移动 pos)。
|
||||
fn peek_next(self: *Parser): *Token {
|
||||
var n = self.toks.len;
|
||||
var base = self.toks.data as *Token;
|
||||
var i = self.pos + 1;
|
||||
while i < n {
|
||||
if base == null { break; }
|
||||
var t = base + i;
|
||||
if !is_trivia(t.kind) { break; }
|
||||
i = i + 1;
|
||||
}
|
||||
if i >= n || base == null { ret null; }
|
||||
ret base + i;
|
||||
}
|
||||
|
||||
fn advance(self: *Parser): *Token {
|
||||
@@ -266,6 +281,7 @@ fn can_start_expr(kind: TokenKind): bool {
|
||||
.Loop => { ret true; },
|
||||
.For => { ret true; },
|
||||
.Match => { ret true; },
|
||||
.Comptime => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
@@ -657,7 +673,33 @@ fn parse_param_list(self: *Parser): Vec {
|
||||
ret out;
|
||||
}
|
||||
|
||||
fn parse_fn_decl(self: *Parser, tags: Vec): usize {
|
||||
// 该 token 是否可能开始一个类型表达式(用于省略冒号的返回类型)。
|
||||
fn token_starts_type(t: *Token): bool {
|
||||
if t == null { ret false; }
|
||||
match t.kind {
|
||||
.Ident => { ret true; },
|
||||
.Keyword(k) => {
|
||||
if @enum2int(k) == @enum2int(Keyword.Type) { ret true; }
|
||||
if @enum2int(k) == @enum2int(Keyword.Bool) { ret true; }
|
||||
if @enum2int(k) == @enum2int(Keyword.Any) { ret true; }
|
||||
if @enum2int(k) == @enum2int(Keyword.Fn) { ret true; }
|
||||
if @enum2int(k) == @enum2int(Keyword.Struct) { ret true; }
|
||||
if @enum2int(k) == @enum2int(Keyword.Union) { ret true; }
|
||||
if @enum2int(k) == @enum2int(Keyword.Enum) { ret true; }
|
||||
if @enum2int(k) == @enum2int(Keyword.Shape) { ret true; }
|
||||
},
|
||||
.Punct(p) => {
|
||||
if @enum2int(p) == @enum2int(Punct.Mul) { ret true; }
|
||||
if @enum2int(p) == @enum2int(Punct.LBracket) { ret true; }
|
||||
if @enum2int(p) == @enum2int(Punct.At) { ret true; }
|
||||
if @enum2int(p) == @enum2int(Punct.Sharp) { ret true; }
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn parse_fn_decl(self: *Parser, tags: Vec, is_comptime: bool): usize {
|
||||
var kw = peek(self);
|
||||
if kw == null || !kind_is_keyword(kw.kind, .Fn) { ret 0; }
|
||||
advance(self);
|
||||
@@ -674,19 +716,26 @@ fn parse_fn_decl(self: *Parser, tags: Vec): usize {
|
||||
advance(self);
|
||||
ret_type = parse_type_expr(self);
|
||||
t = peek(self);
|
||||
} else if token_starts_type(t) {
|
||||
// 省略冒号:`fn f() T { ... }`
|
||||
ret_type = parse_type_expr(self);
|
||||
t = peek(self);
|
||||
}
|
||||
var body: usize = 0;
|
||||
if is_comptime { self.comptime_depth = self.comptime_depth + 1; }
|
||||
if t != null && kind_is_punct(t.kind, .LBrace) {
|
||||
body = parse_block(self);
|
||||
} else if t != null && kind_is_punct(t.kind, .Semicolon) {
|
||||
advance(self);
|
||||
} else {
|
||||
if is_comptime { self.comptime_depth = self.comptime_depth - 1; }
|
||||
parse_error(self, t, "function declaration must end with ';' or block");
|
||||
ret 0;
|
||||
}
|
||||
if is_comptime { self.comptime_depth = self.comptime_depth - 1; }
|
||||
ret new_node(self, .FnDecl(.{
|
||||
.tag_list = tags,
|
||||
.is_comptime = false,
|
||||
.is_comptime = is_comptime,
|
||||
.name = name.text,
|
||||
.params = params,
|
||||
.ret_type = ret_type,
|
||||
@@ -804,8 +853,10 @@ fn parse_comptime_decl(self: *Parser): usize {
|
||||
advance(self);
|
||||
var body: usize = 0;
|
||||
var t = peek(self);
|
||||
self.comptime_depth = self.comptime_depth + 1;
|
||||
if t != null && kind_is_punct(t.kind, .LBrace) { body = parse_block(self); }
|
||||
else { body = parse_expr(self); }
|
||||
self.comptime_depth = self.comptime_depth - 1;
|
||||
ret new_node(self, .ComptimeDecl(.{ .body = body }), kw);
|
||||
}
|
||||
|
||||
@@ -817,7 +868,7 @@ fn parse_container_declaration(self: *Parser): usize {
|
||||
match t.kind {
|
||||
.Eof => { ret 0; },
|
||||
.Keyword(k) => {
|
||||
if @enum2int(k) == @enum2int(Keyword.Fn) { ret parse_fn_decl(self, tags); }
|
||||
if @enum2int(k) == @enum2int(Keyword.Fn) { ret parse_fn_decl(self, tags, false); }
|
||||
if @enum2int(k) == @enum2int(Keyword.Type) { ret parse_type_decl(self, tags); }
|
||||
if @enum2int(k) == @enum2int(Keyword.Var) {
|
||||
ret parse_var_const_decl(self, tags, false);
|
||||
@@ -825,7 +876,15 @@ fn parse_container_declaration(self: *Parser): usize {
|
||||
if @enum2int(k) == @enum2int(Keyword.Const) {
|
||||
ret parse_var_const_decl(self, tags, true);
|
||||
}
|
||||
if @enum2int(k) == @enum2int(Keyword.Comptime) { ret parse_comptime_decl(self); }
|
||||
if @enum2int(k) == @enum2int(Keyword.Comptime) {
|
||||
// `comptime fn ...` -> 标记为编译期函数;否则 `comptime { }` 声明。
|
||||
var nxt = peek_next(self);
|
||||
if nxt != null && kind_is_keyword(nxt.kind, .Fn) {
|
||||
advance(self);
|
||||
ret parse_fn_decl(self, tags, true);
|
||||
}
|
||||
ret parse_comptime_decl(self);
|
||||
}
|
||||
},
|
||||
.Ident => { ret parse_member_decl(self, tags); },
|
||||
.Punct(p) => {
|
||||
@@ -953,15 +1012,41 @@ fn parse_type_base(self: *Parser): usize {
|
||||
match bt.kind {
|
||||
.Ident => {
|
||||
advance(self);
|
||||
// 类型调用 `Name(T, ...)`(comptime 类型构造,如 `vec(i32)`)。
|
||||
if at_punct(self, .LParen) {
|
||||
advance(self);
|
||||
var ref = new_node(self, .TypePath(.{
|
||||
.name = bt.text,
|
||||
.generic_args = ref_vec(),
|
||||
}), bt);
|
||||
var args = ref_vec();
|
||||
while !self.failed && !at_punct(self, .RParen) {
|
||||
var a = parse_type_expr(self);
|
||||
match Vec.push(&args, &a as *u8) { .Ok => {}, .Err(e) => { ret 0; } }
|
||||
if at_punct(self, .Comma) { advance(self); continue; }
|
||||
break;
|
||||
}
|
||||
expect_punct(self, .RParen, "')' in type call");
|
||||
var tn = Ast.node(self.ast, ref);
|
||||
match tn.body {
|
||||
.TypePath(tp) => {
|
||||
tp.generic_args = args;
|
||||
tn.body = .TypePath(tp);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
ret ref;
|
||||
}
|
||||
ret new_node(self, .TypePath(.{
|
||||
.name = bt.text,
|
||||
.generic_args = ref_vec(),
|
||||
}), bt);
|
||||
},
|
||||
.Keyword(k) => {
|
||||
// stage1 is_builtin_type_kw:bool / any 作为类型路径。
|
||||
// stage1 is_builtin_type_kw:bool / any 作为类型路径;stage3 增加 `type`。
|
||||
if @enum2int(k) == @enum2int(Keyword.Bool) ||
|
||||
@enum2int(k) == @enum2int(Keyword.Any) {
|
||||
@enum2int(k) == @enum2int(Keyword.Any) ||
|
||||
@enum2int(k) == @enum2int(Keyword.Type) {
|
||||
advance(self);
|
||||
ret new_node(self, .TypePath(.{
|
||||
.name = bt.text,
|
||||
@@ -1359,6 +1444,21 @@ fn parse_primary(self: *Parser): usize {
|
||||
if @enum2int(k) == @enum2int(Keyword.Loop) { ret parse_loop_expr(self); }
|
||||
if @enum2int(k) == @enum2int(Keyword.For) { ret parse_for_expr(self); }
|
||||
if @enum2int(k) == @enum2int(Keyword.Match) { ret parse_match_expr(self); }
|
||||
// `comptime <expr>`:编译期表达式(可调用普通函数、返回类型等)。
|
||||
if @enum2int(k) == @enum2int(Keyword.Comptime) {
|
||||
advance(self);
|
||||
var cbody = parse_expr(self);
|
||||
ret new_node(self, .ExprComptime(.{ .body = cbody }), t);
|
||||
}
|
||||
// 类型即表达式:仅在 comptime 上下文内允许 `struct { ... }` 等聚合类型。
|
||||
if self.comptime_depth > 0 {
|
||||
if @enum2int(k) == @enum2int(Keyword.Struct) ||
|
||||
@enum2int(k) == @enum2int(Keyword.Union) ||
|
||||
@enum2int(k) == @enum2int(Keyword.Enum) ||
|
||||
@enum2int(k) == @enum2int(Keyword.Shape) {
|
||||
ret parse_type_base(self);
|
||||
}
|
||||
}
|
||||
parse_error(self, t, "unrecognized expression");
|
||||
ret 0;
|
||||
},
|
||||
@@ -1555,6 +1655,15 @@ fn parse_comptime_call(self: *Parser): usize {
|
||||
args = parse_expr_list(self);
|
||||
if !expect_punct(self, .RParen, "')' in comptime call") { ret 0; }
|
||||
}
|
||||
// `@name(args){ ... }`:构造该编译期类型的值(聚合初始化)。
|
||||
if at_punct(self, .LBrace) && self.stop_agg == 0 {
|
||||
var fields = parse_aggregate_fields(self);
|
||||
ret new_node(self, .ExprComptimeInit(.{
|
||||
.name = name.text,
|
||||
.args = args,
|
||||
.fields = fields,
|
||||
}), start);
|
||||
}
|
||||
ret new_node(self, .ExprComptimeCall(.{ .name = name.text, .args = args }), start);
|
||||
}
|
||||
|
||||
@@ -1793,7 +1902,18 @@ fn parse_ret_stmt(self: *Parser): usize {
|
||||
advance(self);
|
||||
var expr: usize = 0;
|
||||
var t = peek(self);
|
||||
if t != null && can_start_expr(t.kind) { expr = parse_expr(self); }
|
||||
var can = false;
|
||||
if t != null {
|
||||
can = can_start_expr(t.kind);
|
||||
// comptime 上下文:允许返回类型表达式(如 `ret struct { ... };`)。
|
||||
if !can && self.comptime_depth > 0 {
|
||||
if kind_is_keyword(t.kind, .Struct) || kind_is_keyword(t.kind, .Union) ||
|
||||
kind_is_keyword(t.kind, .Enum) || kind_is_keyword(t.kind, .Shape) {
|
||||
can = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if can { expr = parse_expr(self); }
|
||||
t = peek(self);
|
||||
if t != null && kind_is_punct(t.kind, .Semicolon) { advance(self); }
|
||||
ret new_node(self, .StatementRet(.{ .expr = expr }), kw);
|
||||
@@ -2225,10 +2345,60 @@ fn test_big_int_literal() {
|
||||
check_int("0o_17", "15");
|
||||
}
|
||||
|
||||
fn expect_contains(buf: *ByteBuffer, needle: []u8) {
|
||||
var actual = ByteBuffer.as_slice(buf.*);
|
||||
if needle.len > actual.len { @assert(false, "contains length"); }
|
||||
var i: usize = 0;
|
||||
while i + needle.len <= actual.len {
|
||||
var j: usize = 0;
|
||||
var ok = true;
|
||||
while j < needle.len {
|
||||
if actual.ptr[i + j] != needle.ptr[j] { ok = false; break; }
|
||||
j = j + 1;
|
||||
}
|
||||
if ok { ret; }
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(false, "expected substring not found");
|
||||
}
|
||||
|
||||
// stage3 语法:`comptime fn ... (t: type) type { ... }` 与省略冒号的返回类型。
|
||||
fn test_parse_comptime_fn() {
|
||||
var src: []u8 = "comptime fn vec(t: type) type { ret t; }\n";
|
||||
var toks = lex_tokens(src);
|
||||
defer { Vec.drop(&toks); }
|
||||
|
||||
var sources = set_new();
|
||||
defer { set_drop(&sources); }
|
||||
match set_add(&sources, ByteView.from_slice("test.spl"), ByteView.from_slice(src)) {
|
||||
.Ok(index) => {},
|
||||
.Err(error) => { @assert(false, "set_add"); },
|
||||
}
|
||||
|
||||
var ast: Ast = undefined;
|
||||
match Ast.new(&sources) {
|
||||
.Ok(v) => { ast = v; },
|
||||
.Err(error) => { @assert(false, "ast new"); },
|
||||
}
|
||||
defer { Ast.drop(&ast); }
|
||||
|
||||
var p = new(&ast, toks, null);
|
||||
parse(&p);
|
||||
@assert(!p.failed, "comptime fn parse ok");
|
||||
@assert(p.error_count == 0, "comptime fn no errors");
|
||||
|
||||
var buf = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&buf); }
|
||||
@assert(Ast.dump(&ast, &buf), "dump");
|
||||
expect_contains(&buf, "fn_decl");
|
||||
expect_contains(&buf, "type_path");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_peek_advance();
|
||||
test_new_node_span();
|
||||
test_parse_decls();
|
||||
test_parse_main();
|
||||
test_big_int_literal();
|
||||
test_parse_comptime_fn();
|
||||
}
|
||||
+3
-7
@@ -7,7 +7,7 @@
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .Map } = @import("utils/map.spl");
|
||||
type .{ .Map, .hash_kind_view } = @import("utils/map.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .hash_view, .eq_view } = @import("utils/hash.spl");
|
||||
|
||||
@@ -43,7 +43,7 @@ type ScopeResult = enum {
|
||||
|
||||
fn new_map(): Map {
|
||||
ret Map.new(@sizeof(ByteView), @alignof(ByteView), @sizeof(Symbol), @alignof(Symbol),
|
||||
hash_view, eq_view);
|
||||
hash_kind_view());
|
||||
}
|
||||
|
||||
fn new(): ScopeResult {
|
||||
@@ -81,11 +81,7 @@ fn drop(self: *Scope) {
|
||||
// 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;
|
||||
ret (self.scopes.data as *ScopeNode) + id;
|
||||
}
|
||||
|
||||
// 新作用域 parent = 当前作用域;不自动切换 current(调用方负责,见 sema)。
|
||||
|
||||
+4
-7
@@ -9,7 +9,7 @@
|
||||
|
||||
type .{ .Error, .Status } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .Map } = @import("utils/map.spl");
|
||||
type .{ .Map, .hash_kind_usize } = @import("utils/map.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .append_usize } = @import("utils/fmt.spl");
|
||||
@@ -69,11 +69,8 @@ type SemaResult = enum {
|
||||
// ---- 小工具 ----
|
||||
|
||||
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;
|
||||
if i >= v.len { ret 0; }
|
||||
ret ((v.data as *usize) + i).*;
|
||||
}
|
||||
|
||||
fn push_u(v: *Vec, x: usize) {
|
||||
@@ -234,7 +231,7 @@ fn new(ast: *Ast, diag: *Diag): SemaResult {
|
||||
.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),
|
||||
hash_kind_usize()),
|
||||
};
|
||||
ret .Ok(s);
|
||||
}
|
||||
|
||||
+35
-2
@@ -13,7 +13,8 @@ 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 .{ .read_all, .print, .eprint, .open_cstr, .write_all } = @import("./utils/io.spl");
|
||||
type File = @import("./sys/file.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");
|
||||
@@ -25,6 +26,7 @@ type LayoutM = @import("./layout.spl");
|
||||
type .{ .Layout } = @import("./layout.spl");
|
||||
type Ir2VmM = @import("./ir2vm.spl");
|
||||
type .{ .Ir2Vm } = @import("./ir2vm.spl");
|
||||
type Ir2CM = @import("./ir2c.spl");
|
||||
type .{ .alloc, .free } = @import("./sys/mem.spl");
|
||||
|
||||
fn token_at(tokens: *Vec, index: usize): Token {
|
||||
@@ -566,6 +568,7 @@ type PipeMode = enum {
|
||||
Vm, // 继续 layout + ir2vm,dump VM
|
||||
Compile, // 继续 layout + ir2vm,写 outpath
|
||||
State, // 只逐步打日志(诊断“卡在哪一步”)
|
||||
C, // 继续 layout + ir2c,dump C
|
||||
}
|
||||
|
||||
fn step(mode: PipeMode, s: []u8) {
|
||||
@@ -723,6 +726,31 @@ fn run_pipeline(log: *Logger, path: ByteView, mode: PipeMode, outpath: ByteView)
|
||||
LayoutM.resolve(&layout, &sema.types.ir);
|
||||
step(mode, "state: layout ok\n");
|
||||
|
||||
if @enum2int(mode) == @enum2int(PipeMode.C) {
|
||||
var cout = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&cout); }
|
||||
var ok = Ir2CM.compile(&builder.ir, &sema.types.ir, &layout, &sema.attr, &cout);
|
||||
if outpath.len != 0 {
|
||||
var f = open_cstr(outpath, ByteView.from_slice("wb"));
|
||||
if !File.valid(&f) {
|
||||
Logger.error(log, "cannot open C output file");
|
||||
ret 1;
|
||||
}
|
||||
match write_all(&f, ByteBuffer.as_slice(cout)) { .Ok => {}, .Err(error) => {} }
|
||||
ignore_i32(File.close(&f));
|
||||
} else {
|
||||
match print(ByteBuffer.as_slice(cout)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 1; },
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
Logger.error(log, "ir2c failed (see generated C for unsupported markers)");
|
||||
ret 1;
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
var ir2vm: Ir2Vm = undefined;
|
||||
Ir2VmM.init(&ir2vm, &builder.ir, &sema.types.ir, &layout, &sema.attr);
|
||||
defer { Ir2VmM.drop(&ir2vm); }
|
||||
@@ -776,11 +804,13 @@ fn main(argc: i32, argv: **u8): i32 {
|
||||
}
|
||||
|
||||
if argc < 4 {
|
||||
Logger.fatal(&log, "usage: splc1 --dump <lex|ast|sema|ir|vm> <file> [out]");
|
||||
Logger.fatal(&log, "usage: splc1 --dump <lex|ast|sema|ir|vm|c> <file> [out]");
|
||||
ret 1;
|
||||
}
|
||||
var mode = ByteView.from_cstr(argv[2]);
|
||||
var path = ByteView.from_cstr(argv[3]);
|
||||
var dout = ByteView.empty();
|
||||
if argc >= 5 { dout = ByteView.from_cstr(argv[4]); }
|
||||
|
||||
if ByteView.eq(mode, ByteView.from_slice("lex")) {
|
||||
ret dump_lex(&log, path);
|
||||
@@ -800,6 +830,9 @@ fn main(argc: i32, argv: **u8): i32 {
|
||||
if ByteView.eq(mode, ByteView.from_slice("state")) {
|
||||
ret run_pipeline(&log, path, .State, ByteView.empty());
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("c")) {
|
||||
ret run_pipeline(&log, path, .C, dout);
|
||||
}
|
||||
Logger.fatal(&log, "dump mode not implemented");
|
||||
ret 1;
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
/* spl_rt.h - SPL native runtime (header-only, C89-friendly)
|
||||
*
|
||||
* 提供 ir2c 生成代码所需的所有 `#extern(vm)` 符号,以及 SPL 基础类型别名。
|
||||
* 目标:生成代码可用 C89 语法通过(不依赖 stdint/stdbool)。
|
||||
*/
|
||||
|
||||
#ifndef __SPL_RT_H__
|
||||
#define __SPL_RT_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
/* 语义清晰的 inline:C99 用 static inline,GNU 用 __inline__,C89 退化为 static */
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
|
||||
#define SPL_INLINE static inline
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#define SPL_INLINE static __inline__
|
||||
#else
|
||||
#define SPL_INLINE static
|
||||
#endif
|
||||
|
||||
/* ---- SPL 基础类型别名 ---- */
|
||||
typedef unsigned char u8;
|
||||
typedef signed char i8;
|
||||
typedef unsigned short u16;
|
||||
typedef signed short i16;
|
||||
typedef unsigned int u32;
|
||||
typedef signed int i32;
|
||||
typedef unsigned long long u64;
|
||||
typedef signed long long i64;
|
||||
typedef u64 usize;
|
||||
typedef i64 isize;
|
||||
/* 注:不定义 bool/true/false,避免与 C23 关键字冲突;生成代码用 u8 表示布尔。 */
|
||||
|
||||
/* ---- panic / trap ---- */
|
||||
SPL_INLINE void spl_panic(const char *msg) {
|
||||
fprintf(stderr, "%s\n", msg ? msg : "panic");
|
||||
exit(1);
|
||||
}
|
||||
SPL_INLINE void spl_trap(void) { abort(); }
|
||||
|
||||
/* ---- mem ---- */
|
||||
SPL_INLINE void *vm_alloc(u64 sz) {
|
||||
if (sz == 0)
|
||||
return NULL;
|
||||
return malloc((size_t)sz);
|
||||
}
|
||||
SPL_INLINE void vm_free(void *p) {
|
||||
if (p)
|
||||
free(p);
|
||||
}
|
||||
SPL_INLINE void *vm_realloc(void *p, u64 sz) {
|
||||
if (!p)
|
||||
return vm_alloc(sz);
|
||||
if (sz == 0) {
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
return realloc(p, (size_t)sz);
|
||||
}
|
||||
SPL_INLINE void *vm_memcpy(void *dst, const void *src, u64 n) {
|
||||
if (n)
|
||||
memcpy(dst, src, (size_t)n);
|
||||
return dst;
|
||||
}
|
||||
SPL_INLINE void *vm_memmove(void *dst, const void *src, u64 n) {
|
||||
if (n)
|
||||
memmove(dst, src, (size_t)n);
|
||||
return dst;
|
||||
}
|
||||
SPL_INLINE i64 vm_memcmp(const void *a, const void *b, u64 n) {
|
||||
if (n == 0)
|
||||
return 0;
|
||||
return (i64)memcmp(a, b, (size_t)n);
|
||||
}
|
||||
SPL_INLINE i32 vm_bytes_eq(const void *a, u64 alen, const void *b, u64 blen) {
|
||||
if (alen != blen)
|
||||
return 0;
|
||||
if (alen == 0)
|
||||
return 1;
|
||||
if (!a || !b)
|
||||
return 0;
|
||||
return memcmp(a, b, (size_t)alen) == 0;
|
||||
}
|
||||
|
||||
/* ---- Vec ---- */
|
||||
typedef struct {
|
||||
usize raw;
|
||||
usize data;
|
||||
usize len;
|
||||
usize cap;
|
||||
usize elem_size;
|
||||
usize elem_align;
|
||||
} spl_vec_t;
|
||||
|
||||
SPL_INLINE int spl_vec_is_valid(const spl_vec_t *v) {
|
||||
usize a;
|
||||
if (v->elem_size == 0)
|
||||
return 0;
|
||||
a = v->elem_align;
|
||||
if (a == 0 || (a & (a - 1)) != 0)
|
||||
return 0;
|
||||
if (v->len > v->cap)
|
||||
return 0;
|
||||
if (v->cap == 0)
|
||||
return v->raw == 0 && v->data == 0 && v->len == 0;
|
||||
if (v->raw == 0 || v->data == 0)
|
||||
return 0;
|
||||
return (v->data & (a - 1)) == 0;
|
||||
}
|
||||
|
||||
SPL_INLINE int spl_align_ok(usize a) { return a != 0 && (a & (a - 1)) == 0; }
|
||||
|
||||
SPL_INLINE void spl_grow(usize current, usize need, usize *out) {
|
||||
usize cap = current < 8 ? 8 : current;
|
||||
usize limit = ~(usize)0;
|
||||
while (cap < need) {
|
||||
if (cap > limit / 2) {
|
||||
cap = need;
|
||||
break;
|
||||
}
|
||||
cap = cap * 2;
|
||||
}
|
||||
*out = cap;
|
||||
}
|
||||
|
||||
SPL_INLINE int spl_vec_allocate(spl_vec_t *out, usize capacity, usize elem_size, usize elem_align) {
|
||||
usize bytes, mask, addr;
|
||||
void *raw;
|
||||
if (elem_size == 0)
|
||||
return 1;
|
||||
if (!spl_align_ok(elem_align))
|
||||
return 4;
|
||||
out->raw = 0;
|
||||
out->data = 0;
|
||||
out->len = 0;
|
||||
out->cap = 0;
|
||||
out->elem_size = elem_size;
|
||||
out->elem_align = elem_align;
|
||||
if (capacity == 0)
|
||||
return 0;
|
||||
if (capacity > ~(usize)0 / elem_size)
|
||||
return 3;
|
||||
bytes = capacity * elem_size;
|
||||
mask = elem_align - 1;
|
||||
if (bytes > ~(usize)0 - mask)
|
||||
return 3;
|
||||
raw = malloc(bytes + mask);
|
||||
if (!raw)
|
||||
return 3;
|
||||
addr = (usize)raw;
|
||||
if (addr > ~(usize)0 - mask) {
|
||||
free(raw);
|
||||
return 3;
|
||||
}
|
||||
out->raw = addr;
|
||||
out->data = (addr + mask) & ~mask;
|
||||
out->cap = capacity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SPL_INLINE int spl_vec_reserve_raw(spl_vec_t *v, usize need) {
|
||||
usize new_cap = 0, used = 0, old;
|
||||
spl_vec_t fresh;
|
||||
int rc;
|
||||
if (need <= v->cap)
|
||||
return 0;
|
||||
spl_grow(v->cap, need, &new_cap);
|
||||
rc = spl_vec_allocate(&fresh, new_cap, v->elem_size, v->elem_align);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (v->len != 0) {
|
||||
if (v->len > ~(usize)0 / v->elem_size) {
|
||||
free((void *)fresh.raw);
|
||||
return 3;
|
||||
}
|
||||
used = v->len * v->elem_size;
|
||||
}
|
||||
if (used)
|
||||
memcpy((void *)fresh.data, (void *)v->data, used);
|
||||
fresh.len = v->len;
|
||||
old = v->raw;
|
||||
*v = fresh;
|
||||
if (old)
|
||||
free((void *)old);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SPL_INLINE void spl_vec_release(spl_vec_t *v) {
|
||||
usize es = v->elem_size, ea = v->elem_align;
|
||||
if (v->raw)
|
||||
free((void *)v->raw);
|
||||
v->raw = 0;
|
||||
v->data = 0;
|
||||
v->len = 0;
|
||||
v->cap = 0;
|
||||
v->elem_size = es;
|
||||
v->elem_align = ea;
|
||||
}
|
||||
|
||||
SPL_INLINE i32 vm_vec_valid(void *vv) {
|
||||
const spl_vec_t *v = (const spl_vec_t *)vv;
|
||||
return v ? spl_vec_is_valid(v) : 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_push_raw(void *vv, const void *elem) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
if (!v || !elem)
|
||||
return 0;
|
||||
if (v->len < v->cap) {
|
||||
memcpy((void *)(v->data + v->len * v->elem_size), elem, v->elem_size);
|
||||
v->len++;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_get(void *vv, u64 index, void **out) {
|
||||
const spl_vec_t *v = (const spl_vec_t *)vv;
|
||||
if (!v || !out)
|
||||
return 1;
|
||||
if (!spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (index >= v->len)
|
||||
return 2;
|
||||
*out = (void *)(v->data + index * v->elem_size);
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_push(void *vv, const void *elem) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
int rc;
|
||||
if (!v || !elem || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (v->len < v->cap) {
|
||||
memcpy((void *)(v->data + v->len * v->elem_size), elem, v->elem_size);
|
||||
v->len++;
|
||||
return 0;
|
||||
}
|
||||
rc = spl_vec_reserve_raw(v, v->len + 1);
|
||||
if (rc)
|
||||
return rc;
|
||||
memmove((void *)(v->data + v->len * v->elem_size), elem, v->elem_size);
|
||||
v->len++;
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_allocate(void *out, u64 capacity, u64 elem_size, u64 elem_align) {
|
||||
return spl_vec_allocate((spl_vec_t *)out, capacity, elem_size, elem_align);
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_reserve_raw(void *vv, u64 need) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
if (!v)
|
||||
return 1;
|
||||
return spl_vec_reserve_raw(v, need);
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_append(void *vv, const void *elems, u64 count) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
usize copy_bytes, new_len, used, source_offset = 0;
|
||||
int source_inside = 0, rc;
|
||||
if (!v || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (count == 0)
|
||||
return 0;
|
||||
if (!elems)
|
||||
return 1;
|
||||
if (count > ~(usize)0 / v->elem_size)
|
||||
return 3;
|
||||
copy_bytes = count * v->elem_size;
|
||||
if (v->len > ~(usize)0 - count)
|
||||
return 3;
|
||||
new_len = v->len + count;
|
||||
used = v->len * v->elem_size;
|
||||
if (used) {
|
||||
usize base = v->data;
|
||||
usize source = (usize)elems;
|
||||
if (source >= base && source - base < used) {
|
||||
source_offset = source - base;
|
||||
if (copy_bytes > used - source_offset)
|
||||
return 2;
|
||||
source_inside = 1;
|
||||
}
|
||||
}
|
||||
rc = spl_vec_reserve_raw(v, new_len);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (source_inside)
|
||||
elems = (const void *)(v->data + source_offset);
|
||||
memmove((void *)(v->data + used), elems, copy_bytes);
|
||||
v->len = new_len;
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_set(void *vv, u64 index, const void *elem) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
if (!v || !elem || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (index >= v->len)
|
||||
return 2;
|
||||
memmove((void *)(v->data + index * v->elem_size), elem, v->elem_size);
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_pop(void *vv, void *out) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
if (!v || !out || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (v->len == 0)
|
||||
return 2;
|
||||
memmove(out, (void *)(v->data + (v->len - 1) * v->elem_size), v->elem_size);
|
||||
v->len--;
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_clone(void *outp, const void *srcp) {
|
||||
spl_vec_t *out = (spl_vec_t *)outp;
|
||||
const spl_vec_t *src = (const spl_vec_t *)srcp;
|
||||
spl_vec_t fresh;
|
||||
usize bytes;
|
||||
int rc;
|
||||
if (!out || !src || !spl_vec_is_valid(out) || !spl_vec_is_valid(src))
|
||||
return 1;
|
||||
if (out->elem_size != src->elem_size || out->elem_align != src->elem_align)
|
||||
return 1;
|
||||
fresh.raw = 0;
|
||||
fresh.data = 0;
|
||||
fresh.len = 0;
|
||||
fresh.cap = 0;
|
||||
fresh.elem_size = src->elem_size;
|
||||
fresh.elem_align = src->elem_align;
|
||||
rc = spl_vec_reserve_raw(&fresh, src->len);
|
||||
if (rc)
|
||||
return rc;
|
||||
bytes = src->len * src->elem_size;
|
||||
if (bytes)
|
||||
memcpy((void *)fresh.data, (void *)src->data, bytes);
|
||||
fresh.len = src->len;
|
||||
if (out->raw)
|
||||
free((void *)out->raw);
|
||||
*out = fresh;
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE void vm_vec_drop(void *vv) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
if (v)
|
||||
spl_vec_release(v);
|
||||
}
|
||||
|
||||
/* ---- Map ---- */
|
||||
typedef struct {
|
||||
spl_vec_t keys;
|
||||
spl_vec_t vals;
|
||||
spl_vec_t states;
|
||||
usize size;
|
||||
usize key_size;
|
||||
usize val_size;
|
||||
usize hash_kind;
|
||||
} spl_map_t;
|
||||
|
||||
SPL_INLINE usize spl_hash_usize_value(usize value) {
|
||||
usize prime = 1099511628211ull;
|
||||
usize h = value;
|
||||
h = (h ^ (h >> 30)) * prime;
|
||||
h = h ^ (h >> 27);
|
||||
h = h * prime;
|
||||
h = h ^ (h >> 31);
|
||||
if (h == 0)
|
||||
return prime;
|
||||
return h;
|
||||
}
|
||||
SPL_INLINE usize spl_hash_bytes(const void *data, usize len) {
|
||||
const unsigned char *p = (const unsigned char *)data;
|
||||
usize h = 14695981039346656037ull;
|
||||
usize prime = 1099511628211ull;
|
||||
usize i;
|
||||
for (i = 0; i < len; i++)
|
||||
h = (h ^ (usize)p[i]) * prime;
|
||||
return h;
|
||||
}
|
||||
SPL_INLINE usize spl_map_hash(const spl_map_t *m, const void *key) {
|
||||
const usize *v;
|
||||
if (m->hash_kind == 0)
|
||||
return spl_hash_usize_value(*(const usize *)key);
|
||||
if (m->hash_kind == 2)
|
||||
return 3;
|
||||
v = (const usize *)key;
|
||||
return spl_hash_bytes((const void *)v[0], v[1]);
|
||||
}
|
||||
SPL_INLINE int spl_map_eq(const spl_map_t *m, const void *a, const void *b) {
|
||||
const usize *x;
|
||||
const usize *y;
|
||||
if (m->hash_kind == 0 || m->hash_kind == 2)
|
||||
return *(const usize *)a == *(const usize *)b;
|
||||
x = (const usize *)a;
|
||||
y = (const usize *)b;
|
||||
if (x[1] != y[1])
|
||||
return 0;
|
||||
if (x[1] == 0)
|
||||
return 1;
|
||||
return memcmp((const void *)x[0], (const void *)y[0], x[1]) == 0;
|
||||
}
|
||||
SPL_INLINE int spl_map_valid(const spl_map_t *m) {
|
||||
if (m->key_size == 0 || m->val_size == 0)
|
||||
return 0;
|
||||
if (m->keys.len != m->states.len || m->vals.len != m->states.len)
|
||||
return 0;
|
||||
if (m->size > m->states.len)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
SPL_INLINE usize spl_map_find_slot(const spl_map_t *m, const void *key) {
|
||||
usize mask, i, probes;
|
||||
unsigned char *st;
|
||||
if (m->states.cap == 0)
|
||||
return ~(usize)0;
|
||||
mask = m->states.cap - 1;
|
||||
i = spl_map_hash(m, key) & mask;
|
||||
st = (unsigned char *)m->states.data;
|
||||
probes = 0;
|
||||
while (probes < m->states.cap && st[i] != 0) {
|
||||
if (st[i] == 1 && spl_map_eq(m, (const void *)(m->keys.data + i * m->key_size), key))
|
||||
return i;
|
||||
i = (i + 1) & mask;
|
||||
probes++;
|
||||
}
|
||||
return ~(usize)0;
|
||||
}
|
||||
SPL_INLINE void spl_map_place(spl_map_t *m, const void *key, const void *val) {
|
||||
usize mask, i, first_deleted, probes;
|
||||
unsigned char *st;
|
||||
mask = m->states.cap - 1;
|
||||
i = spl_map_hash(m, key) & mask;
|
||||
st = (unsigned char *)m->states.data;
|
||||
first_deleted = ~(usize)0;
|
||||
probes = 0;
|
||||
while (probes < m->states.cap && st[i] != 0) {
|
||||
if (st[i] == 2 && first_deleted == ~(usize)0)
|
||||
first_deleted = i;
|
||||
i = (i + 1) & mask;
|
||||
probes++;
|
||||
}
|
||||
if (first_deleted != ~(usize)0)
|
||||
i = first_deleted;
|
||||
memcpy((void *)(m->keys.data + i * m->key_size), key, m->key_size);
|
||||
memcpy((void *)(m->vals.data + i * m->val_size), val, m->val_size);
|
||||
st[i] = 1;
|
||||
m->size++;
|
||||
}
|
||||
SPL_INLINE int spl_map_reserve(spl_map_t *m, usize need) {
|
||||
usize new_cap = 0, i;
|
||||
spl_map_t fresh;
|
||||
int rc;
|
||||
if (!spl_map_valid(m))
|
||||
return 1;
|
||||
if (need <= m->states.cap)
|
||||
return 0;
|
||||
spl_grow(m->states.cap, need, &new_cap);
|
||||
fresh = *m;
|
||||
fresh.keys.raw = 0;
|
||||
fresh.keys.data = 0;
|
||||
fresh.keys.len = 0;
|
||||
fresh.keys.cap = 0;
|
||||
fresh.vals.raw = 0;
|
||||
fresh.vals.data = 0;
|
||||
fresh.vals.len = 0;
|
||||
fresh.vals.cap = 0;
|
||||
fresh.states.raw = 0;
|
||||
fresh.states.data = 0;
|
||||
fresh.states.len = 0;
|
||||
fresh.states.cap = 0;
|
||||
fresh.size = 0;
|
||||
rc = spl_vec_reserve_raw(&fresh.keys, new_cap);
|
||||
if (rc)
|
||||
goto fail;
|
||||
rc = spl_vec_reserve_raw(&fresh.vals, new_cap);
|
||||
if (rc)
|
||||
goto fail;
|
||||
rc = spl_vec_reserve_raw(&fresh.states, new_cap);
|
||||
if (rc)
|
||||
goto fail;
|
||||
fresh.keys.len = new_cap;
|
||||
fresh.vals.len = new_cap;
|
||||
fresh.states.len = new_cap;
|
||||
memset((void *)fresh.states.data, 0, new_cap);
|
||||
for (i = 0; i < m->states.len; i++) {
|
||||
if (((unsigned char *)m->states.data)[i] == 1)
|
||||
spl_map_place(&fresh, (const void *)(m->keys.data + i * m->key_size),
|
||||
(const void *)(m->vals.data + i * m->val_size));
|
||||
}
|
||||
spl_vec_release(&m->keys);
|
||||
spl_vec_release(&m->vals);
|
||||
spl_vec_release(&m->states);
|
||||
*m = fresh;
|
||||
return 0;
|
||||
fail:
|
||||
spl_vec_release(&fresh.keys);
|
||||
spl_vec_release(&fresh.vals);
|
||||
spl_vec_release(&fresh.states);
|
||||
return rc;
|
||||
}
|
||||
SPL_INLINE i32 vm_map_valid(void *mm) {
|
||||
const spl_map_t *m = (const spl_map_t *)mm;
|
||||
return m ? spl_map_valid(m) : 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_map_reserve(void *mm, u64 need) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
if (!m)
|
||||
return 1;
|
||||
return spl_map_reserve(m, need);
|
||||
}
|
||||
SPL_INLINE i32 vm_map_put(void *mm, const void *key, const void *val) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
usize existing;
|
||||
if (!m || !key || !val || !spl_map_valid(m))
|
||||
return 1;
|
||||
existing = spl_map_find_slot(m, key);
|
||||
if (existing != ~(usize)0) {
|
||||
memcpy((void *)(m->vals.data + existing * m->val_size), val, m->val_size);
|
||||
return 0;
|
||||
}
|
||||
if (m->states.cap == 0) {
|
||||
int rc = spl_map_reserve(m, 8);
|
||||
if (rc)
|
||||
return rc;
|
||||
} else {
|
||||
usize next_size, scaled;
|
||||
if (m->size > ~(usize)0 - 1)
|
||||
return 3;
|
||||
next_size = m->size + 1;
|
||||
if (next_size > ~(usize)0 / 128)
|
||||
return 3;
|
||||
scaled = next_size * 128;
|
||||
if (scaled / m->states.cap >= 70) {
|
||||
int rc;
|
||||
if (m->states.cap > ~(usize)0 / 2)
|
||||
return 3;
|
||||
rc = spl_map_reserve(m, m->states.cap * 2);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
spl_map_place(m, key, val);
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_map_get(void *mm, const void *key, void *out) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
usize i;
|
||||
if (!m || !key || !out || !spl_map_valid(m))
|
||||
return 1;
|
||||
i = spl_map_find_slot(m, key);
|
||||
if (i == ~(usize)0)
|
||||
return 1;
|
||||
memcpy(out, (void *)(m->vals.data + i * m->val_size), m->val_size);
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_map_del(void *mm, const void *key) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
usize i;
|
||||
if (!m || !key || !spl_map_valid(m))
|
||||
return 1;
|
||||
i = spl_map_find_slot(m, key);
|
||||
if (i == ~(usize)0)
|
||||
return 1;
|
||||
((unsigned char *)m->states.data)[i] = 2;
|
||||
m->size--;
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE void vm_map_clear(void *mm) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
if (!m)
|
||||
return;
|
||||
if (m->states.data && m->states.len)
|
||||
memset((void *)m->states.data, 0, m->states.len);
|
||||
m->size = 0;
|
||||
}
|
||||
SPL_INLINE void vm_map_drop(void *mm) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
if (!m)
|
||||
return;
|
||||
spl_vec_release(&m->keys);
|
||||
spl_vec_release(&m->vals);
|
||||
spl_vec_release(&m->states);
|
||||
m->size = 0;
|
||||
}
|
||||
|
||||
/* ---- file ---- */
|
||||
SPL_INLINE void *vm_fopen(void *path, void *mode) {
|
||||
return (void *)fopen((const char *)path, (const char *)mode);
|
||||
}
|
||||
SPL_INLINE i32 vm_fclose(void *f) { return f ? fclose((FILE *)f) : 0; }
|
||||
SPL_INLINE u64 vm_fread(void *buf, u64 sz, u64 n, void *f) {
|
||||
return f ? (u64)fread(buf, (size_t)sz, (size_t)n, (FILE *)f) : 0;
|
||||
}
|
||||
SPL_INLINE u64 vm_fwrite(void *buf, u64 sz, u64 n, void *f) {
|
||||
return f ? (u64)fwrite(buf, (size_t)sz, (size_t)n, (FILE *)f) : 0;
|
||||
}
|
||||
SPL_INLINE u64 vm_fsize(void *f) {
|
||||
long cur, end;
|
||||
if (!f)
|
||||
return 0;
|
||||
cur = ftell((FILE *)f);
|
||||
if (cur < 0)
|
||||
return 0;
|
||||
if (fseek((FILE *)f, 0, SEEK_END) != 0)
|
||||
return 0;
|
||||
end = ftell((FILE *)f);
|
||||
fseek((FILE *)f, cur, SEEK_SET);
|
||||
return end < 0 ? 0 : (u64)end;
|
||||
}
|
||||
SPL_INLINE void *vm_stdin(void) { return (void *)stdin; }
|
||||
SPL_INLINE void *vm_stdout(void) { return (void *)stdout; }
|
||||
SPL_INLINE void *vm_stderr(void) { return (void *)stderr; }
|
||||
SPL_INLINE i32 vm_putchar(i32 c) { return fputc(c, stdout); }
|
||||
SPL_INLINE i32 vm_getchar(void) { return fgetc(stdin); }
|
||||
|
||||
/* ---- misc ---- */
|
||||
SPL_INLINE void vm_exit(i32 code) { exit(code); }
|
||||
SPL_INLINE u64 vm_timestamp(u64 kind) {
|
||||
time_t sec = time(NULL);
|
||||
if (kind == 1)
|
||||
return (u64)sec;
|
||||
if (kind == 2)
|
||||
return 0;
|
||||
if (kind == 4)
|
||||
return (u64)sec * 1000000000ull;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 内嵌 VM(comptime):实现在 spl_vm_embed.c,链接 stage0 VM ---- */
|
||||
#define SPL_VM_EMBED_IMPLIMENTATION
|
||||
#include "spl_vm_embed.h"
|
||||
|
||||
#endif /* __SPL_RT_H__ */
|
||||
@@ -0,0 +1,109 @@
|
||||
/* spl_vm_embed.h - 原生编译器内嵌 VM(comptime 执行)。
|
||||
*
|
||||
* 单 TU 合并式:定义 SPL_VM_EMBED_IMPLIMENTATION 后 include 本头,会把 stage0 的
|
||||
* spl_mcode.c / spl_syscall.c / spl_vm.c 一并纳入当前翻译单元(不单独编译/链接)。
|
||||
*/
|
||||
|
||||
#ifndef __SPL_VM_EMBED_H__
|
||||
#define __SPL_VM_EMBED_H__
|
||||
|
||||
void *vm_new(void);
|
||||
void vm_drop(void *vm);
|
||||
int vm_load_from_buffer(void *vm, const void *buf, unsigned long long len);
|
||||
void vm_push(void *vm, unsigned long long val);
|
||||
int vm_call(void *vm, const char *name, unsigned long long nargs);
|
||||
int vm_run(void *vm);
|
||||
|
||||
#ifdef SPL_VM_EMBED_IMPLIMENTATION
|
||||
|
||||
#include "../../stage0/spl_mcode.h"
|
||||
void spl_syscall_register(spl_prog_t *prog) { (void)prog; /* stub */ }
|
||||
|
||||
#include "../../stage0/spl_mcode.c"
|
||||
#include "../../stage0/spl_vm.c"
|
||||
|
||||
void *vm_new(void) {
|
||||
spl_vm_t *vm = (spl_vm_t *)malloc(sizeof(spl_vm_t));
|
||||
if (!vm)
|
||||
return 0;
|
||||
spl_vm_init(vm);
|
||||
return vm;
|
||||
}
|
||||
|
||||
void vm_drop(void *p) {
|
||||
spl_vm_t *vm = (spl_vm_t *)p;
|
||||
if (!vm)
|
||||
return;
|
||||
if (vm->prog) {
|
||||
spl_prog_drop(vm->prog);
|
||||
free(vm->prog);
|
||||
vm->prog = 0;
|
||||
}
|
||||
spl_vm_drop(vm);
|
||||
free(vm);
|
||||
}
|
||||
|
||||
int vm_load_from_buffer(void *p, const void *buf, unsigned long long len) {
|
||||
spl_vm_t *vm = (spl_vm_t *)p;
|
||||
spl_prog_t *prog;
|
||||
if (!vm || !buf)
|
||||
return -1;
|
||||
prog = (spl_prog_t *)malloc(sizeof(spl_prog_t));
|
||||
if (!prog)
|
||||
return -1;
|
||||
if (spl_prog_load_from_buffer((const char *)buf, (usize)len, prog) != 0) {
|
||||
free(prog);
|
||||
return -1;
|
||||
}
|
||||
spl_syscall_register(prog);
|
||||
if (spl_vm_load_prog(vm, prog) != 0) {
|
||||
spl_prog_drop(prog);
|
||||
free(prog);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vm_push(void *p, unsigned long long val) {
|
||||
spl_vm_t *vm = (spl_vm_t *)p;
|
||||
if (!vm)
|
||||
return;
|
||||
if (vm->sp >= vm->config.max_stack_depth)
|
||||
return;
|
||||
vm->stacks.data[vm->sp++] = (spl_vm_val_t)val;
|
||||
}
|
||||
|
||||
int vm_call(void *p, const char *name, unsigned long long nargs) {
|
||||
spl_vm_t *vm = (spl_vm_t *)p;
|
||||
spl_vm_func_t *func;
|
||||
if (!vm || !name)
|
||||
return -1;
|
||||
func = spl_prog_get_func(vm->prog, name);
|
||||
if (!func)
|
||||
return -1;
|
||||
if (vm->cp >= vm->config.max_call_depth)
|
||||
return -1;
|
||||
/* 入口调用:ip=-1 作 sentinel;实参由 vm_push 压栈。 */
|
||||
vm->ip = (usize)-1;
|
||||
vm->cp = 0;
|
||||
vm->fp = 0;
|
||||
vm->frames.data[0].saved_sp = vm->sp - (usize)nargs;
|
||||
vm->frames.data[0].saved_fp = vm->fp;
|
||||
vm->frames.data[0].saved_ip = vm->ip;
|
||||
vm->frames.data[0].nargs = (spl_vm_val_t)nargs;
|
||||
vm->cp = 1;
|
||||
vm->ip = (usize)func->address;
|
||||
vm->fp = vm->sp - (usize)nargs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vm_run(void *p) {
|
||||
spl_vm_t *vm = (spl_vm_t *)p;
|
||||
if (!vm)
|
||||
return -1;
|
||||
spl_vm_run_until(vm, 0);
|
||||
return (int)vm->exit_code;
|
||||
}
|
||||
#endif /* SPL_VM_EMBED_IMPLIMENTATION */
|
||||
|
||||
#endif /* __SPL_VM_EMBED_H__ */
|
||||
@@ -25,6 +25,10 @@ fn steps(): usize {
|
||||
ret vm_timestamp(3);
|
||||
}
|
||||
|
||||
fn calls(): usize {
|
||||
ret vm_timestamp(5);
|
||||
}
|
||||
|
||||
fn now_ns(): usize {
|
||||
ret vm_timestamp(4);
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ type spl_vm_val_t = u64; // usize
|
||||
#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_load_from_buffer(vm: *any, buf: *any, len: usize): i32;
|
||||
|
||||
#extern(vm) fn vm_push(vm: *any, val: spl_vm_val_t): void;
|
||||
// TODO
|
||||
|
||||
+58
-30
@@ -66,37 +66,65 @@ fn is_trivia(kind: TokenKind): bool {
|
||||
}
|
||||
}
|
||||
|
||||
// 先按长度与首字节分派,再做完整比较,避免对每个标识符做全表串比较。
|
||||
fn keyword_of(text: ByteView): KeywordResult {
|
||||
if is(text, "any") { ret .Yes(.Any); }
|
||||
if is(text, "as") { ret .Yes(.As); }
|
||||
if is(text, "bool") { ret .Yes(.Bool); }
|
||||
if is(text, "break") { ret .Yes(.Break); }
|
||||
if is(text, "catch") { ret .Yes(.Catch); }
|
||||
if is(text, "comptime") { ret .Yes(.Comptime); }
|
||||
if is(text, "const") { ret .Yes(.Const); }
|
||||
if is(text, "continue") { ret .Yes(.Continue); }
|
||||
if is(text, "defer") { ret .Yes(.Defer); }
|
||||
if is(text, "else") { ret .Yes(.Else); }
|
||||
if is(text, "enum") { ret .Yes(.Enum); }
|
||||
if is(text, "errdefer") { ret .Yes(.Errdefer); }
|
||||
if is(text, "false") { ret .Yes(.False); }
|
||||
if is(text, "fn") { ret .Yes(.Fn); }
|
||||
if is(text, "for") { ret .Yes(.For); }
|
||||
if is(text, "if") { ret .Yes(.If); }
|
||||
if is(text, "loop") { ret .Yes(.Loop); }
|
||||
if is(text, "match") { ret .Yes(.Match); }
|
||||
if is(text, "null") { ret .Yes(.Null); }
|
||||
if is(text, "ret") { ret .Yes(.Ret); }
|
||||
if is(text, "shape") { ret .Yes(.Shape); }
|
||||
if is(text, "struct") { ret .Yes(.Struct); }
|
||||
if is(text, "true") { ret .Yes(.True); }
|
||||
if is(text, "try") { ret .Yes(.Try); }
|
||||
if is(text, "type") { ret .Yes(.Type); }
|
||||
if is(text, "undefined") { ret .Yes(.Undefined); }
|
||||
if is(text, "union") { ret .Yes(.Union); }
|
||||
if is(text, "var") { ret .Yes(.Var); }
|
||||
if is(text, "while") { ret .Yes(.While); }
|
||||
if is(text, "_") { ret .Yes(.Underscore); }
|
||||
var n = text.len;
|
||||
if n == 0 { ret .No; }
|
||||
var c0 = text.ptr[0];
|
||||
if n == 2 {
|
||||
if c0 == 'a' && is(text, "as") { ret .Yes(.As); }
|
||||
if c0 == 'f' && is(text, "fn") { ret .Yes(.Fn); }
|
||||
if c0 == 'i' && is(text, "if") { ret .Yes(.If); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 3 {
|
||||
if c0 == 'a' && is(text, "any") { ret .Yes(.Any); }
|
||||
if c0 == 'f' && is(text, "for") { ret .Yes(.For); }
|
||||
if c0 == 'r' && is(text, "ret") { ret .Yes(.Ret); }
|
||||
if c0 == 't' && is(text, "try") { ret .Yes(.Try); }
|
||||
if c0 == 'v' && is(text, "var") { ret .Yes(.Var); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 4 {
|
||||
if c0 == 'b' && is(text, "bool") { ret .Yes(.Bool); }
|
||||
if c0 == 'e' && is(text, "else") { ret .Yes(.Else); }
|
||||
if c0 == 'e' && is(text, "enum") { ret .Yes(.Enum); }
|
||||
if c0 == 'l' && is(text, "loop") { ret .Yes(.Loop); }
|
||||
if c0 == 'n' && is(text, "null") { ret .Yes(.Null); }
|
||||
if c0 == 't' && is(text, "true") { ret .Yes(.True); }
|
||||
if c0 == 't' && is(text, "type") { ret .Yes(.Type); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 5 {
|
||||
if c0 == 'b' && is(text, "break") { ret .Yes(.Break); }
|
||||
if c0 == 'c' && is(text, "catch") { ret .Yes(.Catch); }
|
||||
if c0 == 'c' && is(text, "const") { ret .Yes(.Const); }
|
||||
if c0 == 'd' && is(text, "defer") { ret .Yes(.Defer); }
|
||||
if c0 == 'f' && is(text, "false") { ret .Yes(.False); }
|
||||
if c0 == 'm' && is(text, "match") { ret .Yes(.Match); }
|
||||
if c0 == 's' && is(text, "shape") { ret .Yes(.Shape); }
|
||||
if c0 == 'u' && is(text, "union") { ret .Yes(.Union); }
|
||||
if c0 == 'w' && is(text, "while") { ret .Yes(.While); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 6 {
|
||||
if c0 == 's' && is(text, "struct") { ret .Yes(.Struct); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 8 {
|
||||
if c0 == 'c' && is(text, "comptime") { ret .Yes(.Comptime); }
|
||||
if c0 == 'c' && is(text, "continue") { ret .Yes(.Continue); }
|
||||
if c0 == 'e' && is(text, "errdefer") { ret .Yes(.Errdefer); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 9 {
|
||||
if c0 == 'u' && is(text, "undefined") { ret .Yes(.Undefined); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 1 {
|
||||
if c0 == '_' && is(text, "_") { ret .Yes(.Underscore); }
|
||||
ret .No;
|
||||
}
|
||||
ret .No;
|
||||
}
|
||||
|
||||
|
||||
+5
-12
@@ -9,7 +9,7 @@
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .Map } = @import("utils/map.spl");
|
||||
type .{ .Map, .hash_kind_usize } = @import("utils/map.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .append_usize } = @import("utils/fmt.spl");
|
||||
@@ -301,11 +301,8 @@ fn usize_at(v: Vec, i: usize): usize {
|
||||
}
|
||||
|
||||
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;
|
||||
if i >= v.len { ret null; }
|
||||
ret (v.data as *Member) + i;
|
||||
}
|
||||
|
||||
fn push_member(v: *Vec, name: []u8, type_id: usize) {
|
||||
@@ -323,11 +320,7 @@ fn push_member(v: *Vec, name: []u8, type_id: usize) {
|
||||
// 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;
|
||||
ret (self.table.data as *TypeEntry) + id;
|
||||
}
|
||||
|
||||
// ---- 生命周期 ----
|
||||
@@ -336,7 +329,7 @@ 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),
|
||||
hash_kind_usize()),
|
||||
};
|
||||
var sentinel = TypeEntry { .hash_next = 0, .node = .Error };
|
||||
match Vec.push(&tt.table, &sentinel as *u8) {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
type .{ .Error, .make } = @import("error.spl");
|
||||
|
||||
#extern(vm) fn vm_bytes_eq(a: *any, alen: usize, b: *any, blen: usize): i32;
|
||||
|
||||
type ByteView = @self;
|
||||
|
||||
ptr: *u8,
|
||||
@@ -62,13 +64,7 @@ fn byte_at(self: ByteView, index: usize): ByteResult {
|
||||
}
|
||||
|
||||
fn eq(a: ByteView, b: ByteView): bool {
|
||||
if a.len != b.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < a.len {
|
||||
if a.ptr[i] != b.ptr[i] { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
ret vm_bytes_eq(a.ptr as *any, a.len, b.ptr as *any, b.len) != 0;
|
||||
}
|
||||
|
||||
// 按无符号字节进行字典序比较,结果只承诺小于、等于或大于零。
|
||||
|
||||
+45
-190
@@ -7,9 +7,14 @@
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
type .{ .hash_usize, .eq_usize, .hash_view, .eq_view } = @import("hash.spl");
|
||||
type .{ .checked_add, .checked_mul, .grow_capacity } = @import("memory.spl");
|
||||
type .{ .copy } = @import("../sys/mem.spl");
|
||||
|
||||
#extern(vm) fn vm_map_valid(m: *any): i32;
|
||||
#extern(vm) fn vm_map_reserve(m: *any, need: usize): i32;
|
||||
#extern(vm) fn vm_map_put(m: *any, key: *any, val: *any): i32;
|
||||
#extern(vm) fn vm_map_get(m: *any, key: *any, out: *any): i32;
|
||||
#extern(vm) fn vm_map_del(m: *any, key: *any): i32;
|
||||
#extern(vm) fn vm_map_clear(m: *any): void;
|
||||
#extern(vm) fn vm_map_drop(m: *any): void;
|
||||
|
||||
// 槽位状态:Empty/Occupied/Deleted(墓碑,仅在扩容重哈希时清除)。
|
||||
// 负载因子 70/128。
|
||||
@@ -20,11 +25,6 @@ type SlotState = enum {
|
||||
Deleted,
|
||||
}
|
||||
|
||||
fn state_code(state: SlotState): u8 {
|
||||
var s: SlotState = state;
|
||||
ret @enum2int(s) as u8;
|
||||
}
|
||||
|
||||
// 查找/删除结果:显式区分命中与缺失,替代 bool 哨兵。
|
||||
type LookupResult = enum {
|
||||
Found,
|
||||
@@ -39,11 +39,15 @@ states: Vec,
|
||||
size: usize,
|
||||
key_size: usize,
|
||||
val_size: usize,
|
||||
hash_fn: fn(*u8): usize,
|
||||
eq_fn: fn(*u8, *u8): bool,
|
||||
hash_kind: usize,
|
||||
|
||||
// 键解释:0 = usize;1 = ByteView(按内容哈希/比较)。
|
||||
fn hash_kind_usize(): usize { ret 0; }
|
||||
fn hash_kind_view(): usize { ret 1; }
|
||||
fn hash_kind_constant(): usize { ret 2; }
|
||||
|
||||
fn blank(key_size: usize, key_align: usize, val_size: usize, val_align: usize,
|
||||
hash_fn: fn(*u8): usize, eq_fn: fn(*u8, *u8): bool): Map {
|
||||
hash_kind: usize): Map {
|
||||
ret Map {
|
||||
.keys = Vec.blank(key_size, key_align),
|
||||
.vals = Vec.blank(val_size, val_align),
|
||||
@@ -51,23 +55,30 @@ fn blank(key_size: usize, key_align: usize, val_size: usize, val_align: usize,
|
||||
.size = 0,
|
||||
.key_size = key_size,
|
||||
.val_size = val_size,
|
||||
.hash_fn = hash_fn,
|
||||
.eq_fn = eq_fn,
|
||||
.hash_kind = hash_kind,
|
||||
};
|
||||
}
|
||||
|
||||
fn new(key_size: usize, key_align: usize, val_size: usize, val_align: usize,
|
||||
hash_fn: fn(*u8): usize, eq_fn: fn(*u8, *u8): bool): Map {
|
||||
ret blank(key_size, key_align, val_size, val_align, hash_fn, eq_fn);
|
||||
hash_kind: usize): Map {
|
||||
ret blank(key_size, key_align, val_size, val_align, hash_kind);
|
||||
}
|
||||
|
||||
fn valid(self: Map): bool {
|
||||
if self.key_size == 0 || self.val_size == 0 { ret false; }
|
||||
if self.states.len != self.keys.len || self.states.len != self.vals.len {
|
||||
ret false;
|
||||
}
|
||||
if self.size > self.states.len { ret false; }
|
||||
ret true;
|
||||
fn status_from_code(code: i32): Status {
|
||||
if code == 0 { ret .Ok; }
|
||||
if code == 2 { ret .Err(make(.OutOfRange)); }
|
||||
if code == 3 { ret .Err(make(.Overflow)); }
|
||||
if code == 4 { ret .Err(make(.InvalidAlignment)); }
|
||||
ret .Err(make(.InvalidArgument));
|
||||
}
|
||||
|
||||
fn lookup_from_code(code: i32): LookupResult {
|
||||
if code == 0 { ret .Found; }
|
||||
ret .NotFound;
|
||||
}
|
||||
|
||||
fn valid(self: *Map): bool {
|
||||
ret vm_map_valid(self as *any) != 0;
|
||||
}
|
||||
|
||||
fn size(self: Map): usize {
|
||||
@@ -85,10 +96,8 @@ fn slot_count(self: Map): usize {
|
||||
|
||||
fn slot_state(self: Map, index: usize): SlotState {
|
||||
var code = self.states.data[index];
|
||||
var occupied: SlotState = .Occupied;
|
||||
var deleted_state: SlotState = .Deleted;
|
||||
if code == state_code(occupied) { ret .Occupied; }
|
||||
if code == state_code(deleted_state) { ret .Deleted; }
|
||||
if code == 1 { ret .Occupied; }
|
||||
if code == 2 { ret .Deleted; }
|
||||
ret .Empty;
|
||||
}
|
||||
|
||||
@@ -109,186 +118,32 @@ fn val_at(self: *Map, index: usize): *u8 {
|
||||
ret self.vals.data + index * self.val_size;
|
||||
}
|
||||
|
||||
// 返回占用键的槽位;未找到返回 ~0。容量为 0 时直接未找到。
|
||||
fn find_slot(self: *Map, key: *u8): usize {
|
||||
if self.states.cap == 0 { ret ~(0 as usize); }
|
||||
var mask = self.states.cap - 1;
|
||||
var i = self.hash_fn(key) & mask;
|
||||
var empty: SlotState = .Empty;
|
||||
var occupied: SlotState = .Occupied;
|
||||
var probes: usize = 0;
|
||||
while probes < self.states.cap && self.states.data[i] != state_code(empty) {
|
||||
if self.states.data[i] == state_code(occupied) &&
|
||||
self.eq_fn(self.keys.data + i * self.key_size, key) {
|
||||
ret i;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
probes = probes + 1;
|
||||
}
|
||||
ret ~(0 as usize);
|
||||
}
|
||||
|
||||
// 要求容量非零且键不存在。优先复用探测链中的第一个墓碑。
|
||||
fn place(self: *Map, key: *u8, val: *u8) {
|
||||
var mask = self.states.cap - 1;
|
||||
var i = self.hash_fn(key) & mask;
|
||||
var empty: SlotState = .Empty;
|
||||
var deleted: SlotState = .Deleted;
|
||||
var occupied: SlotState = .Occupied;
|
||||
var first_deleted = ~(0 as usize);
|
||||
var probes: usize = 0;
|
||||
while probes < self.states.cap && self.states.data[i] != state_code(empty) {
|
||||
if self.states.data[i] == state_code(deleted) && first_deleted == ~(0 as usize) {
|
||||
first_deleted = i;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
probes = probes + 1;
|
||||
}
|
||||
if first_deleted != ~(0 as usize) {
|
||||
i = first_deleted;
|
||||
}
|
||||
copy((self.keys.data + i * self.key_size) as *any, key as *any, self.key_size);
|
||||
copy((self.vals.data + i * self.val_size) as *any, val as *any, self.val_size);
|
||||
self.states.data[i] = state_code(occupied);
|
||||
self.size = self.size + 1;
|
||||
}
|
||||
|
||||
// 保证至少 need 个槽位。所有检查在旧表改动前完成,失败保持原表不变。
|
||||
fn reserve(self: *Map, need: usize): Status {
|
||||
if !valid(self.*) { ret .Err(make(.InvalidArgument)); }
|
||||
if need <= self.states.cap { ret .Ok; }
|
||||
|
||||
var new_cap = grow_capacity(self.states.cap, 8, need);
|
||||
|
||||
var fresh = self.*;
|
||||
fresh.keys = Vec.blank(self.key_size, self.keys.elem_align);
|
||||
fresh.vals = Vec.blank(self.val_size, self.vals.elem_align);
|
||||
fresh.states = Vec.blank(1, 1);
|
||||
fresh.size = 0;
|
||||
|
||||
match Vec.reserve(&fresh.keys, new_cap) {
|
||||
.Ok => {},
|
||||
.Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
match Vec.reserve(&fresh.vals, new_cap) {
|
||||
.Ok => {},
|
||||
.Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
match Vec.reserve(&fresh.states, new_cap) {
|
||||
.Ok => {},
|
||||
.Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
fresh.keys.len = new_cap;
|
||||
fresh.vals.len = new_cap;
|
||||
fresh.states.len = new_cap;
|
||||
|
||||
var i: usize = 0;
|
||||
var empty: SlotState = .Empty;
|
||||
var occupied: SlotState = .Occupied;
|
||||
while i < new_cap {
|
||||
fresh.states.data[i] = state_code(empty);
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < self.states.len {
|
||||
if self.states.data[i] == state_code(occupied) {
|
||||
place(&fresh, self.keys.data + i * self.key_size,
|
||||
self.vals.data + i * self.val_size);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var old_keys = self.keys;
|
||||
var old_vals = self.vals;
|
||||
var old_states = self.states;
|
||||
self.* = fresh;
|
||||
Vec.drop(&old_keys);
|
||||
Vec.drop(&old_vals);
|
||||
Vec.drop(&old_states);
|
||||
ret .Ok;
|
||||
ret status_from_code(vm_map_reserve(self as *any, need));
|
||||
}
|
||||
|
||||
// 覆盖已存在的键;否则插入并可能扩容。
|
||||
fn put(self: *Map, key: *u8, val: *u8): Status {
|
||||
if !valid(self.*) || key == null || val == null {
|
||||
ret .Err(make(.InvalidArgument));
|
||||
}
|
||||
|
||||
var existing = find_slot(self, key);
|
||||
if existing != ~(0 as usize) {
|
||||
copy((self.vals.data + existing * self.val_size) as *any, val as *any, self.val_size);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
if self.states.cap == 0 {
|
||||
match reserve(self, 8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
} else {
|
||||
var next_size: usize = 0;
|
||||
match checked_add(self.size, 1) {
|
||||
.Ok(value) => { next_size = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var scaled: usize = 0;
|
||||
match checked_mul(next_size, 128) {
|
||||
.Ok(value) => { scaled = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if scaled / self.states.cap >= 70 {
|
||||
var need: usize = 0;
|
||||
match checked_mul(self.states.cap, 2) {
|
||||
.Ok(value) => { need = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match reserve(self, need) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
place(self, key, val);
|
||||
ret .Ok;
|
||||
ret status_from_code(vm_map_put(self as *any, key as *any, val as *any));
|
||||
}
|
||||
|
||||
// 找到键则把值复制到 out 并返回 Found;否则 out 不变并返回 NotFound。
|
||||
fn get(self: *Map, key: *u8, out: *u8): LookupResult {
|
||||
if !valid(self.*) || key == null || out == null { ret .NotFound; }
|
||||
var i = find_slot(self, key);
|
||||
if i == ~(0 as usize) { ret .NotFound; }
|
||||
copy(out as *any, (self.vals.data + i * self.val_size) as *any, self.val_size);
|
||||
ret .Found;
|
||||
ret lookup_from_code(vm_map_get(self as *any, key as *any, out as *any));
|
||||
}
|
||||
|
||||
fn del(self: *Map, key: *u8): LookupResult {
|
||||
if !valid(self.*) || key == null { ret .NotFound; }
|
||||
var i = find_slot(self, key);
|
||||
if i == ~(0 as usize) { ret .NotFound; }
|
||||
var deleted_state: SlotState = .Deleted;
|
||||
self.states.data[i] = state_code(deleted_state);
|
||||
self.size = self.size - 1;
|
||||
ret .Found;
|
||||
ret lookup_from_code(vm_map_del(self as *any, key as *any));
|
||||
}
|
||||
|
||||
// 清空内容但保留容量。墓碑与负载计数一并复位。
|
||||
fn clear(self: *Map) {
|
||||
var i: usize = 0;
|
||||
var empty: SlotState = .Empty;
|
||||
while i < self.states.len {
|
||||
self.states.data[i] = state_code(empty);
|
||||
i = i + 1;
|
||||
}
|
||||
self.size = 0;
|
||||
vm_map_clear(self as *any);
|
||||
}
|
||||
|
||||
fn drop(self: *Map) {
|
||||
Vec.drop(&self.keys);
|
||||
Vec.drop(&self.vals);
|
||||
Vec.drop(&self.states);
|
||||
self.size = 0;
|
||||
vm_map_drop(self as *any);
|
||||
}
|
||||
|
||||
fn take(source: *Map): Map {
|
||||
@@ -348,13 +203,13 @@ fn deleted(m: *Map, key: *u8): bool {
|
||||
}
|
||||
|
||||
fn make_int_map(): Map {
|
||||
ret new(@sizeof(usize), @alignof(usize), @sizeof(i32), @alignof(i32), hash_usize, eq_usize);
|
||||
ret new(@sizeof(usize), @alignof(usize), @sizeof(i32), @alignof(i32), hash_kind_usize());
|
||||
}
|
||||
|
||||
fn test_put_get_update() {
|
||||
var m = make_int_map();
|
||||
@assert(size(m) == 0 && capacity(m) == 0, "empty map");
|
||||
@assert(valid(m));
|
||||
@assert(valid(&m));
|
||||
|
||||
var key: usize = 0;
|
||||
var value: i32 = 0;
|
||||
@@ -367,7 +222,7 @@ fn test_put_get_update() {
|
||||
key = 7;
|
||||
value = 70;
|
||||
expect_ok(put(&m, &key as *u8, &value as *u8));
|
||||
@assert(size(m) == 1 && valid(m));
|
||||
@assert(size(m) == 1 && valid(&m));
|
||||
|
||||
out = -1;
|
||||
@assert(found(&m, &key as *u8, &out as *u8) && out == 70, "insert then get");
|
||||
@@ -463,7 +318,7 @@ fn test_take_and_self_put() {
|
||||
|
||||
fn test_byte_view_keys() {
|
||||
var m = new(@sizeof(ByteView), @alignof(ByteView), @sizeof(i32), @alignof(i32),
|
||||
hash_view, eq_view);
|
||||
hash_kind_view());
|
||||
defer { drop(&m); }
|
||||
|
||||
var alpha = ByteView.from_slice("alpha");
|
||||
|
||||
+3
-25
@@ -5,6 +5,7 @@
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
|
||||
#extern(vm) fn vm_memcpy(dst: *any, src: *any, n: usize): *any;
|
||||
#extern(vm) fn vm_memmove(dst: *any, src: *any, n: usize): *any;
|
||||
|
||||
fn ignore_ptr(p: *any) {}
|
||||
|
||||
@@ -73,31 +74,8 @@ 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 {
|
||||
dst.ptr[i] = src.ptr[i];
|
||||
i = i + 1;
|
||||
}
|
||||
} else {
|
||||
var i = count;
|
||||
while i > 0 {
|
||||
i = i - 1;
|
||||
dst.ptr[i] = src.ptr[i];
|
||||
}
|
||||
}
|
||||
// 重叠安全,统一走原生 memmove。
|
||||
ignore_ptr(vm_memmove(dst.ptr as *any, src.ptr as *any, count));
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
// 开放寻址集合。键按字节复制到集合自己的存储中;集合只释放自身存储,
|
||||
// 不释放键值内部引用的资源。哈希与相等回调的约定和 Map 一致。
|
||||
type .{ .Status } = @import("error.spl");
|
||||
type .{ .Map, .LookupResult } = @import("map.spl");
|
||||
type .{ .Map, .LookupResult, .hash_kind_usize, .hash_kind_view, .hash_kind_constant } =
|
||||
@import("map.spl");
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
type .{ .hash_usize, .eq_usize, .hash_view, .eq_view } = @import("hash.spl");
|
||||
|
||||
@@ -11,15 +12,14 @@ type Set = @self;
|
||||
|
||||
map: Map,
|
||||
|
||||
fn new(key_size: usize, key_align: usize,
|
||||
hash_fn: fn(*u8): usize, eq_fn: fn(*u8, *u8): bool): Set {
|
||||
fn new(key_size: usize, key_align: usize, hash_kind: usize): Set {
|
||||
ret Set {
|
||||
.map = Map.new(key_size, key_align, 1, 1, hash_fn, eq_fn),
|
||||
.map = Map.new(key_size, key_align, 1, 1, hash_kind),
|
||||
};
|
||||
}
|
||||
|
||||
fn valid(self: Set): bool {
|
||||
ret Map.valid(self.map) && self.map.val_size == 1;
|
||||
ret Map.valid(&self.map) && self.map.val_size == 1;
|
||||
}
|
||||
|
||||
fn size(self: Set): usize {
|
||||
@@ -104,7 +104,7 @@ fn constant_hash(key: *u8): usize {
|
||||
}
|
||||
|
||||
fn test_insert_find_remove() {
|
||||
var set = new(@sizeof(usize), @alignof(usize), hash_usize, eq_usize);
|
||||
var set = new(@sizeof(usize), @alignof(usize), hash_kind_usize());
|
||||
defer { drop(&set); }
|
||||
@assert(valid(set));
|
||||
@assert(size(set) == 0 && capacity(set) == 0);
|
||||
@@ -169,7 +169,7 @@ fn test_insert_find_remove() {
|
||||
}
|
||||
|
||||
fn test_collisions_and_take() {
|
||||
var set = new(@sizeof(usize), @alignof(usize), constant_hash, eq_usize);
|
||||
var set = new(@sizeof(usize), @alignof(usize), hash_kind_constant());
|
||||
defer { drop(&set); }
|
||||
|
||||
var i: usize = 0;
|
||||
@@ -213,7 +213,7 @@ fn test_collisions_and_take() {
|
||||
}
|
||||
|
||||
fn test_byte_view_keys() {
|
||||
var set = new(@sizeof(ByteView), @alignof(ByteView), hash_view, eq_view);
|
||||
var set = new(@sizeof(ByteView), @alignof(ByteView), hash_kind_view());
|
||||
defer { drop(&set); }
|
||||
|
||||
var alpha = ByteView.from_slice("alpha");
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .ByteView, .FindResult } = @import("byte_view.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .Map } = @import("map.spl");
|
||||
type .{ .Map, .hash_kind_view } = @import("map.spl");
|
||||
type .{ .hash_view, .eq_view } = @import("hash.spl");
|
||||
|
||||
type Position = struct {
|
||||
@@ -134,7 +134,7 @@ fn set_new(): Sources {
|
||||
ret Sources {
|
||||
.items = Vec.blank(@sizeof(Source), 8),
|
||||
.by_name = Map.blank(@sizeof(ByteView), @alignof(ByteView), @sizeof(usize),
|
||||
@alignof(usize), hash_view, eq_view),
|
||||
@alignof(usize), hash_kind_view()),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+44
-172
@@ -7,6 +7,18 @@ type .{ .Error, .Kind, .Status } = @import("error.spl");
|
||||
type .{ .SizeResult, .checked_add, .checked_mul, .grow_capacity, .move } = @import("memory.spl");
|
||||
type .{ .alloc, .copy, .free } = @import("../sys/mem.spl");
|
||||
|
||||
#extern(vm) fn vm_vec_valid(v: *any): i32;
|
||||
#extern(vm) fn vm_vec_push_raw(v: *any, elem: *any): i32;
|
||||
#extern(vm) fn vm_vec_push(v: *any, elem: *any): i32;
|
||||
#extern(vm) fn vm_vec_get(v: *any, index: usize, out: *any): i32;
|
||||
#extern(vm) fn vm_vec_allocate(out: *any, capacity: usize, elem_size: usize, elem_align: usize): i32;
|
||||
#extern(vm) fn vm_vec_reserve_raw(v: *any, need: usize): i32;
|
||||
#extern(vm) fn vm_vec_append(v: *any, elems: *any, count: usize): i32;
|
||||
#extern(vm) fn vm_vec_set(v: *any, index: usize, elem: *any): i32;
|
||||
#extern(vm) fn vm_vec_pop(v: *any, out: *any): i32;
|
||||
#extern(vm) fn vm_vec_clone(out: *any, src: *any): i32;
|
||||
#extern(vm) fn vm_vec_drop(v: *any): void;
|
||||
|
||||
type Vec = @self;
|
||||
|
||||
raw: *u8,
|
||||
@@ -51,95 +63,41 @@ fn new(elem_size: usize, elem_align: usize): VecResult {
|
||||
|
||||
// 按指针接收,避免每次调用复制整个 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 & (a - 1)) == 0;
|
||||
ret vm_vec_valid(self as *any) != 0;
|
||||
}
|
||||
|
||||
fn status_error(kind: Kind): Status {
|
||||
ret .Err(Error.make(kind));
|
||||
}
|
||||
|
||||
// native 错误码 -> Error.Kind(0 ok / 1 InvalidArgument / 2 OutOfRange / 3 Overflow / 4 InvalidAlignment)
|
||||
fn kind_from_code(code: i32): Kind {
|
||||
if code == 2 { ret .OutOfRange; }
|
||||
if code == 3 { ret .Overflow; }
|
||||
if code == 4 { ret .InvalidAlignment; }
|
||||
ret .InvalidArgument;
|
||||
}
|
||||
|
||||
fn status_from_code(code: i32): Status {
|
||||
if code == 0 { ret .Ok; }
|
||||
ret status_error(kind_from_code(code));
|
||||
}
|
||||
|
||||
// 计算元素区字节数。错误时不修改任何 Vec。
|
||||
fn byte_count(count: usize, elem_size: usize): SizeResult {
|
||||
ret checked_mul(count, elem_size);
|
||||
}
|
||||
|
||||
fn allocate(capacity: usize, elem_size: usize, elem_align: usize): VecResult {
|
||||
if elem_size == 0 { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if !valid_alignment(elem_align) {
|
||||
ret .Err(Error.make(.InvalidAlignment));
|
||||
}
|
||||
if capacity == 0 { ret .Ok(blank(elem_size, elem_align)); }
|
||||
|
||||
var bytes: usize = 0;
|
||||
match byte_count(capacity, elem_size) {
|
||||
.Ok(value) => { bytes = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var total: usize = 0;
|
||||
match checked_add(bytes, elem_align - 1) {
|
||||
.Ok(value) => { total = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var raw = alloc(total) as *u8;
|
||||
if raw == null {
|
||||
ret .Err(Error.make(.Overflow));
|
||||
}
|
||||
var address = raw as usize;
|
||||
var mask = elem_align - 1;
|
||||
if address > (~(0 as usize)) - mask {
|
||||
free(raw as *any);
|
||||
ret .Err(Error.make(.Overflow));
|
||||
}
|
||||
var aligned = (address + mask) & ~mask;
|
||||
ret .Ok(Vec {
|
||||
.raw = raw,
|
||||
.data = aligned as *u8,
|
||||
.len = 0,
|
||||
.cap = capacity,
|
||||
.elem_size = elem_size,
|
||||
.elem_align = elem_align,
|
||||
});
|
||||
var v: Vec = undefined;
|
||||
var rc = vm_vec_allocate(&v as *any, capacity, elem_size, elem_align);
|
||||
if rc == 0 { ret .Ok(v); }
|
||||
ret .Err(Error.make(kind_from_code(rc)));
|
||||
}
|
||||
|
||||
// 与 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);
|
||||
|
||||
var fresh: Vec = undefined;
|
||||
match allocate(new_cap, self.elem_size, self.elem_align) {
|
||||
.Ok(value) => { fresh = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var used_bytes: usize = 0;
|
||||
match byte_count(self.len, self.elem_size) {
|
||||
.Ok(value) => { used_bytes = value; },
|
||||
.Err(error) => {
|
||||
drop(&fresh);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
if used_bytes != 0 {
|
||||
copy(fresh.data as *any, self.data as *any, used_bytes);
|
||||
}
|
||||
fresh.len = self.len;
|
||||
|
||||
var old_raw = self.raw;
|
||||
self.* = fresh;
|
||||
free(old_raw as *any);
|
||||
ret .Ok;
|
||||
ret status_from_code(vm_vec_reserve_raw(self as *any, need));
|
||||
}
|
||||
|
||||
// 保证至少可容纳 need 个元素(外部入口,含校验)。
|
||||
@@ -154,81 +112,30 @@ fn with_capacity(elem_size: usize, elem_align: usize, capacity: usize): VecResul
|
||||
}
|
||||
|
||||
fn get(self: *Vec, index: usize): PtrResult {
|
||||
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);
|
||||
var out: *u8 = null;
|
||||
var rc = vm_vec_get(self as *any, index, &out as *any);
|
||||
if rc == 0 { ret .Ok(out); }
|
||||
if rc == 2 { ret .Err(Error.make(.OutOfRange)); }
|
||||
ret .Err(Error.make(.InvalidArgument));
|
||||
}
|
||||
|
||||
fn set(self: *Vec, index: usize, elem: *u8): Status {
|
||||
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]);
|
||||
ret status_from_code(vm_vec_set(self as *any, index, elem as *any));
|
||||
}
|
||||
|
||||
// 追加 count 个连续元素。elems 可以指向 self 当前已初始化的元素区。
|
||||
fn append(self: *Vec, elems: *u8, count: usize): Status {
|
||||
if !valid(self) { ret status_error(.InvalidArgument); }
|
||||
if count == 0 { ret .Ok; }
|
||||
if elems == null { ret status_error(.InvalidArgument); }
|
||||
|
||||
var copy_bytes: usize = 0;
|
||||
match byte_count(count, self.elem_size) {
|
||||
.Ok(value) => { copy_bytes = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var new_len: usize = 0;
|
||||
match checked_add(self.len, count) {
|
||||
.Ok(value) => { new_len = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var used_bytes: usize = 0;
|
||||
match byte_count(self.len, self.elem_size) {
|
||||
.Ok(value) => { used_bytes = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var source_inside = false;
|
||||
var source_offset: usize = 0;
|
||||
if used_bytes != 0 {
|
||||
var base = self.data as usize;
|
||||
var source = elems as usize;
|
||||
if source >= base && source - base < used_bytes {
|
||||
source_offset = source - base;
|
||||
if copy_bytes > used_bytes - source_offset {
|
||||
ret status_error(.OutOfRange);
|
||||
}
|
||||
source_inside = true;
|
||||
}
|
||||
}
|
||||
|
||||
match reserve_raw(self, new_len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if source_inside { elems = self.data + source_offset; }
|
||||
|
||||
var dst = self.data + used_bytes;
|
||||
match move(dst[0..copy_bytes], elems[0..copy_bytes]) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
self.len = new_len;
|
||||
ret .Ok;
|
||||
ret status_from_code(vm_vec_append(self as *any, elems as *any, count));
|
||||
}
|
||||
|
||||
fn push(self: *Vec, elem: *u8): Status {
|
||||
ret append(self, elem, 1);
|
||||
ret status_from_code(vm_vec_push(self as *any, elem as *any));
|
||||
}
|
||||
|
||||
// 热路径:假定 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;
|
||||
}
|
||||
if elem == null { ret false; }
|
||||
if vm_vec_push_raw(self as *any, elem as *any) != 0 { ret true; }
|
||||
match append(self, elem, 1) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
@@ -238,15 +145,7 @@ fn push_raw(self: *Vec, elem: *u8): bool {
|
||||
|
||||
// 将末元素复制到 out,然后缩短数组。out 可以与元素区重叠。
|
||||
fn pop(self: *Vec, out: *u8): Status {
|
||||
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]) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
self.len = self.len - 1;
|
||||
ret .Ok;
|
||||
ret status_from_code(vm_vec_pop(self as *any, out as *any));
|
||||
}
|
||||
|
||||
fn clear(self: *Vec) {
|
||||
@@ -256,31 +155,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 out.elem_size != src.elem_size || out.elem_align != src.elem_align {
|
||||
ret status_error(.InvalidArgument);
|
||||
}
|
||||
|
||||
var fresh = blank(src.elem_size, src.elem_align);
|
||||
match reserve(&fresh, src.len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var bytes: usize = 0;
|
||||
match byte_count(src.len, src.elem_size) {
|
||||
.Ok(value) => { bytes = value; },
|
||||
.Err(error) => {
|
||||
drop(&fresh);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
if bytes != 0 {
|
||||
copy(fresh.data as *any, src.data as *any, bytes);
|
||||
}
|
||||
fresh.len = src.len;
|
||||
drop(out);
|
||||
out.* = fresh;
|
||||
ret .Ok;
|
||||
ret status_from_code(vm_vec_clone(out as *any, &src as *any));
|
||||
}
|
||||
|
||||
// 显式转移所有权,返回值必须由接收方最终 drop。
|
||||
@@ -291,10 +166,7 @@ fn take(source: *Vec): Vec {
|
||||
}
|
||||
|
||||
fn drop(self: *Vec) {
|
||||
var elem_size = self.elem_size;
|
||||
var elem_align = self.elem_align;
|
||||
free(self.raw as *any);
|
||||
self.* = blank(elem_size, elem_align);
|
||||
vm_vec_drop(self as *any);
|
||||
}
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): i32 {
|
||||
ret 0;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Expect retcode = 1
|
||||
|
||||
fn main(): i32 {
|
||||
var x: i32 = 1;
|
||||
ret x;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): i32 {
|
||||
var x: i32 = undefined;
|
||||
x = 0;
|
||||
ret x;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Expect retcode = 42
|
||||
|
||||
fn main(): i32 {
|
||||
var x: i32 = undefined;
|
||||
x = 1;
|
||||
var y = 6 + x;
|
||||
x -= y;
|
||||
y *= x * -1;
|
||||
ret y;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Expect retcode = 6
|
||||
|
||||
fn add(a: i32, b: i32): i32 {
|
||||
ret a + b;
|
||||
}
|
||||
|
||||
fn main(): i32 {
|
||||
var a = add(1, 2);
|
||||
ret add(a, 3);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Expect retcode = 10
|
||||
|
||||
fn main(): i32 {
|
||||
var result: i32 = 0;
|
||||
var x: i32 = 0;
|
||||
if x != 0 {
|
||||
result += 1;
|
||||
} else {
|
||||
result += 2;
|
||||
}
|
||||
|
||||
x = 2;
|
||||
if x == 1 {
|
||||
result += 4;
|
||||
} else if x == 2 {
|
||||
result += 8;
|
||||
} else {
|
||||
result += 16;
|
||||
}
|
||||
ret result;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Expect retcode = 10
|
||||
|
||||
fn main(): i32 {
|
||||
var i: i32 = 0;
|
||||
while i < 10 {
|
||||
i = i + 1;
|
||||
}
|
||||
ret i;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Expect retcode = 20
|
||||
|
||||
fn main(): i32 {
|
||||
var res: i32 = 0;
|
||||
var array: [5]i32 = .[ 1, 2, 3, 4, 5 ];
|
||||
var slice: []i32 = array[1..3];
|
||||
|
||||
var i: u64 = undefined;
|
||||
|
||||
i = 0;
|
||||
while i < array.len {
|
||||
res += array[i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < slice.len {
|
||||
res += slice[i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
ret res;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Expect retcode = 4
|
||||
|
||||
fn main(): i32 {
|
||||
var str = "hello world";
|
||||
ret str[2] as i32 - str[0] as i32;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Expect retcode = 0
|
||||
// Expect stdout = `hello vm_printf\n1 0 -1 65536\n`
|
||||
#extern(vm) fn vm_printf(fmt: []u8, ...);
|
||||
|
||||
fn main(): () {
|
||||
vm_printf("hello vm_printf\n");
|
||||
vm_printf("%d %d %d %d\n", 1, 0, -1, 65536);
|
||||
vm_printf("%s %d %s\n", "hello world", 666, "hello spl");
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): i32 {
|
||||
@assert(0 == 0);
|
||||
@assert(0 != 1);
|
||||
@assert(1 != 0);
|
||||
@assert(1 == 1);
|
||||
@assert(true);
|
||||
@assert(!false);
|
||||
ret 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Vector2i = struct {
|
||||
x: isize,
|
||||
y: isize,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var v = Vector2i {};
|
||||
v.x = -2;
|
||||
v.y = 3;
|
||||
@assert(v.x * v.y == -6);
|
||||
@assert((v.x * v.y != -6) == false);
|
||||
@assert((v.x * v.y * -1) as i32 == 6);
|
||||
|
||||
var a: Vector2i = .{ .y = 2 };
|
||||
@assert(a.x == 0);
|
||||
@assert(a.y == 2);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Expect retcode = 0
|
||||
// non-optimized
|
||||
|
||||
type Rectangle = struct {
|
||||
x: isize,
|
||||
y: isize
|
||||
}
|
||||
|
||||
type Shape = enum {
|
||||
square: struct { x: isize },
|
||||
rectangle: Rectangle,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var s: Shape = .{ .square = .{ .x = 6 } };
|
||||
@assert(s.square.x == 6);
|
||||
// non-optimized
|
||||
@assert(s.rectangle.x == 6);
|
||||
// non-optimized
|
||||
@assert(s.rectangle.y == 0);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Rectangle = struct {
|
||||
fn area(self: *@self): isize {
|
||||
ret self.x * self.y;
|
||||
}
|
||||
x: isize,
|
||||
y: isize,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var r = Rectangle { .x = 2, .y = 3 };
|
||||
@assert(r.area(&r) == 6, "method call");
|
||||
@assert(Rectangle.area(&r) == 6, "type call");
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Outer = struct {
|
||||
inner: Inner,
|
||||
fn total(self: *@self): i32 {
|
||||
ret self.compute(self) + constant_value();
|
||||
}
|
||||
fn compute(self: *@self): i32 {
|
||||
ret self.inner.value;
|
||||
}
|
||||
}
|
||||
|
||||
type Inner = struct {
|
||||
value: i32,
|
||||
fn doubled(self: *@self): i32 {
|
||||
ret self.value * 2;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var i: Inner = .{ .value = 21 };
|
||||
@assert(i.doubled(&i) == 42);
|
||||
|
||||
var o: Outer = .{ .inner = i };
|
||||
@assert(o.total(&o) == 21 + 7);
|
||||
}
|
||||
|
||||
fn constant_value(): i32 {
|
||||
ret 7;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main() {
|
||||
var res = add((1, 2), (3, 4));
|
||||
@assert(res[0] == 4);
|
||||
@assert(res[1] == 6);
|
||||
@assert(res[1] + res[0] == 10);
|
||||
}
|
||||
|
||||
fn add(a: (i32, i32), b: (i32, i32)): (i32, i32) {
|
||||
ret (a[0] + b[0], a[1] + b[1]);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main() {
|
||||
var count: i32 = 0;
|
||||
$outter while count < 10 {
|
||||
$inner loop {
|
||||
count = count + 1;
|
||||
if count == 3 {
|
||||
continue $outter;
|
||||
}
|
||||
if count == 7 {
|
||||
break $outter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@assert(count == 7);
|
||||
|
||||
count = 0;
|
||||
while count < 10 {
|
||||
count = count + 1;
|
||||
if count == 5 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@assert(count == 5);
|
||||
|
||||
count = 0;
|
||||
var sum: i32 = 0;
|
||||
while count < 5 {
|
||||
count = count + 1;
|
||||
if count == 3 {
|
||||
continue;
|
||||
}
|
||||
sum = sum + count;
|
||||
}
|
||||
@assert(sum == 12);
|
||||
|
||||
count = 0;
|
||||
var outer_hits: i32 = 0;
|
||||
$a loop {
|
||||
$b loop {
|
||||
count = count + 1;
|
||||
if count == 2 {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
outer_hits = outer_hits + 1;
|
||||
if outer_hits == 3 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@assert(outer_hits == 3);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
var g: i32 = 0;
|
||||
|
||||
fn f1() {
|
||||
defer { g = g + 1; }
|
||||
defer { g = g * 10; }
|
||||
}
|
||||
|
||||
fn f2(): i32 {
|
||||
var x: i32 = 42;
|
||||
defer { x = 100; }
|
||||
ret x;
|
||||
}
|
||||
|
||||
fn f3(): i32 {
|
||||
defer { g = 999; }
|
||||
ret 7;
|
||||
}
|
||||
|
||||
fn f4(): i32 {
|
||||
var acc: i32 = 0;
|
||||
acc = acc + 1;
|
||||
defer { acc = acc + 100; }
|
||||
acc = acc + 10;
|
||||
ret acc;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// 1. LIFO:先注册的 defer 最后执行
|
||||
g = 0;
|
||||
f1();
|
||||
@assert(g == 1); // 0 * 10 = 0, 0 + 1 = 1
|
||||
|
||||
// 2. ret 先求值再 defer,defer 改不了返回值
|
||||
g = 0;
|
||||
var r2: i32 = f2();
|
||||
@assert(r2 == 42);
|
||||
@assert(g == 0);
|
||||
|
||||
// 3. defer 在 ret 前执行
|
||||
g = 0;
|
||||
var r3: i32 = f3();
|
||||
@assert(r3 == 7);
|
||||
@assert(g == 999);
|
||||
|
||||
// 4. defer 里的代码对 ret 表达式的结果无影响
|
||||
var r4: i32 = f4();
|
||||
@assert(r4 == 11); // acc = 1 + 10 = 11
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main() {
|
||||
var str: []u8 = undefined;
|
||||
str = "hello world";
|
||||
var p: *any = str.ptr;
|
||||
@assert((p as *u8)[1] == 'e');
|
||||
p = null;
|
||||
@assert(p == null);
|
||||
p = str.ptr as *u16 + 1;
|
||||
@assert((p as *u8)[0] == 'l');
|
||||
@assert((p as *u8).* == 'l');
|
||||
@assert((&(p as *u8)[1])[1] == 'o');
|
||||
@assert((&(p as *u8).*)[2] == 'o');
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Rectangle = @import("014_method.spl").Rectangle;
|
||||
|
||||
fn main() {
|
||||
var r = Rectangle { .x = 2, .y = 3 };
|
||||
@assert(r.area(&r) == 6);
|
||||
@assert(Rectangle.area(&r) == 6);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Node = struct {
|
||||
val: i32,
|
||||
next: *Node,
|
||||
};
|
||||
|
||||
fn sum(n: *Node): i32 {
|
||||
if n == null {
|
||||
ret 0;
|
||||
}
|
||||
ret n.val + sum(n.next);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var a = Node { .val = 1, .next = null };
|
||||
var b = Node { .val = 2, .next = &a };
|
||||
var c = Node { .val = 3, .next = &b };
|
||||
@assert(sum(&c) == 6);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type A = struct {
|
||||
tag: i32,
|
||||
b: *B,
|
||||
};
|
||||
|
||||
type B = struct {
|
||||
tag: i32,
|
||||
a: *A,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
var a = A { .tag = 1, .b = null };
|
||||
var b = B { .tag = 2, .a = &a };
|
||||
a.b = &b;
|
||||
@assert(a.b.tag == 2);
|
||||
@assert(b.a.tag == 1);
|
||||
@assert(a.b.a.tag == 1);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Rectangle = @import("014_method.spl").Rectangle;
|
||||
|
||||
fn make_rect(): Rectangle {
|
||||
ret Rectangle { .x = 5, .y = 7 };
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var r = make_rect();
|
||||
@assert(r.area(&r) == 35);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Option = enum {
|
||||
None,
|
||||
Some: i32,
|
||||
}
|
||||
|
||||
fn test_unit_variant() {
|
||||
var x: Option = .{ .None };
|
||||
var .None = x;
|
||||
}
|
||||
|
||||
fn test_payload_variant() {
|
||||
var x: Option = .{ .Some = 42 };
|
||||
var .Some(n) = x;
|
||||
@assert(n == 42, "some payload");
|
||||
}
|
||||
|
||||
fn unwrap_some(o: Option): i32 {
|
||||
var .Some(n) = o;
|
||||
ret n;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_unit_variant();
|
||||
test_payload_variant();
|
||||
|
||||
var x: Option = .{ .Some = 7 };
|
||||
@assert(unwrap_some(x) == 7, "cross-fn unwrap");
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Option = enum {
|
||||
None,
|
||||
Some: i32,
|
||||
};
|
||||
|
||||
fn make_pair(): (i32, i32) { ret (1, 2); }
|
||||
fn make_some(v: i32): Option { ret .Some(v); }
|
||||
|
||||
fn test_ident() {
|
||||
if var x = 42 {
|
||||
@assert(x == 42, "ifvar ident x==42");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_tuple() {
|
||||
if var (a, b) = make_pair() {
|
||||
@assert(a == 1, "ifvar tuple a==1");
|
||||
@assert(b == 2, "ifvar tuple b==2");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_enum_sugar() {
|
||||
if var .Some(n) = make_some(7) {
|
||||
@assert(n == 7, "ifvar enum sugar n==7");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_enum_unified() {
|
||||
if var .{ .Some = n } = make_some(9) {
|
||||
@assert(n == 9, "ifvar enum unified n==9");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_enum_unit() {
|
||||
var o: Option = .None;
|
||||
if var .None = o {
|
||||
@assert(true, "ifvar enum unit reached");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_else_path() {
|
||||
var hit = 0;
|
||||
if var x = 42 {
|
||||
hit = 1;
|
||||
} else {
|
||||
hit = 2;
|
||||
}
|
||||
@assert(hit == 1, "ifvar else-path hit==1");
|
||||
}
|
||||
|
||||
fn test_typed() {
|
||||
if var x: i32 = 100 {
|
||||
@assert(x == 100, "ifvar typed x==100");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_ident();
|
||||
test_tuple();
|
||||
test_enum_sugar();
|
||||
test_enum_unified();
|
||||
test_enum_unit();
|
||||
test_else_path();
|
||||
test_typed();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Option = enum {
|
||||
None,
|
||||
Some: i32,
|
||||
};
|
||||
|
||||
fn test_literal() {
|
||||
var x: i32 = 2;
|
||||
var r: i32 = 0;
|
||||
match x {
|
||||
1 => { r = 10; },
|
||||
2 => { r = 20; },
|
||||
_ => { r = -1; },
|
||||
}
|
||||
@assert(r == 20, "match literal r==20");
|
||||
}
|
||||
|
||||
fn test_wildcard_only() {
|
||||
var x: i32 = 42;
|
||||
var r: i32 = 0;
|
||||
match x {
|
||||
_ => { r = 1; },
|
||||
}
|
||||
@assert(r == 1, "match wildcard r==1");
|
||||
}
|
||||
|
||||
fn test_enum_unit() {
|
||||
var o: Option = .None;
|
||||
var r: i32 = 0;
|
||||
match o {
|
||||
.None => { r = 1; },
|
||||
.Some(n) => { r = n; },
|
||||
}
|
||||
@assert(r == 1, "match enum unit r==1");
|
||||
}
|
||||
|
||||
fn test_enum_payload() {
|
||||
var o: Option = .Some(42);
|
||||
var r: i32 = 0;
|
||||
match o {
|
||||
.None => { r = 0; },
|
||||
.Some(n) => { r = n; },
|
||||
}
|
||||
@assert(r == 42, "match enum payload r==42");
|
||||
}
|
||||
|
||||
fn test_multi_pattern() {
|
||||
var x: i32 = 3;
|
||||
var r: i32 = 0;
|
||||
match x {
|
||||
1, 2, 3 => { r = 123; },
|
||||
_ => { r = 0; },
|
||||
}
|
||||
@assert(r == 123, "match multi r==123");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_literal();
|
||||
test_wildcard_only();
|
||||
test_enum_unit();
|
||||
test_enum_payload();
|
||||
test_multi_pattern();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type LibA = struct {
|
||||
fn a1(): i32 { ret 1; }
|
||||
};
|
||||
|
||||
type LibB = struct {
|
||||
fn b1(): i32 { ret 10; }
|
||||
fn b2(): i32 { ret 20; }
|
||||
};
|
||||
|
||||
type LibC = struct {
|
||||
fn c1(): i32 { ret 100; }
|
||||
fn c2(): i32 { ret 200; }
|
||||
fn c3(): i32 { ret 300; }
|
||||
};
|
||||
|
||||
type LibMixed = struct {
|
||||
type Byte = u8;
|
||||
type Word = u16;
|
||||
fn identity(v: i32): i32 { ret v; }
|
||||
};
|
||||
|
||||
type MyInt = i32;
|
||||
|
||||
type .a1 = LibA;
|
||||
|
||||
type .{ .b1, .b2 } = LibB;
|
||||
|
||||
type .c1, .c2, .c3 = LibC;
|
||||
|
||||
type .{ .Byte, .Word, .identity } = LibMixed;
|
||||
|
||||
fn test_alias_regression() {
|
||||
var x: MyInt = 10;
|
||||
@assert(x == 10, "type alias MyInt still works");
|
||||
}
|
||||
|
||||
fn test_single_unpack() {
|
||||
@assert(a1() == 1, "single unpack a1()=1");
|
||||
}
|
||||
|
||||
fn test_aggregate_unpack() {
|
||||
@assert(b1() == 10, "aggregate unpack b1()=10");
|
||||
@assert(b2() == 20, "aggregate unpack b2()=20");
|
||||
}
|
||||
|
||||
fn test_list_unpack() {
|
||||
@assert(c1() == 100, "list unpack c1()=100");
|
||||
@assert(c2() == 200, "list unpack c2()=200");
|
||||
@assert(c3() == 300, "list unpack c3()=300");
|
||||
}
|
||||
|
||||
fn test_type_and_fn_unpack() {
|
||||
var b: Byte = 255;
|
||||
@assert(b == 255, "Byte type unpack works");
|
||||
|
||||
var w: Word = 1000;
|
||||
@assert(w == 1000, "Word type unpack works");
|
||||
|
||||
@assert(identity(42) == 42, "identity unpack works");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_alias_regression();
|
||||
test_single_unpack();
|
||||
test_aggregate_unpack();
|
||||
test_list_unpack();
|
||||
test_type_and_fn_unpack();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Str = @self;
|
||||
|
||||
ptr: *u8,
|
||||
len: u64,
|
||||
|
||||
fn empty(): Str {
|
||||
ret Str { .ptr = null, .len = 0 };
|
||||
}
|
||||
|
||||
fn from_str(s: []u8): Str {
|
||||
ret Str { .ptr = s.ptr, .len = s.len };
|
||||
}
|
||||
|
||||
fn size(self: *Str): u64 {
|
||||
ret self.len;
|
||||
}
|
||||
|
||||
fn at(self: *Str, idx: u64): u8 {
|
||||
@assert(idx < self.len);
|
||||
ret self.ptr[idx];
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
type Str = @import("028_self_import.spl");
|
||||
|
||||
fn main() {
|
||||
var s = Str.from_str("hello");
|
||||
@assert(s.len == 5, "hello len");
|
||||
@assert(s.ptr[0] == 104, "h");
|
||||
@assert(s.ptr[4] == 111, "o");
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn rhs_and() : bool {
|
||||
@assert(false, "right side of && evaluated");
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn rhs_or() : bool {
|
||||
@assert(false, "right side of || evaluated");
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn main() : i32 {
|
||||
var a: bool = false && rhs_and();
|
||||
if a {
|
||||
ret 1
|
||||
}
|
||||
|
||||
var b: bool = true || rhs_or();
|
||||
if !b {
|
||||
ret 2
|
||||
}
|
||||
|
||||
var c: bool = true && true;
|
||||
if !c {
|
||||
ret 3
|
||||
}
|
||||
|
||||
var d: bool = false || true;
|
||||
if !d {
|
||||
ret 4
|
||||
}
|
||||
|
||||
var e: bool = false && false;
|
||||
if e {
|
||||
ret 5
|
||||
}
|
||||
|
||||
var f: bool = true || false;
|
||||
if !f {
|
||||
ret 6
|
||||
}
|
||||
|
||||
var g: bool = (false || false) && rhs_and();
|
||||
if g {
|
||||
ret 7
|
||||
}
|
||||
|
||||
var h: bool = (true && true) || rhs_or();
|
||||
if !h {
|
||||
ret 8
|
||||
}
|
||||
|
||||
ret 0
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 函数指针 = 函数名本身;fn 类型按可空指针处理(null 表示空函数指针)。
|
||||
// 覆盖:裸函数名取值、null 初始化/赋值/比较、聚合字段、函数返回、间接调用。
|
||||
// 不覆盖 &: 对函数标识符取地址是编译错误,由负向用例另行验证。
|
||||
|
||||
type Table = @self;
|
||||
|
||||
hash_fn: fn(*u8): usize,
|
||||
eq_fn: fn(*u8, *u8): bool,
|
||||
|
||||
fn hash_byte(p: *u8): usize {
|
||||
ret (p.* as usize) + 1;
|
||||
}
|
||||
|
||||
fn eq_byte(a: *u8, b: *u8): bool {
|
||||
ret a.* == b.*;
|
||||
}
|
||||
|
||||
fn pick_ok(): fn(*u8): usize {
|
||||
ret hash_byte;
|
||||
}
|
||||
|
||||
fn pick_null(): fn(*u8): usize {
|
||||
ret null;
|
||||
}
|
||||
|
||||
var g_fn: fn(*u8): usize = hash_byte;
|
||||
var g_table: Table = Table { .hash_fn = hash_byte, .eq_fn = eq_byte };
|
||||
|
||||
fn main() {
|
||||
// 首个被翻译的函数地址必须非零(否则会被误判为 null)。
|
||||
@assert(hash_byte != null, "first function is not null");
|
||||
@assert(pick_ok() != null, "return function is not null");
|
||||
|
||||
// 局部变量:裸函数名取值、可空。
|
||||
var h: fn(*u8): usize = hash_byte;
|
||||
@assert(h != null, "local function not null");
|
||||
@assert(h == g_fn, "local equals global");
|
||||
|
||||
var n: fn(*u8): usize = null;
|
||||
@assert(n == null, "null initializes function pointer");
|
||||
|
||||
h = null;
|
||||
@assert(h == null, "assign null to function pointer");
|
||||
h = hash_byte;
|
||||
@assert(h != null, "reassign function pointer");
|
||||
|
||||
// 全局函数指针与聚合字段。
|
||||
@assert(g_table.hash_fn != null, "global aggregate field");
|
||||
var z = Table { .hash_fn = null, .eq_fn = null };
|
||||
@assert(z.hash_fn == null && z.eq_fn == null, "null aggregate fields");
|
||||
z.hash_fn = hash_byte;
|
||||
@assert(z.hash_fn != null, "reassign aggregate field");
|
||||
|
||||
// 函数返回 null / 函数值。
|
||||
@assert(pick_null() == null, "return null function");
|
||||
|
||||
// 间接调用。
|
||||
var x: u8 = 41;
|
||||
@assert(g_table.hash_fn(&x) == 42, "indirect call via field");
|
||||
@assert(h(&x) == 42, "indirect call via local");
|
||||
@assert(g_table.eq_fn(&x, &x), "indirect call with two args");
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Expect retcode = 0
|
||||
// @enum2int:取 enum 的 tag(判别值)。注意 `as` 不允许 enum<->int。
|
||||
|
||||
type Color = enum {
|
||||
Red,
|
||||
Green: i32,
|
||||
Blue,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var r: Color = .Red;
|
||||
var g: Color = .Green(7);
|
||||
var b: Color = .Blue;
|
||||
|
||||
@assert(@enum2int(r) == 0, "Red tag");
|
||||
@assert(@enum2int(g) == 1, "Green tag");
|
||||
@assert(@enum2int(b) == 2, "Blue tag");
|
||||
|
||||
var as_usize: usize = @enum2int(b) as usize;
|
||||
@assert(as_usize == 2, "tag to usize");
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Expect retcode = 0
|
||||
// @offsetof / @field_count / @sizeof / @alignof 全部来自 layout。
|
||||
|
||||
type P = struct { a: u8, b: usize, c: u32 }
|
||||
type U = union { x: u8, y: u64 }
|
||||
|
||||
fn main() {
|
||||
@assert(@field_count(P) == 3, "P field_count");
|
||||
@assert(@offsetof(P, a) == 0, "P.a");
|
||||
@assert(@offsetof(P, b) == 8, "P.b");
|
||||
@assert(@offsetof(P, c) == 16, "P.c");
|
||||
@assert(@sizeof(P) == 24, "P size");
|
||||
@assert(@alignof(P) == 8, "P align");
|
||||
|
||||
@assert(@field_count(U) == 2, "U field_count");
|
||||
@assert(@offsetof(U, y) == 0, "U.y shared");
|
||||
@assert(@sizeof(U) == 8, "U size");
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Expect retcode = 0
|
||||
// 对函数返回的临时结构体取字段(rvalue.field)应可用:
|
||||
// 直接取值、作为实参、参与运算等。
|
||||
// 现状缺陷:ast2ir 对非左值基对象的字段访问返回空节点且不报错,导致静默得到 0。
|
||||
|
||||
type Pair = struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
fn make(a: i32, b: i32): Pair {
|
||||
ret Pair { .x = a, .y = b };
|
||||
}
|
||||
|
||||
fn id(x: i32): i32 {
|
||||
ret x;
|
||||
}
|
||||
|
||||
fn main(): i32 {
|
||||
var direct = make(3, 4).y;
|
||||
@assert(direct == 4, "direct rvalue field");
|
||||
|
||||
@assert(id(make(5, 6).x) == 5, "rvalue field as argument");
|
||||
|
||||
@assert(make(1, 2).x + make(3, 4).y == 5, "two rvalues combined");
|
||||
|
||||
ret 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Expect retcode = 0
|
||||
// 用「枚举类型名.变体」限定访问枚举值(EnumType.Variant)。
|
||||
// 现状缺陷:会被当作普通成员访问,sema 报 no_field: cannot resolve field type。
|
||||
|
||||
type Color = enum {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
}
|
||||
|
||||
fn main(): i32 {
|
||||
@assert(@enum2int(Color.Red) == 0, "Red tag");
|
||||
@assert(@enum2int(Color.Green) == 1, "Green tag");
|
||||
@assert(@enum2int(Color.Blue) == 2, "Blue tag");
|
||||
|
||||
var c: Color = Color.Blue;
|
||||
@assert(@enum2int(c) == 2, "qualified assignment");
|
||||
|
||||
ret 0;
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_literals();
|
||||
test_var_types();
|
||||
test_arith();
|
||||
test_bitwise();
|
||||
test_shift();
|
||||
test_cmp();
|
||||
test_compound();
|
||||
test_as_trunc_ext();
|
||||
test_min_max();
|
||||
test_unsigned_wrap_free();
|
||||
}
|
||||
|
||||
fn test_literals() {
|
||||
// 十进制
|
||||
@assert(0 == 0, "dec 0");
|
||||
@assert(42 == 42, "dec 42");
|
||||
@assert(1234567 == 1234567, "dec 1234567");
|
||||
|
||||
// 十六进制
|
||||
@assert(0x0 == 0, "hex 0");
|
||||
@assert(0xFF == 255, "hex FF");
|
||||
@assert(0xDEAD == 57005, "hex DEAD");
|
||||
@assert(0x7FFFFFFF == 2147483647, "hex i32max");
|
||||
|
||||
// 八进制
|
||||
@assert(0o0 == 0, "oct 0");
|
||||
@assert(0o7 == 7, "oct 7");
|
||||
@assert(0o52 == 42, "oct 52");
|
||||
|
||||
// 二进制
|
||||
@assert(0b0 == 0, "bin 0");
|
||||
@assert(0b101 == 5, "bin 101");
|
||||
@assert(0b101010 == 42, "bin 101010");
|
||||
|
||||
// 下划线分隔
|
||||
@assert(1_000 == 1000, "1_000");
|
||||
@assert(1_000_000 == 1000000, "1_000_000");
|
||||
@assert(0xFF_FF == 65535, "hex underline");
|
||||
|
||||
// 负字面量
|
||||
@assert(-1 == 0 - 1, "-1");
|
||||
@assert(-42 == 0 - 42, "-42");
|
||||
}
|
||||
|
||||
fn test_var_types() {
|
||||
var a: i8 = 42;
|
||||
var b: i16 = 42;
|
||||
var c: i32 = 42;
|
||||
var d: i64 = 42;
|
||||
var e: u8 = 42;
|
||||
var f: u16 = 42;
|
||||
var g: u32 = 42;
|
||||
var h: u64 = 42;
|
||||
var i: isize = 42;
|
||||
var j: usize = 42;
|
||||
|
||||
@assert(a == 42, "i8");
|
||||
@assert(b == 42, "i16");
|
||||
@assert(c == 42, "i32");
|
||||
@assert(d == 42, "i64");
|
||||
@assert(e == 42, "u8");
|
||||
@assert(f == 42, "u16");
|
||||
@assert(g == 42, "u32");
|
||||
@assert(h == 42, "u64");
|
||||
@assert(i == 42, "isize");
|
||||
@assert(j == 42, "usize");
|
||||
|
||||
// 类型推断
|
||||
var k = 100;
|
||||
@assert(k == 100, "infer");
|
||||
}
|
||||
|
||||
fn test_arith() {
|
||||
@assert(2 + 3 == 5, "add");
|
||||
@assert(10 - 3 == 7, "sub");
|
||||
@assert(6 * 7 == 42, "mul");
|
||||
@assert(20 / 4 == 5, "div");
|
||||
@assert(10 % 3 == 1, "rem");
|
||||
|
||||
// 优先级
|
||||
@assert(2 + 3 * 4 == 14, "prec 1");
|
||||
@assert((2 + 3) * 4 == 20, "parens");
|
||||
@assert(10 - 3 - 2 == 5, "left assoc sub");
|
||||
@assert(100 / 10 / 2 == 5, "left assoc div");
|
||||
@assert(2 * 3 % 4 == 2, "mul/rem left");
|
||||
|
||||
// 带类型
|
||||
var x: i32 = 10;
|
||||
var y: i32 = 3;
|
||||
@assert(x + y == 13, "i32 add");
|
||||
@assert(x - y == 7, "i32 sub");
|
||||
@assert(x * y == 30, "i32 mul");
|
||||
@assert(x / y == 3, "i32 div");
|
||||
@assert(x % y == 1, "i32 rem");
|
||||
|
||||
// 负数除法:向零截断
|
||||
var n: i32 = -7;
|
||||
@assert(n / 3 == -2, "neg div");
|
||||
@assert(n % 3 == -1, "neg rem");
|
||||
@assert(7 / (0 - 3) == -2, "div neg");
|
||||
@assert(7 % (0 - 3) == 1, "rem neg");
|
||||
@assert(n / (0 - 3) == 2, "neg div neg");
|
||||
@assert(n % (0 - 3) == -1, "neg rem neg");
|
||||
|
||||
// 恒等式:(a/b)*b + a%b == a
|
||||
var a: i32 = -17;
|
||||
var b: i32 = 5;
|
||||
@assert(a == (a / b) * b + a % b, "divrem identity");
|
||||
}
|
||||
|
||||
fn test_bitwise() {
|
||||
@assert((0b1100 & 0b1010) == 0b1000, "and");
|
||||
@assert((0b1100 | 0b1010) == 0b1110, "or");
|
||||
@assert((0b1100 ^ 0b1010) == 0b0110, "xor");
|
||||
|
||||
var x: i32 = 0x12345678;
|
||||
@assert((x & x) == x, "x&x");
|
||||
@assert((x | x) == x, "x|x");
|
||||
@assert((x ^ x) == 0, "x^x");
|
||||
@assert((x & 0) == 0, "x&0");
|
||||
@assert((x | 0) == x, "x|0");
|
||||
@assert((x ^ 0) == x, "x^0");
|
||||
|
||||
// 全 1 掩码
|
||||
@assert((x & (0 - 1)) == x, "x&all1");
|
||||
@assert((x | (0 - 1)) == (0 - 1), "x|all1");
|
||||
|
||||
// NOT
|
||||
@assert(~0 == 0 - 1, "~0");
|
||||
@assert(~(0 - 1) == 0, "~-1");
|
||||
|
||||
var y: i32 = 0;
|
||||
@assert(~y == 0 - 1, "~i32 0");
|
||||
|
||||
// 无符号
|
||||
var u: u32 = 0xF0F0F0F0;
|
||||
@assert((u & 0xFF) == 0xF0, "u32 & mask");
|
||||
@assert((u | 0) == u, "u32 | 0");
|
||||
}
|
||||
|
||||
fn test_shift() {
|
||||
var one: i32 = 1;
|
||||
@assert((one << 0) == 1, "shl 0");
|
||||
@assert((one << 1) == 2, "shl 1");
|
||||
@assert((one << 4) == 16, "shl 4");
|
||||
@assert((one << 30) == 1073741824, "shl 30");
|
||||
|
||||
// 算术右移:保留符号
|
||||
var neg1: i32 = 0 - 1;
|
||||
@assert((neg1 >> 1) == (0 - 1), "-1 >> 1");
|
||||
@assert(((0 - 2) >> 1) == (0 - 1), "-2 >> 1");
|
||||
@assert(((0 - 8) >> 2) == (0 - 2), "-8 >> 2");
|
||||
|
||||
// 正数右移
|
||||
@assert((8 >> 1) == 4, "8 >> 1");
|
||||
@assert((1 >> 1) == 0, "1 >> 1");
|
||||
|
||||
// 无符号逻辑右移
|
||||
var u: u32 = 0x80000000;
|
||||
@assert((u >> 1) == 0x40000000, "u32 >> 1");
|
||||
|
||||
// i64
|
||||
var big: i64 = 1;
|
||||
@assert((big << 40) == 1099511627776, "i64 << 40");
|
||||
@assert((big << 62) == 4611686018427387904, "i64 << 62");
|
||||
}
|
||||
|
||||
fn test_cmp() {
|
||||
@assert(1 < 2, "1<2");
|
||||
@assert(1 <= 1, "1<=1");
|
||||
@assert(2 > 1, "2>1");
|
||||
@assert(2 >= 2, "2>=2");
|
||||
@assert(1 == 1, "1==1");
|
||||
@assert(1 != 2, "1!=2");
|
||||
@assert(!(2 < 1), "!(2<1)");
|
||||
@assert(!(1 <= 0), "!(1<=0)");
|
||||
|
||||
// 有符号
|
||||
var a: i32 = 0 - 1;
|
||||
var b: i32 = 1;
|
||||
@assert(a < b, "-1 < 1");
|
||||
@assert(b > a, "1 > -1");
|
||||
|
||||
// 无符号:0xFFFFFFFF 应该 > 1
|
||||
var ua: u32 = 0xFFFFFFFF;
|
||||
var ub: u32 = 1;
|
||||
@assert(ua > ub, "u32 max > 1");
|
||||
@assert(ub < ua, "1 < u32 max");
|
||||
|
||||
// 跨类型比较:comptime 与 typed
|
||||
var c: i32 = 42;
|
||||
@assert(c == 42, "i32 == literal");
|
||||
@assert(c > 41, "i32 > literal");
|
||||
@assert(c <= 42, "i32 <= literal");
|
||||
}
|
||||
|
||||
fn test_compound() {
|
||||
var x: i32 = 10;
|
||||
x += 5; @assert(x == 15, "+= ");
|
||||
x -= 3; @assert(x == 12, "-=");
|
||||
x *= 2; @assert(x == 24, "*=");
|
||||
x /= 4; @assert(x == 6, "/=");
|
||||
x %= 4; @assert(x == 2, "%=");
|
||||
x <<= 2; @assert(x == 8, "<<=");
|
||||
x >>= 1; @assert(x == 4, ">>=");
|
||||
x &= 6; @assert(x == 4, "&=");
|
||||
x |= 1; @assert(x == 5, "|=");
|
||||
x ^= 3; @assert(x == 6, "^=");
|
||||
}
|
||||
|
||||
fn test_as_trunc_ext() {
|
||||
// 窄化:截断到低位(补码)
|
||||
var a: i32 = 1000;
|
||||
var a8: i8 = a as i8;
|
||||
// 1000 & 0xFF = 0xE8 = -24 (i8)
|
||||
@assert(a8 == (0 - 24), "i32 1000 -> i8 -24");
|
||||
|
||||
var b: i32 = 127;
|
||||
var b8: i8 = b as i8;
|
||||
@assert(b8 == 127, "127 -> i8");
|
||||
|
||||
var c: i32 = 128;
|
||||
var c8: i8 = c as i8;
|
||||
@assert(c8 == 0 - 127 - 1, "128 -> i8 -128");
|
||||
|
||||
// 宽化有符号:符号扩展
|
||||
var d8: i8 = 0 - 1;
|
||||
var d64: i64 = d8 as i64;
|
||||
@assert(d64 == (0 - 1), "i8 -1 -> i64 -1");
|
||||
|
||||
var e8: i8 = 0 - 127 - 1;
|
||||
var e32: i32 = e8 as i32;
|
||||
@assert(e32 == (0 - 128), "i8 -128 -> i32 -128");
|
||||
|
||||
// 宽化无符号:零扩展
|
||||
var f8: u8 = 255;
|
||||
var f32: i32 = f8 as i32;
|
||||
@assert(f32 == 255, "u8 255 -> i32 255");
|
||||
|
||||
var g8: u8 = 200;
|
||||
var g64: i64 = g8 as i64;
|
||||
@assert(g64 == 200, "u8 200 -> i64 200");
|
||||
|
||||
// i8 -> u8 位模式不变
|
||||
var h8: i8 = 0 - 1;
|
||||
var h8u: u8 = h8 as u8;
|
||||
@assert(h8u == 255, "i8 -1 -> u8 255");
|
||||
|
||||
// 组合截断+扩展
|
||||
var k: i64 = 0x1_0000_0001;
|
||||
var k32: i32 = k as i32;
|
||||
@assert(k32 == 1, "i64 -> i32 trunc");
|
||||
}
|
||||
|
||||
fn test_min_max() {
|
||||
// i32 边界
|
||||
var max: i32 = 2147483647;
|
||||
var min: i32 = (0 - 2147483647) - 1;
|
||||
@assert(max == 0x7FFFFFFF, "i32 max");
|
||||
@assert(min == 0 - 2147483647 - 1, "i32 min");
|
||||
|
||||
// 边界比较
|
||||
@assert(max > min, "max > min");
|
||||
@assert(max >= max, "max >= max");
|
||||
@assert(min <= min, "min <= min");
|
||||
|
||||
// -max 与 -max-1
|
||||
@assert((0 - max) == (0 - 2147483647), "-max");
|
||||
@assert(((0 - max) - 1) == min, "-max-1 == min");
|
||||
|
||||
// i8 边界
|
||||
var i8max: i8 = 127;
|
||||
var i8min: i8 = (0 - 127) - 1;
|
||||
@assert(i8max == 127, "i8 max");
|
||||
@assert(i8min == 0 - 127 - 1, "i8 min");
|
||||
|
||||
// u8 边界
|
||||
var u8max: u8 = 255;
|
||||
@assert(u8max == 255, "u8 max");
|
||||
@assert((u8max as i32) == 255, "u8 max as i32");
|
||||
|
||||
// i64 边界
|
||||
var i64max: i64 = 9223372036854775807;
|
||||
@assert(i64max == 9223372036854775807, "i64 max");
|
||||
}
|
||||
|
||||
fn test_unsigned_wrap_free() {
|
||||
// 无符号运算不涉及负数
|
||||
var a: u32 = 0xFFFFFFFF;
|
||||
var b: u32 = 1;
|
||||
|
||||
// a + b 会溢出 u32,但作为字面量+变量组合,我们只测能表达的
|
||||
@assert((a & 0xFFFFFF00) == 0xFFFFFF00, "u32 mask");
|
||||
@assert((a | b) == 0xFFFFFFFF, "u32 |");
|
||||
@assert((a ^ a) == 0, "u32 ^ self");
|
||||
@assert((a >> 1) == 0x7FFFFFFF, "u32 >> 1");
|
||||
@assert((b << 31) == 0x80000000, "u32 << 31");
|
||||
|
||||
// usize / isize
|
||||
var i: isize = 0 - 1;
|
||||
@assert(i < 0, "isize -1");
|
||||
var u: usize = 100;
|
||||
@assert(u > 50, "usize 100 > 50");
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_literals();
|
||||
test_not();
|
||||
test_and_or();
|
||||
test_cmp_result();
|
||||
test_assign();
|
||||
}
|
||||
|
||||
fn test_literals() {
|
||||
var t: bool = true;
|
||||
var f: bool = false;
|
||||
@assert(t, "true");
|
||||
@assert(!f, "false");
|
||||
@assert(t == true, "t == true");
|
||||
@assert(f == false, "f == false");
|
||||
@assert(t != f, "t != f");
|
||||
}
|
||||
|
||||
fn test_not() {
|
||||
@assert(!true == false, "!true");
|
||||
@assert(!false == true, "!false");
|
||||
var t: bool = true;
|
||||
@assert(!t == false, "!t");
|
||||
@assert(!!t == true, "!!t");
|
||||
}
|
||||
|
||||
fn test_and_or() {
|
||||
// 纯表达式,不测副作用
|
||||
@assert(true && true, "T && T");
|
||||
@assert(!(true && false), "!(T && F)");
|
||||
@assert(!(false && true), "!(F && T)");
|
||||
@assert(!(false && false), "!(F && F)");
|
||||
|
||||
@assert(true || true, "T || T");
|
||||
@assert(true || false, "T || F");
|
||||
@assert(false || true, "F || T");
|
||||
@assert(!(false || false), "!(F || F)");
|
||||
|
||||
// 优先级:&& 高于 ||
|
||||
@assert(true || false && false, "T || (F && F)");
|
||||
@assert((true || false) && false == false, "(T||F) && F == F");
|
||||
|
||||
// 组合
|
||||
var a: i32 = 5;
|
||||
var b: i32 = 10;
|
||||
@assert(a < b && b < 20, "a<b && b<20");
|
||||
@assert(a > b || b > 5, "a>b || b>5");
|
||||
@assert(a == 5 && b == 10, "both eq");
|
||||
}
|
||||
|
||||
fn test_cmp_result() {
|
||||
// 比较返回 bool
|
||||
var x: bool = 1 < 2;
|
||||
@assert(x, "1<2 as bool");
|
||||
var y: bool = 1 == 2;
|
||||
@assert(!y, "1==2 as bool");
|
||||
|
||||
var a: i32 = 5;
|
||||
var b: bool = a > 0;
|
||||
@assert(b, "a > 0");
|
||||
var c: bool = a == 5;
|
||||
@assert(c, "a == 5");
|
||||
|
||||
// bool 变量作为条件
|
||||
if x { } else { @assert(false, "if x"); }
|
||||
if !y { } else { @assert(false, "if !y"); }
|
||||
}
|
||||
|
||||
fn test_assign() {
|
||||
var b: bool = false;
|
||||
b = true;
|
||||
@assert(b, "assign true");
|
||||
b = !b;
|
||||
@assert(!b, "assign !b");
|
||||
b = 1 < 2;
|
||||
@assert(b, "assign cmp");
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_if();
|
||||
test_if_else();
|
||||
test_while();
|
||||
test_while_else();
|
||||
test_loop_break();
|
||||
test_continue();
|
||||
test_nested();
|
||||
test_label();
|
||||
}
|
||||
|
||||
fn test_if() {
|
||||
var x: i32 = 10;
|
||||
if x > 5 {
|
||||
@assert(true, "x>5");
|
||||
} else {
|
||||
@assert(false, "x>5 else");
|
||||
}
|
||||
|
||||
if x < 5 {
|
||||
@assert(false, "x<5");
|
||||
} else {
|
||||
@assert(true, "x<5 else");
|
||||
}
|
||||
|
||||
// if 表达式求值:作为 tail
|
||||
var y: i32 = if x > 5 { 1 } else { 0 };
|
||||
@assert(y == 1, "if expr y");
|
||||
}
|
||||
|
||||
fn test_if_else() {
|
||||
var x: i32 = 5;
|
||||
if x < 0 {
|
||||
@assert(false, "x<0");
|
||||
} else if x == 0 {
|
||||
@assert(false, "x==0");
|
||||
} else if x == 5 {
|
||||
@assert(true, "x==5");
|
||||
} else {
|
||||
@assert(false, "else");
|
||||
}
|
||||
|
||||
// 嵌套
|
||||
if x > 0 {
|
||||
if x < 10 {
|
||||
@assert(true, "0<x<10");
|
||||
} else {
|
||||
@assert(false, "x>=10");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_while() {
|
||||
var i: i32 = 0;
|
||||
var sum: i32 = 0;
|
||||
while i < 10 {
|
||||
sum = sum + i;
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(i == 10, "i==10");
|
||||
@assert(sum == 45, "sum 0..9");
|
||||
|
||||
// 零次循环
|
||||
var n: i32 = 0;
|
||||
while n < 0 {
|
||||
@assert(false, "never");
|
||||
}
|
||||
@assert(n == 0, "n unchanged");
|
||||
}
|
||||
|
||||
fn test_while_else() {
|
||||
// while 的 else 分支:条件为假时进一次
|
||||
var i: i32 = 100;
|
||||
var hit_else: bool = false;
|
||||
while i < 10 {
|
||||
i = i + 1;
|
||||
} else {
|
||||
hit_else = true;
|
||||
}
|
||||
@assert(hit_else, "while else when cond false initially");
|
||||
|
||||
var j: i32 = 0;
|
||||
var hit_else2: bool = false;
|
||||
while j < 3 {
|
||||
j = j + 1;
|
||||
} else {
|
||||
hit_else2 = true;
|
||||
}
|
||||
@assert(j == 3, "j==3");
|
||||
@assert(hit_else2, "while else after normal end");
|
||||
}
|
||||
|
||||
fn test_loop_break() {
|
||||
var i: i32 = 0;
|
||||
loop {
|
||||
if i >= 5 { break; }
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(i == 5, "loop break");
|
||||
|
||||
// loop 表达式带 break 值
|
||||
var v: i32 = loop {
|
||||
break 42;
|
||||
};
|
||||
@assert(v == 42, "loop break value");
|
||||
}
|
||||
|
||||
fn test_continue() {
|
||||
var i: i32 = 0;
|
||||
var sum: i32 = 0;
|
||||
while i < 10 {
|
||||
i = i + 1;
|
||||
if i % 2 == 0 { continue; }
|
||||
sum = sum + i;
|
||||
}
|
||||
// 1 + 3 + 5 + 7 + 9 = 25
|
||||
@assert(sum == 25, "sum odds");
|
||||
|
||||
var j: i32 = 0;
|
||||
var cnt: i32 = 0;
|
||||
loop {
|
||||
j = j + 1;
|
||||
if j > 100 { break; }
|
||||
if j % 3 != 0 { continue; }
|
||||
cnt = cnt + 1;
|
||||
}
|
||||
// 3,6,...,99 共 33 个
|
||||
@assert(cnt == 33, "multiples of 3 <= 100");
|
||||
}
|
||||
|
||||
fn test_nested() {
|
||||
var i: i32 = 0;
|
||||
var total: i32 = 0;
|
||||
while i < 5 {
|
||||
var j: i32 = 0;
|
||||
while j < 5 {
|
||||
total = total + 1;
|
||||
j = j + 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(total == 25, "nested loops");
|
||||
|
||||
// 嵌套 break 只退一层
|
||||
var a: i32 = 0;
|
||||
var b: i32 = 0;
|
||||
while a < 3 {
|
||||
a = a + 1;
|
||||
while true {
|
||||
b = b + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@assert(a == 3, "outer a");
|
||||
@assert(b == 3, "inner b");
|
||||
}
|
||||
|
||||
fn test_label() {
|
||||
// 带标签的 break 跳出多层
|
||||
var hit: i32 = 0;
|
||||
$outer while true {
|
||||
var i: i32 = 0;
|
||||
while i < 10 {
|
||||
i = i + 1;
|
||||
hit = hit + 1;
|
||||
if hit > 5 {
|
||||
break $outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@assert(hit == 6, "label break hit");
|
||||
|
||||
// 带标签的 continue
|
||||
var sum: i32 = 0;
|
||||
var oi: i32 = 0;
|
||||
$ol while oi < 3 {
|
||||
oi = oi + 1;
|
||||
var ii: i32 = 0;
|
||||
while ii < 3 {
|
||||
ii = ii + 1;
|
||||
if ii == 2 { continue $ol; }
|
||||
sum = sum + 1;
|
||||
}
|
||||
}
|
||||
// 每个外循环:ii=1 时 sum+1;ii=2 continue;所以 3 次,sum=3
|
||||
@assert(sum == 3, "label continue sum");
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_block_scope();
|
||||
test_shadowing();
|
||||
test_loop_scope();
|
||||
test_if_scope();
|
||||
test_fn_scope();
|
||||
}
|
||||
|
||||
fn test_block_scope() {
|
||||
var x: i32 = 1;
|
||||
{
|
||||
var y: i32 = 2;
|
||||
@assert(y == 2, "inner y");
|
||||
}
|
||||
// y 已离开作用域,不能再引用
|
||||
@assert(x == 1, "outer x");
|
||||
}
|
||||
|
||||
fn test_shadowing() {
|
||||
var x: i32 = 1;
|
||||
{
|
||||
var x: i32 = 100;
|
||||
@assert(x == 100, "shadow inner");
|
||||
}
|
||||
@assert(x == 1, "outer x after block");
|
||||
|
||||
// if 体里的遮蔽
|
||||
var y: i32 = 5;
|
||||
if true {
|
||||
var y: i32 = 50;
|
||||
@assert(y == 50, "if body y");
|
||||
}
|
||||
@assert(y == 5, "outer y after if");
|
||||
}
|
||||
|
||||
fn test_loop_scope() {
|
||||
// 每次迭代都是新作用域
|
||||
var i: i32 = 0;
|
||||
while i < 3 {
|
||||
var z: i32 = i * 10;
|
||||
@assert(z == i * 10, "loop z");
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
// 循环变量在体外不可见:无法直接测,但同名不冲突
|
||||
var j: i32 = 0;
|
||||
while j < 2 {
|
||||
var j2: i32 = 100;
|
||||
@assert(j2 == 100, "inner j2");
|
||||
j = j + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_if_scope() {
|
||||
var x: i32 = 1;
|
||||
if true {
|
||||
var x: i32 = 2;
|
||||
@assert(x == 2, "if inner x");
|
||||
} else {
|
||||
var x: i32 = 3;
|
||||
@assert(false, "not reached");
|
||||
}
|
||||
@assert(x == 1, "outer x");
|
||||
|
||||
// 兄弟分支的变量不冲突
|
||||
if true {
|
||||
var a: i32 = 10;
|
||||
@assert(a == 10, "then a");
|
||||
} else {
|
||||
var a: i32 = 20;
|
||||
@assert(false, "not reached");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_fn_scope() {
|
||||
var r1: i32 = make(5);
|
||||
var r2: i32 = make(10);
|
||||
@assert(r1 == 6, "make 5");
|
||||
@assert(r2 == 11, "make 10");
|
||||
}
|
||||
|
||||
fn make(x: i32): i32 {
|
||||
var y: i32 = x + 1;
|
||||
ret y;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_void();
|
||||
test_multi_args();
|
||||
test_recursion();
|
||||
test_mutual_recursion();
|
||||
test_nested_calls();
|
||||
}
|
||||
|
||||
fn test_void() {
|
||||
do_nothing();
|
||||
var r: i32 = compute();
|
||||
@assert(r == 42, "compute");
|
||||
}
|
||||
|
||||
fn do_nothing() {
|
||||
}
|
||||
|
||||
fn compute(): i32 {
|
||||
ret 42;
|
||||
}
|
||||
|
||||
fn test_multi_args() {
|
||||
@assert(add3(1, 2, 3) == 6, "add3");
|
||||
@assert(sub(10, 3) == 7, "sub");
|
||||
@assert(max2(5, 3) == 5, "max2 a");
|
||||
@assert(max2(3, 5) == 5, "max2 b");
|
||||
@assert(mix(1, 2, 3, 4, 5) == 15, "mix 5 args");
|
||||
}
|
||||
|
||||
fn add3(a: i32, b: i32, c: i32): i32 {
|
||||
ret a + b + c;
|
||||
}
|
||||
|
||||
fn sub(a: i32, b: i32): i32 {
|
||||
ret a - b;
|
||||
}
|
||||
|
||||
fn max2(a: i32, b: i32): i32 {
|
||||
if a > b { ret a; }
|
||||
ret b;
|
||||
}
|
||||
|
||||
fn mix(a: i32, b: i32, c: i32, d: i32, e: i32): i32 {
|
||||
ret a + b + c + d + e;
|
||||
}
|
||||
|
||||
fn test_recursion() {
|
||||
@assert(fact(0) == 1, "fact 0");
|
||||
@assert(fact(1) == 1, "fact 1");
|
||||
@assert(fact(5) == 120, "fact 5");
|
||||
@assert(fact(10) == 3628800, "fact 10");
|
||||
|
||||
@assert(fib(0) == 0, "fib 0");
|
||||
@assert(fib(1) == 1, "fib 1");
|
||||
@assert(fib(10) == 55, "fib 10");
|
||||
@assert(fib(20) == 6765, "fib 20");
|
||||
|
||||
@assert(sum_to(100) == 5050, "sum_to 100");
|
||||
}
|
||||
|
||||
fn fact(n: i32): i32 {
|
||||
if n <= 1 { ret 1; }
|
||||
ret n * fact(n - 1);
|
||||
}
|
||||
|
||||
fn fib(n: i32): i32 {
|
||||
if n < 2 { ret n; }
|
||||
ret fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
|
||||
fn sum_to(n: i32): i32 {
|
||||
if n == 0 { ret 0; }
|
||||
ret n + sum_to(n - 1);
|
||||
}
|
||||
|
||||
fn test_mutual_recursion() {
|
||||
@assert(is_even(0), "even 0");
|
||||
@assert(is_even(10), "even 10");
|
||||
@assert(!is_even(7), "not even 7");
|
||||
@assert(is_odd(7), "odd 7");
|
||||
@assert(!is_odd(10), "not odd 10");
|
||||
}
|
||||
|
||||
fn is_even(n: i32): bool {
|
||||
if n == 0 { ret true; }
|
||||
ret is_odd(n - 1);
|
||||
}
|
||||
|
||||
fn is_odd(n: i32): bool {
|
||||
if n == 0 { ret false; }
|
||||
ret is_even(n - 1);
|
||||
}
|
||||
|
||||
fn test_nested_calls() {
|
||||
// inc(5) = 6, square(6) = 36, 36 + 5 = 41
|
||||
@assert(outer(5) == 41, "outer 5");
|
||||
@assert(outer(0) == 1, "outer 0"); // inc(0)=1, square(1)=1, 1+0=1
|
||||
}
|
||||
|
||||
fn square(x: i32): i32 {
|
||||
ret x * x;
|
||||
}
|
||||
|
||||
fn inc(x: i32): i32 {
|
||||
ret x + 1;
|
||||
}
|
||||
|
||||
fn outer(x: i32): i32 {
|
||||
ret square(inc(x)) + x;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_i_to_u();
|
||||
test_u_to_i();
|
||||
test_width_chain();
|
||||
test_bool_int();
|
||||
test_ptr_int();
|
||||
}
|
||||
|
||||
fn test_i_to_u() {
|
||||
var a: i32 = -1;
|
||||
var b: u32 = a as u32;
|
||||
@assert(b == 0xFFFFFFFF, "i32 -1 -> u32 max");
|
||||
|
||||
var c: i8 = -1;
|
||||
var d: u8 = c as u8;
|
||||
@assert(d == 255, "i8 -1 -> u8 255");
|
||||
|
||||
var e: i64 = -1;
|
||||
var f: u64 = e as u64;
|
||||
@assert(f == 0xFFFFFFFFFFFFFFFF, "i64 -1 -> u64 max");
|
||||
}
|
||||
|
||||
fn test_u_to_i() {
|
||||
var a: u32 = 0xFFFFFFFF;
|
||||
var b: i32 = a as i32;
|
||||
@assert(b == -1, "u32 max -> i32 -1");
|
||||
|
||||
var c: u8 = 255;
|
||||
var d: i8 = c as i8;
|
||||
@assert(d == -1, "u8 255 -> i8 -1");
|
||||
|
||||
var e: u8 = 127;
|
||||
var f: i8 = e as i8;
|
||||
@assert(f == 127, "u8 127 -> i8 127");
|
||||
}
|
||||
|
||||
fn test_width_chain() {
|
||||
// i8 -> i16 -> i32 -> i64 保持符号
|
||||
var a: i8 = -100;
|
||||
var b: i16 = a as i16;
|
||||
var c: i32 = b as i32;
|
||||
var d: i64 = c as i64;
|
||||
@assert(d == -100, "i8->i64 -100");
|
||||
|
||||
// 下行截断
|
||||
var e: i64 = 0x1_0000_0001;
|
||||
var f: i32 = e as i32;
|
||||
@assert(f == 1, "i64->i32 trunc");
|
||||
|
||||
var g: i16 = 0x1_0000_0001 as i16;
|
||||
// 0x0001 保留
|
||||
@assert(g == 1, "i64->i16 trunc");
|
||||
}
|
||||
|
||||
fn test_bool_int() {
|
||||
var t: bool = true;
|
||||
var f: bool = false;
|
||||
|
||||
// bool -> int
|
||||
var a: i32 = t as i32;
|
||||
var b: i32 = f as i32;
|
||||
@assert(a == 1, "true as i32");
|
||||
@assert(b == 0, "false as i32");
|
||||
|
||||
var c: u8 = t as u8;
|
||||
@assert(c == 1, "true as u8");
|
||||
|
||||
// int -> bool(由 sema 决定是否允许)
|
||||
// 保守:不测,避免未实现路径
|
||||
}
|
||||
|
||||
fn test_ptr_int() {
|
||||
var x: i32 = 42;
|
||||
var p: *i32 = &x;
|
||||
// ptr -> int -> ptr 往返
|
||||
var addr: usize = p as usize;
|
||||
var p2: *i32 = addr as *i32;
|
||||
@assert(p2.* == 42, "ptr roundtrip");
|
||||
|
||||
@assert(p == p2, "ptr eq");
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_basic();
|
||||
test_ptr_arg();
|
||||
test_ptr_field();
|
||||
test_ptr_arith();
|
||||
test_ptr_cmp();
|
||||
}
|
||||
|
||||
fn test_basic() {
|
||||
var x: i32 = 42;
|
||||
var p: *i32 = &x;
|
||||
@assert(p.* == 42, "deref");
|
||||
|
||||
p.* = 100;
|
||||
@assert(x == 100, "store via ptr");
|
||||
|
||||
x = 7;
|
||||
@assert(p.* == 7, "read after store");
|
||||
}
|
||||
|
||||
fn test_ptr_arg() {
|
||||
var x: i32 = 10;
|
||||
double_it(&x);
|
||||
@assert(x == 20, "double via ptr");
|
||||
|
||||
double_it(&x);
|
||||
@assert(x == 40, "double twice");
|
||||
}
|
||||
|
||||
fn double_it(p: *i32) {
|
||||
p.* = p.* * 2;
|
||||
}
|
||||
|
||||
fn test_ptr_field() {
|
||||
// 用 Str 作为例子
|
||||
var s: Str = Str { .ptr = null, .len = 0 };
|
||||
var ps: *Str = &s;
|
||||
ps.len = 42;
|
||||
@assert(s.len == 42, "field via ptr");
|
||||
ps.len = 100;
|
||||
@assert(s.len == 100, "field via ptr again");
|
||||
}
|
||||
|
||||
type Str = struct {
|
||||
ptr: *u8,
|
||||
len: u64,
|
||||
}
|
||||
|
||||
fn test_ptr_arith() {
|
||||
var arr: [5]i32 = .[10, 20, 30, 40, 50];
|
||||
var base: *i32 = &arr[0];
|
||||
@assert(base.* == 10, "base[0]");
|
||||
@assert((base + 1).* == 20, "base[1]");
|
||||
@assert((base + 2).* == 30, "base[2]");
|
||||
@assert((base + 4).* == 50, "base[4]");
|
||||
|
||||
var p2: *i32 = base + 3;
|
||||
@assert(p2.* == 40, "p2");
|
||||
var p3: *i32 = p2 - 1;
|
||||
@assert(p3.* == 30, "p3");
|
||||
}
|
||||
|
||||
fn test_ptr_cmp() {
|
||||
var arr: [3]i32 = .[1, 2, 3];
|
||||
var p0: *i32 = &arr[0];
|
||||
var p1: *i32 = &arr[1];
|
||||
|
||||
@assert(p0 != p1, "p0 != p1");
|
||||
@assert(p0 == p0, "p0 == p0");
|
||||
@assert(p1 == p0 + 1, "p1 == p0+1");
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_construct();
|
||||
test_method();
|
||||
test_mut_self();
|
||||
test_nested();
|
||||
}
|
||||
|
||||
type Point = struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
|
||||
fn new(x: i32, y: i32): Point {
|
||||
ret Point { .x = x, .y = y };
|
||||
}
|
||||
|
||||
fn sum(self: *Point): i32 {
|
||||
ret self.x + self.y;
|
||||
}
|
||||
|
||||
fn translate(self: *Point, dx: i32, dy: i32) {
|
||||
self.x = self.x + dx;
|
||||
self.y = self.y + dy;
|
||||
}
|
||||
|
||||
fn norm2(self: *Point): i32 {
|
||||
ret self.x * self.x + self.y * self.y;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_construct() {
|
||||
var p = Point { .x = 3, .y = 4 };
|
||||
@assert(p.x == 3, "p.x");
|
||||
@assert(p.y == 4, "p.y");
|
||||
|
||||
var q = Point.new(10, 20);
|
||||
@assert(q.x == 10, "q.x");
|
||||
@assert(q.y == 20, "q.y");
|
||||
}
|
||||
|
||||
fn test_method() {
|
||||
var p = Point.new(3, 4);
|
||||
@assert(Point.sum(&p) == 7, "sum");
|
||||
@assert(Point.norm2(&p) == 25, "norm2");
|
||||
}
|
||||
|
||||
fn test_mut_self() {
|
||||
var p = Point.new(1, 2);
|
||||
Point.translate(&p, 10, 20);
|
||||
@assert(p.x == 11, "after translate x");
|
||||
@assert(p.y == 22, "after translate y");
|
||||
}
|
||||
|
||||
type Line = struct {
|
||||
a: Point,
|
||||
b: Point,
|
||||
}
|
||||
|
||||
fn test_nested() {
|
||||
var l = Line {
|
||||
.a = Point.new(0, 0),
|
||||
.b = Point.new(3, 4),
|
||||
};
|
||||
@assert(l.a.x == 0, "l.a.x");
|
||||
@assert(l.b.y == 4, "l.b.y");
|
||||
@assert(Point.norm2(&l.b) == 25, "l.b norm2");
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_literal();
|
||||
test_index();
|
||||
test_write();
|
||||
test_len();
|
||||
test_iterate();
|
||||
}
|
||||
|
||||
fn test_literal() {
|
||||
var a: [5]i32 = .[1, 2, 3, 4, 5];
|
||||
@assert(a[0] == 1, "a[0]");
|
||||
@assert(a[4] == 5, "a[4]");
|
||||
|
||||
var b: [3]i32 = .[10, 20, 30];
|
||||
@assert(b[0] == 10, "b[0]");
|
||||
@assert(b[2] == 30, "b[2]");
|
||||
}
|
||||
|
||||
fn test_index() {
|
||||
var a: [5]i32 = .[1, 2, 3, 4, 5];
|
||||
var i: i32 = 0;
|
||||
while i < 5 {
|
||||
@assert(a[i] == i + 1, "a[i]");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_write() {
|
||||
var a: [3]i32 = .[0, 0, 0];
|
||||
a[0] = 100;
|
||||
a[1] = 200;
|
||||
a[2] = 300;
|
||||
@assert(a[0] == 100, "a[0]");
|
||||
@assert(a[1] == 200, "a[1]");
|
||||
@assert(a[2] == 300, "a[2]");
|
||||
|
||||
// 通过指针写
|
||||
var p: *i32 = &a[1];
|
||||
p.* = 999;
|
||||
@assert(a[1] == 999, "a[1] via ptr");
|
||||
}
|
||||
|
||||
fn test_len() {
|
||||
var a: [5]i32 = .[1, 2, 3, 4, 5];
|
||||
@assert(a.len == 5, "a.len");
|
||||
|
||||
var b: [10]u8 = .[];
|
||||
@assert(b.len == 10, "b.len");
|
||||
}
|
||||
|
||||
fn test_iterate() {
|
||||
var a: [5]i32 = .[10, 20, 30, 40, 50];
|
||||
var sum: i32 = 0;
|
||||
var i: u64 = 0;
|
||||
while i < a.len {
|
||||
sum = sum + a[i];
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(sum == 150, "array sum");
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_from_array();
|
||||
test_from_ptr();
|
||||
test_from_slice();
|
||||
test_fields();
|
||||
}
|
||||
|
||||
fn test_from_array() {
|
||||
var a: [5]i32 = .[10, 20, 30, 40, 50];
|
||||
var s: []i32 = a[1..4];
|
||||
@assert(s.len == 3, "s.len");
|
||||
@assert(s.ptr[0] == 20, "s[0]");
|
||||
@assert(s.ptr[1] == 30, "s[1]");
|
||||
@assert(s.ptr[2] == 40, "s[2]");
|
||||
|
||||
// 整段切片
|
||||
var full: []i32 = a[0..5];
|
||||
@assert(full.len == 5, "full.len");
|
||||
@assert(full.ptr[4] == 50, "full[4]");
|
||||
|
||||
// 空切片
|
||||
var e: []i32 = a[2..2];
|
||||
@assert(e.len == 0, "empty slice");
|
||||
}
|
||||
|
||||
fn test_from_ptr() {
|
||||
var a: [5]i32 = .[1, 2, 3, 4, 5];
|
||||
var p: *i32 = &a[0];
|
||||
var s: []i32 = p[0..3];
|
||||
@assert(s.len == 3, "ptr slice len");
|
||||
@assert(s.ptr[0] == 1, "ptr s[0]");
|
||||
@assert(s.ptr[2] == 3, "ptr s[2]");
|
||||
}
|
||||
|
||||
fn test_from_slice() {
|
||||
var a: [10]i32 = .[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
var s1: []i32 = a[2..8];
|
||||
@assert(s1.len == 6, "s1.len");
|
||||
|
||||
var s2: []i32 = s1[1..4];
|
||||
@assert(s2.len == 3, "s2.len");
|
||||
@assert(s2.ptr[0] == 3, "s2[0]");
|
||||
@assert(s2.ptr[2] == 5, "s2[2]");
|
||||
}
|
||||
|
||||
fn test_fields() {
|
||||
var a: [5]i32 = .[100, 200, 300, 400, 500];
|
||||
var s: []i32 = a[1..4];
|
||||
@assert(s.len == 3, "len field");
|
||||
@assert(s.ptr[0] == 200, "ptr field via index");
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
#extern(vm) fn vm_memcmp(dst: *any, src: *any, size: usize): isize;
|
||||
|
||||
type Str = struct {
|
||||
ptr: *u8,
|
||||
len: u64,
|
||||
|
||||
fn from_str(s: []u8): Str {
|
||||
ret Str { .ptr = s.ptr, .len = s.len };
|
||||
}
|
||||
|
||||
fn empty(): Str {
|
||||
ret Str { .ptr = null, .len = 0 };
|
||||
}
|
||||
|
||||
fn eq_slice(a: Str, s: []u8): bool {
|
||||
if a.len != s.len { ret false; }
|
||||
if a.len == 0 { ret true; }
|
||||
ret vm_memcmp(a.ptr, s.ptr, a.len) == 0;
|
||||
}
|
||||
|
||||
fn eq(a: Str, b: Str): bool {
|
||||
if a.len != b.len { ret false; }
|
||||
if a.len == 0 { ret true; }
|
||||
ret vm_memcmp(a.ptr, b.ptr, a.len) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn main(): void {
|
||||
var s = Str.from_str("hello");
|
||||
@assert(s.len == 5, "hello len");
|
||||
@assert(s.ptr[0] == 104, "h");
|
||||
@assert(s.ptr[4] == 111, "o");
|
||||
|
||||
@assert(Str.eq_slice(s, "hello"), "eq_slice hello");
|
||||
@assert(!Str.eq_slice(s, "world"), "!eq_slice world");
|
||||
|
||||
var e = Str.empty();
|
||||
@assert(e.len == 0, "empty len");
|
||||
@assert(Str.eq(e, e), "empty eq empty");
|
||||
@assert(!Str.eq(s, e), "!eq hello empty");
|
||||
|
||||
var t = Str.from_str("hello");
|
||||
@assert(Str.eq(s, t), "eq hello hello");
|
||||
|
||||
var u = Str.from_str("");
|
||||
@assert(u.len == 0, "empty literal");
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): void {
|
||||
test_single();
|
||||
test_nested_ret();
|
||||
test_recursive_ret();
|
||||
test_field_chain();
|
||||
}
|
||||
|
||||
type Pair = struct {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
type Nested = struct {
|
||||
p: Pair,
|
||||
q: Pair,
|
||||
}
|
||||
|
||||
fn mk_pair(a: i32, b: i32): Pair {
|
||||
ret Pair { .a = a, .b = b };
|
||||
}
|
||||
|
||||
fn mk_nested(pa: Pair, qb: Pair): Nested {
|
||||
ret Nested { .p = pa, .q = qb };
|
||||
}
|
||||
|
||||
fn sum_pair(p: Pair): i32 {
|
||||
ret p.a + p.b;
|
||||
}
|
||||
|
||||
fn test_single() {
|
||||
var p = mk_pair(3, 4);
|
||||
@assert(p.a == 3, "p.a");
|
||||
@assert(p.b == 4, "p.b");
|
||||
@assert(sum_pair(p) == 7, "sum_pair");
|
||||
}
|
||||
|
||||
fn test_nested_ret() {
|
||||
var p = mk_pair(1, 2);
|
||||
var q = mk_pair(10, 20);
|
||||
var n = mk_nested(p, q);
|
||||
@assert(n.p.a == 1, "n.p.a");
|
||||
@assert(n.q.b == 20, "n.q.b");
|
||||
}
|
||||
|
||||
fn test_recursive_ret() {
|
||||
var p = countdown_pair(5);
|
||||
@assert(p.a == 5, "countdown 5 a");
|
||||
@assert(p.b == 0, "countdown 5 b");
|
||||
}
|
||||
|
||||
fn countdown_pair(n: i32): Pair {
|
||||
if n == 0 {
|
||||
ret Pair { .a = 0, .b = 0 };
|
||||
}
|
||||
var sub = countdown_pair(n - 1);
|
||||
ret Pair { .a = n, .b = sub.b };
|
||||
}
|
||||
|
||||
fn test_field_chain() {
|
||||
var p = mk_pair(7, 8);
|
||||
var q = mk_pair(9, 10);
|
||||
var n = mk_nested(p, q);
|
||||
var s1 = sum_pair(n.p);
|
||||
var s2 = sum_pair(n.q);
|
||||
@assert(s1 == 15, "n.p sum");
|
||||
@assert(s2 == 19, "n.q sum");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
#extern(vm) fn vm_printf(fmt: []u8, ...);
|
||||
|
||||
fn main(): void {
|
||||
vm_printf("hello\n");
|
||||
vm_printf("num = %d\n", 42);
|
||||
vm_printf("a=%d b=%d c=%d\n", 1, 2, 3);
|
||||
vm_printf("char = %c\n", 65);
|
||||
vm_printf("str = %s\n", "world");
|
||||
vm_printf("mixed: %d %s %c\n", 7, "x", 66);
|
||||
vm_printf("neg = %d\n", 0 - 5);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
#extern(vm) fn vm_alloc(sz: usize): *any;
|
||||
#extern(vm) fn vm_free(ptr: *any);
|
||||
#extern(vm) fn vm_memcpy(dst: *any, src: *any, size: usize): *any;
|
||||
#extern(vm) fn vm_memcmp(dst: *any, src: *any, size: usize): isize;
|
||||
|
||||
fn main(): void {
|
||||
test_alloc();
|
||||
test_memcpy();
|
||||
test_memcmp();
|
||||
}
|
||||
|
||||
fn test_alloc() {
|
||||
var p = vm_alloc(64) as *u8;
|
||||
@assert(p != null, "alloc");
|
||||
|
||||
// 写入若干字节
|
||||
p[0] = 1;
|
||||
p[1] = 2;
|
||||
p[63] = 255;
|
||||
@assert(p[0] == 1, "p[0]");
|
||||
@assert(p[1] == 2, "p[1]");
|
||||
@assert(p[63] == 255, "p[63]");
|
||||
|
||||
vm_free(p);
|
||||
}
|
||||
|
||||
fn test_memcpy() {
|
||||
var src: [8]u8 = .[1, 2, 3, 4, 5, 6, 7, 8];
|
||||
var dst = vm_alloc(8) as *u8;
|
||||
vm_memcpy(dst, &src[0], 8);
|
||||
|
||||
var i: u64 = 0;
|
||||
while i < 8 {
|
||||
@assert(dst[i] == src[i], "copy byte");
|
||||
i = i + 1;
|
||||
}
|
||||
vm_free(dst);
|
||||
}
|
||||
|
||||
fn test_memcmp() {
|
||||
var a: [4]u8 = .[1, 2, 3, 4];
|
||||
var b: [4]u8 = .[1, 2, 3, 4];
|
||||
var c: [4]u8 = .[1, 2, 3, 5];
|
||||
|
||||
@assert(vm_memcmp(&a[0], &b[0], 4) == 0, "a==b");
|
||||
@assert(vm_memcmp(&a[0], &c[0], 4) != 0, "a!=c");
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
#extern(vm) fn vm_alloc(sz: usize): *any;
|
||||
#extern(vm) fn vm_free(ptr: *any);
|
||||
|
||||
type Node = struct {
|
||||
val: i32,
|
||||
next: *Node,
|
||||
|
||||
fn new(v: i32): Node {
|
||||
ret Node { .val = v, .next = null };
|
||||
}
|
||||
}
|
||||
|
||||
type Tree = struct {
|
||||
val: i32,
|
||||
children: []Tree, // 切片间接
|
||||
}
|
||||
|
||||
type A = struct { b: *B }
|
||||
type B = struct { a: *A } // 互相引用
|
||||
|
||||
fn main(): void {
|
||||
test_loop_sum();
|
||||
test_nested_loops();
|
||||
test_deep_recursion();
|
||||
test_linked_list();
|
||||
test_matrix();
|
||||
|
||||
var tree: Tree = undefined;
|
||||
var a: A = undefined;
|
||||
var b: B = undefined;
|
||||
}
|
||||
|
||||
fn test_loop_sum() {
|
||||
var sum: i64 = 0;
|
||||
var i: i64 = 1;
|
||||
while i <= 1000 {
|
||||
sum = sum + i;
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(sum == 500500, "sum 1..1000");
|
||||
}
|
||||
|
||||
fn test_nested_loops() {
|
||||
var total: i64 = 0;
|
||||
var i: i32 = 0;
|
||||
while i < 100 {
|
||||
var j: i32 = 0;
|
||||
while j < 100 {
|
||||
total = total + 1;
|
||||
j = j + 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(total == 10000, "100x100");
|
||||
}
|
||||
|
||||
fn test_deep_recursion() {
|
||||
@assert(sum_deep(500) == 125250, "sum_deep 500");
|
||||
}
|
||||
|
||||
fn sum_deep(n: i32): i32 {
|
||||
if n == 0 { ret 0; }
|
||||
ret n + sum_deep(n - 1);
|
||||
}
|
||||
|
||||
fn test_linked_list() {
|
||||
var n3 = Node.new(30);
|
||||
var n2 = Node.new(20);
|
||||
var n1 = Node.new(10);
|
||||
|
||||
n1.next = &n2;
|
||||
n2.next = &n3;
|
||||
|
||||
var total: i32 = 0;
|
||||
var p: *Node = &n1;
|
||||
while p != null {
|
||||
total = total + p.val;
|
||||
p = p.next;
|
||||
}
|
||||
@assert(total == 60, "list sum");
|
||||
}
|
||||
|
||||
fn test_matrix() {
|
||||
var m: [10][10]i32 = .[];
|
||||
var i: i32 = 0;
|
||||
while i < 10 {
|
||||
var j: i32 = 0;
|
||||
while j < 10 {
|
||||
m[i][j] = i * 10 + j;
|
||||
j = j + 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var sum: i32 = 0;
|
||||
var a: i32 = 0;
|
||||
while a < 10 {
|
||||
var b: i32 = 0;
|
||||
while b < 10 {
|
||||
sum = sum + m[a][b];
|
||||
b = b + 1;
|
||||
}
|
||||
a = a + 1;
|
||||
}
|
||||
// 0+1+...+99 = 4950
|
||||
@assert(sum == 4950, "matrix sum");
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn mark(trace: *i32, n: i32) {
|
||||
trace.* = trace.* * 10 + n;
|
||||
}
|
||||
|
||||
fn scopes(trace: *i32) {
|
||||
defer { mark(trace, 4); }
|
||||
{
|
||||
defer { mark(trace, 3); }
|
||||
{
|
||||
defer { mark(trace, 2); }
|
||||
defer { mark(trace, 1); }
|
||||
}
|
||||
@assert(trace.* == 12, "inner scope LIFO");
|
||||
}
|
||||
@assert(trace.* == 123, "outer scope cleanup");
|
||||
}
|
||||
|
||||
fn tail_value() {
|
||||
var x: i32 = 7;
|
||||
var result: i32 = {
|
||||
defer { x = 9; }
|
||||
x
|
||||
};
|
||||
@assert(result == 7, "tail evaluated before cleanup");
|
||||
@assert(x == 9, "tail scope cleaned");
|
||||
}
|
||||
|
||||
fn nested_cleanup(trace: *i32) {
|
||||
defer {
|
||||
defer { mark(trace, 2); }
|
||||
mark(trace, 1);
|
||||
}
|
||||
}
|
||||
|
||||
type Pair = struct { first: i32, second: i32, }
|
||||
|
||||
fn aggregate_return(): Pair {
|
||||
var value = Pair { .first = 7, .second = 8 };
|
||||
defer { value.first = 99; }
|
||||
ret value;
|
||||
}
|
||||
|
||||
fn aggregate_tail() {
|
||||
var value = Pair { .first = 7, .second = 8 };
|
||||
var result: Pair = {
|
||||
defer { value.first = 99; }
|
||||
value
|
||||
};
|
||||
@assert(result.first == 7, "aggregate tail snapshot");
|
||||
@assert(value.first == 99, "aggregate tail cleanup");
|
||||
}
|
||||
|
||||
fn defer_scope() {
|
||||
var trace: i32 = 0;
|
||||
scopes(&trace);
|
||||
@assert(trace == 1234, "each scope cleaned exactly once");
|
||||
tail_value();
|
||||
trace = 0;
|
||||
nested_cleanup(&trace);
|
||||
@assert(trace == 12, "defer within cleanup scope");
|
||||
var pair = aggregate_return();
|
||||
@assert(pair.first == 7, "aggregate return snapshot");
|
||||
@assert(pair.second == 8);
|
||||
aggregate_tail();
|
||||
}
|
||||
|
||||
fn choose(trace: *i32, flag: bool): i32 {
|
||||
defer { mark(trace, 3); }
|
||||
if flag {
|
||||
defer { mark(trace, 1); }
|
||||
ret 10;
|
||||
} else {
|
||||
defer { mark(trace, 2); }
|
||||
ret 20;
|
||||
}
|
||||
}
|
||||
|
||||
fn conditional(trace: *i32, flag: bool) {
|
||||
if flag {
|
||||
defer { mark(trace, 1); }
|
||||
}
|
||||
mark(trace, 2);
|
||||
}
|
||||
|
||||
fn early(trace: *i32, flag: bool): i32 {
|
||||
defer { mark(trace, 3); }
|
||||
if flag { ret 10; }
|
||||
ret 20;
|
||||
defer { mark(trace, 9); }
|
||||
}
|
||||
|
||||
fn return_value(): i32 {
|
||||
var x: i32 = 42;
|
||||
defer { x = 100; }
|
||||
ret x;
|
||||
}
|
||||
|
||||
type Choice = enum { Left, Right, }
|
||||
|
||||
fn match_path(trace: *i32, choice: Choice): i32 {
|
||||
defer { mark(trace, 3); }
|
||||
match choice {
|
||||
.Left => {
|
||||
defer { mark(trace, 1); }
|
||||
ret 10;
|
||||
},
|
||||
.Right => {
|
||||
defer { mark(trace, 2); }
|
||||
ret 20;
|
||||
},
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn defer_paths() {
|
||||
var trace: i32 = 0;
|
||||
@assert(choose(&trace, true) == 10);
|
||||
@assert(trace == 13, "then return cleanup");
|
||||
trace = 0;
|
||||
@assert(choose(&trace, false) == 20);
|
||||
@assert(trace == 23, "else return cleanup");
|
||||
trace = 0;
|
||||
conditional(&trace, false);
|
||||
@assert(trace == 2, "unentered branch has no cleanup");
|
||||
trace = 0;
|
||||
conditional(&trace, true);
|
||||
@assert(trace == 12, "branch cleanup before following code");
|
||||
trace = 0;
|
||||
@assert(early(&trace, false) == 20);
|
||||
@assert(trace == 3, "fallthrough retains function cleanup");
|
||||
trace = 0;
|
||||
@assert(early(&trace, true) == 10);
|
||||
@assert(trace == 3, "early return skips unreachable defer");
|
||||
@assert(return_value() == 42, "return evaluated before cleanup");
|
||||
trace = 0;
|
||||
@assert(match_path(&trace, .Left) == 10);
|
||||
@assert(trace == 13, "left match cleanup");
|
||||
trace = 0;
|
||||
@assert(match_path(&trace, .Right) == 20);
|
||||
@assert(trace == 23, "right match cleanup");
|
||||
}
|
||||
|
||||
fn iterations(trace: *i32) {
|
||||
var i: i32 = 0;
|
||||
while i < 3 {
|
||||
i = i + 1;
|
||||
defer { mark(trace, i); }
|
||||
}
|
||||
@assert(trace.* == 123, "normal iteration cleanup");
|
||||
}
|
||||
|
||||
fn jumps(trace: *i32) {
|
||||
defer { mark(trace, 4); }
|
||||
var i: i32 = 0;
|
||||
while i < 4 {
|
||||
i = i + 1;
|
||||
defer { mark(trace, i); }
|
||||
if i == 1 { continue; }
|
||||
if i == 2 { break; }
|
||||
}
|
||||
@assert(trace.* == 12, "continue and break cleanup");
|
||||
}
|
||||
|
||||
fn labels(trace: *i32) {
|
||||
var i: i32 = 0;
|
||||
$outer while i < 3 {
|
||||
i = i + 1;
|
||||
defer { mark(trace, 2); }
|
||||
loop {
|
||||
defer { mark(trace, 1); }
|
||||
if i == 1 { continue $outer; }
|
||||
break $outer;
|
||||
}
|
||||
}
|
||||
@assert(trace.* == 1212, "label exits clean inner and outer scopes");
|
||||
}
|
||||
|
||||
fn inner_break(trace: *i32) {
|
||||
loop {
|
||||
defer { mark(trace, 2); }
|
||||
loop {
|
||||
defer { mark(trace, 1); }
|
||||
break;
|
||||
}
|
||||
@assert(trace.* == 1, "inner break retains outer cleanup");
|
||||
break;
|
||||
}
|
||||
@assert(trace.* == 12);
|
||||
}
|
||||
|
||||
fn break_value() {
|
||||
var x: i32 = 7;
|
||||
var result: i32 = loop {
|
||||
defer { x = 9; }
|
||||
break x;
|
||||
};
|
||||
@assert(result == 7, "break value evaluated before cleanup");
|
||||
@assert(x == 9, "break cleanup executed");
|
||||
}
|
||||
|
||||
fn loop_in_cleanup(trace: *i32) {
|
||||
loop {
|
||||
defer {
|
||||
var i: i32 = 0;
|
||||
while i < 2 {
|
||||
i = i + 1;
|
||||
defer { mark(trace, i); }
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@assert(trace.* == 12, "cleanup can contain its own loop");
|
||||
}
|
||||
|
||||
fn defer_loops() {
|
||||
var trace: i32 = 0;
|
||||
iterations(&trace);
|
||||
trace = 0;
|
||||
jumps(&trace);
|
||||
@assert(trace == 124, "function cleanup retained after loop");
|
||||
trace = 0;
|
||||
labels(&trace);
|
||||
trace = 0;
|
||||
inner_break(&trace);
|
||||
break_value();
|
||||
trace = 0;
|
||||
loop_in_cleanup(&trace);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
defer_scope();
|
||||
defer_paths();
|
||||
defer_loops();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Expect retcode = 0
|
||||
// 导入模块时不能在被导入模块的作用域中提前解析当前模块的函数签名。
|
||||
|
||||
type imported = @import("028_self_import.spl");
|
||||
type QualifiedStr = imported.Str;
|
||||
type .{ .Str, .from_str } = imported;
|
||||
|
||||
type Result = enum {
|
||||
Ok: usize,
|
||||
Err: QualifiedStr,
|
||||
}
|
||||
|
||||
fn success(): Result {
|
||||
ret .Ok(42);
|
||||
}
|
||||
|
||||
fn failure(): Result {
|
||||
ret .Err(from_str("bad"));
|
||||
}
|
||||
|
||||
fn qualified(value: QualifiedStr): usize {
|
||||
ret imported.size(&value);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match success() {
|
||||
.Ok(value) => { @assert(value == 42); },
|
||||
.Err(message) => { @assert(false); },
|
||||
}
|
||||
match failure() {
|
||||
.Ok(value) => { @assert(false); },
|
||||
.Err(message) => {
|
||||
@assert(message.len == 3);
|
||||
@assert(message.ptr[0] == 98 as u8);
|
||||
@assert(qualified(message) == 3);
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Expect retcode = 0
|
||||
// 切片支持按上下文类型构造 ptr/len 聚合字面量。
|
||||
|
||||
fn make(data: *u8, length: usize): []u8 {
|
||||
ret .{ .ptr = data, .len = length };
|
||||
}
|
||||
|
||||
type Packet = struct { bytes: []u8, }
|
||||
type Result = enum { Ok: []u8, Err, }
|
||||
|
||||
fn wrapped(data: *u8): Result {
|
||||
ret .Ok(.{ .ptr = data, .len = 2 });
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var data: [3]u8 = .[10, 20, 30];
|
||||
var direct: []u8 = .{ .len = 3, .ptr = &data[0] };
|
||||
@assert(direct.len == 3);
|
||||
@assert(direct.ptr == &data[0]);
|
||||
direct.ptr[1] = 21;
|
||||
@assert(data[1] == 21);
|
||||
|
||||
var returned = make(&data[1], 2);
|
||||
@assert(returned.len == 2);
|
||||
@assert(returned.ptr[0] == 21);
|
||||
@assert(returned.ptr[1] == 30);
|
||||
|
||||
var packet = Packet { .bytes = .{ .ptr = &data[0], .len = 1 } };
|
||||
@assert(packet.bytes.len == 1);
|
||||
@assert(packet.bytes.ptr[0] == 10);
|
||||
|
||||
match wrapped(&data[0]) {
|
||||
.Ok(bytes) => {
|
||||
@assert(bytes.len == 2);
|
||||
@assert(bytes.ptr[1] == 21);
|
||||
},
|
||||
.Err => { @assert(false); },
|
||||
}
|
||||
|
||||
direct = .{ .ptr = null, .len = 0 };
|
||||
@assert(direct.ptr == null);
|
||||
@assert(direct.len == 0);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Expect retcode = 0
|
||||
// 类型查询内建的参数是类型表达式,不应按运行时标识符解析。
|
||||
|
||||
type Pair = struct {
|
||||
left: u8,
|
||||
right: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@assert(@sizeof(u8) == 1);
|
||||
@assert(@bitsizeof(u8) == 8);
|
||||
@assert(@sizeof(i32) == 4);
|
||||
@assert(@bitsizeof(i32) == 32);
|
||||
@assert(@alignof(i32) == 4);
|
||||
@assert(@sizeof(Pair) >= 5);
|
||||
@assert(@alignof(Pair) >= 1);
|
||||
@assert(@sizeof((u8, u32)) >= 5);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Expect retcode = 0
|
||||
// 被导入模块中的类型 intrinsic 必须使用与入口模块相同的参数解析规则。
|
||||
|
||||
type imported = @import("120_import_type_intrinsics_impl.spl");
|
||||
|
||||
fn main() {
|
||||
@assert(imported.int_size() == 4);
|
||||
@assert(imported.int_alignment() == 4);
|
||||
@assert(@sizeof(imported.Word) == @sizeof(usize));
|
||||
@assert(@alignof(imported.Word) == @alignof(usize));
|
||||
}
|
||||
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user