stage1 修复vm 提供简单参数检查 修复聚合类型bug

This commit is contained in:
zzy
2026-07-14 22:30:19 +08:00
parent f7f71c5f52
commit ab63c8ed90
7 changed files with 367 additions and 32 deletions

View File

@@ -476,7 +476,7 @@ void test_call_add(void) {
* ret i64 ip=10
*/
spl_ins_t add_insns[] = {ins(SPL_LADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_LADDR, SPL_VOID, 1), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_LADDR, SPL_VOID, 8), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_ADD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
spl_ins_t main_insns[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
ins(SPL_PUSH, SPL_VOID, 0), ins(SPL_CALL, SPL_VOID, 2),
@@ -583,7 +583,7 @@ void test_calli(void) {
* ret i64 ip=11
*/
spl_ins_t add_insns[] = {ins(SPL_LADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_LADDR, SPL_VOID, 1), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_LADDR, SPL_VOID, 8), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_ADD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
spl_ins_t main_insns[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
ins(SPL_PUSH, SPL_VOID, 2), ins(SPL_PUSH, SPL_VOID, 0),

View File

@@ -107,7 +107,7 @@ void spl_patch_to_here(spl_comp_t *ctx, spl_val_t addr) {
spl_patch(ctx, addr, offset);
}
/* ---- Multi-slot copy helper ---- */
/* ---- Multi-slot copy helpers ---- */
void spl_emit_copy_slots(spl_comp_t *ctx, int dest_offset, usize nslots) {
for (usize i = 0; i < nslots; i++) {
@@ -124,6 +124,31 @@ void spl_emit_copy_slots(spl_comp_t *ctx, int dest_offset, usize nslots) {
}
}
/* Copy nslots from source address to destination address.
* Stack before: [..., depth_items..., dest_addr, src_addr] (src TOS)
* Stack after: [..., depth_items...]
* depth: number of items below dest_addr that must be preserved (e.g. 1 if
* there's a base pointer between the rest of the stack and dest_addr). */
void spl_emit_copy_addr_to_addr(spl_comp_t *ctx, usize nslots, int depth) {
for (usize i = 0; i < nslots; i++) {
spl_emit(ctx, SPL_DUP, SPL_VOID, 0);
if (i > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, i * sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
spl_emit(ctx, SPL_PICK, SPL_VOID, 2 + depth);
if (i > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, i * sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, SPL_PTR, 0);
}
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
}
/* ---- Uniform LOAD/STORE type helper ---- */
spl_type_t spl_type_emit_type(spl_type_info_t *type) {
@@ -165,17 +190,22 @@ int spl_type_is_scalar(spl_type_info_t *type) {
void spl_emit_load_to_var(spl_comp_t *ctx, int ptr_slot_offset, usize byte_offset,
spl_type_info_t *data_type, int var_offset) {
spl_type_t bt = spl_type_emit_type(data_type);
spl_emit(ctx, SPL_LADDR, SPL_PTR, ptr_slot_offset);
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
if (byte_offset > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, byte_offset);
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
spl_emit(ctx, SPL_LOAD, bt, 0);
spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, bt, 0);
if (data_type && !spl_type_is_scalar(data_type) &&
spl_type_size(data_type) > sizeof(spl_val_t)) {
spl_emit_copy_slots(ctx, var_offset, spl_type_slot_count(data_type));
} else {
spl_type_t bt = spl_type_emit_type(data_type);
spl_emit(ctx, SPL_LOAD, bt, 0);
spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, bt, 0);
}
}
/* ---- Scope management ---- */

View File

@@ -287,6 +287,7 @@ void spl_patch_to_here(spl_comp_t *ctx, spl_val_t addr);
* Stack: [..., temp_addr] → [...]
* Copies nslots slots (each sizeof(spl_val_t) bytes) from temp offset to dest_offset. */
void spl_emit_copy_slots(spl_comp_t *ctx, int dest_offset, usize nslots);
void spl_emit_copy_addr_to_addr(spl_comp_t *ctx, usize nslots, int depth);
void spl_emit_ret(spl_comp_t *ctx, spl_type_info_t *ret_type);
void spl_emit_store_init(spl_comp_t *ctx, int var_offset, spl_type_info_t *var_type);

View File

@@ -457,7 +457,11 @@ static void parse_one_field_init(spl_comp_t *ctx, spl_field_vec_t *fields, int b
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
spl_parse_expr(ctx, PREC_MIN);
spl_emit(ctx, SPL_STORE, st, 0);
if (elem_type && !spl_type_is_scalar(elem_type)) {
spl_emit_copy_addr_to_addr(ctx, spl_type_slot_count(elem_type), 1);
} else {
spl_emit(ctx, SPL_STORE, st, 0);
}
skip_nl(ctx);
}
if (peek(ctx)->type == TOK_COMMA)
@@ -657,6 +661,67 @@ static int parse_call_args(spl_comp_t *ctx) {
return nargs;
}
/* Type check: warn when a value is passed where a pointer of the same type is expected.
* This catches bugs like passing `tok` (Token) where `*Token` is expected. */
static void spl_check_arg_type(spl_comp_t *ctx, const char *fname, spl_type_info_t *arg_type,
spl_type_info_t *param_type, int arg_idx) {
if (!arg_type || !param_type)
return;
if (param_type->kind == TYPE_PTR && param_type->elem && arg_type->kind != TYPE_PTR &&
arg_type->name && param_type->elem->name &&
strcmp(arg_type->name, param_type->elem->name) == 0) {
fprintf(stderr,
"%s: warning: argument %d of '%s' expects '%s*', "
"got '%s' (missing '&'?)\n",
ctx->fname, arg_idx + 1, fname, param_type->elem->name, arg_type->name);
}
}
/* Wrapper: parse_call_args with type checking against parameter types */
static int parse_call_args_checked(spl_comp_t *ctx, const char *fname,
spl_type_info_t **param_types, int nparams) {
int nargs = 0; /* physical slot count (for CALL) */
int nlogical = 0; /* logical arg index (for param_types lookup) */
if (peek(ctx)->type != TOK_R_PAREN) {
for (;;) {
spl_expr_result_t arg = spl_parse_expr(ctx, PREC_MIN);
if (param_types && nlogical < nparams && param_types[nlogical])
spl_check_arg_type(ctx, fname, arg.type, param_types[nlogical], nlogical);
/* For multi-slot struct/enum by-value parameters, expand
* the address left on the stack into multiple data slots. */
int arg_slots = 1;
if (param_types && nlogical < nparams && param_types[nlogical] && arg.type &&
param_types[nlogical]->kind != TYPE_PTR &&
!spl_type_is_scalar(param_types[nlogical]) &&
spl_type_size(param_types[nlogical]) > sizeof(spl_val_t)) {
usize nslots = (spl_type_size(param_types[nlogical]) + sizeof(spl_val_t) - 1) /
sizeof(spl_val_t);
for (usize i = 0; i < nslots; i++) {
spl_emit(ctx, SPL_DUP, SPL_VOID, 0);
if (i > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, i * sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
}
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
arg_slots = (int)nslots;
}
nargs += arg_slots;
nlogical++;
if (peek(ctx)->type == TOK_COMMA) {
advance(ctx);
continue;
}
break;
}
}
return nargs;
}
static spl_expr_result_t parse_ident(spl_comp_t *ctx) {
spl_tok_t *t = advance(ctx);
char name[256];
@@ -673,7 +738,7 @@ static spl_expr_result_t parse_ident(spl_comp_t *ctx) {
spl_func_info_t *f = &vec_at(ctx->funcs, fi);
advance(ctx); /* skip ( */
int nargs = parse_call_args(ctx);
int nargs = parse_call_args_checked(ctx, f->name, f->param_types, f->nparams);
expect(ctx, TOK_R_PAREN);
if (f->is_extern) {
@@ -690,6 +755,23 @@ static spl_expr_result_t parse_ident(spl_comp_t *ctx) {
}
spl_expr_result_t r = {f->ret_type, 0};
/* Multi-slot struct return: copy from returned address to caller's
* temp space. The returned address points to fp[1] in callee's frame
* (fp[0] is a gap). After RET, sp = callee_fp, so the first PUSH in
* emit_copy_slots (DUP) writes to the gap slot, not data. */
if (f->ret_type && !spl_type_is_scalar(f->ret_type) &&
spl_type_size(f->ret_type) > sizeof(spl_val_t)) {
usize nslots = spl_type_slot_count(f->ret_type);
int temp_offset = ctx->current_local_bytes;
ctx->current_local_bytes += (int)(nslots * sizeof(spl_val_t));
if (ctx->current_local_bytes > ctx->peak_local_bytes)
ctx->peak_local_bytes = ctx->current_local_bytes;
spl_emit_copy_slots(ctx, temp_offset, nslots);
spl_emit(ctx, SPL_LADDR, SPL_PTR, temp_offset);
r.is_lvalue = 1;
}
return r;
}
@@ -946,7 +1028,15 @@ static spl_expr_result_t parse_postfix_expr(spl_comp_t *ctx, spl_expr_result_t l
nargs = 0;
}
nargs += parse_call_args(ctx);
{
spl_type_info_t **cp = func->param_types;
int cn = func->nparams;
if (is_instance && cn > 0) {
cp++;
cn--;
}
nargs += parse_call_args_checked(ctx, func->name, cp, cn);
}
expect(ctx, TOK_R_PAREN);
/* Record fixup for forward-reference resolution */
@@ -957,6 +1047,20 @@ static spl_expr_result_t parse_postfix_expr(spl_comp_t *ctx, spl_expr_result_t l
vec_push(ctx->call_fixup_funcs, func->func_idx);
left = (spl_expr_result_t){func->ret_type, 0};
/* Multi-slot return: copy to temp, keep address */
if (func->ret_type && !spl_type_is_scalar(func->ret_type) &&
spl_type_size(func->ret_type) > sizeof(spl_val_t)) {
usize nslots = spl_type_slot_count(func->ret_type);
int temp_offset = ctx->current_local_bytes;
ctx->current_local_bytes += (int)(nslots * sizeof(spl_val_t));
if (ctx->current_local_bytes > ctx->peak_local_bytes)
ctx->peak_local_bytes = ctx->current_local_bytes;
spl_emit_copy_slots(ctx, temp_offset, nslots);
spl_emit(ctx, SPL_LADDR, SPL_PTR, temp_offset);
left.is_lvalue = 1;
}
break;
}
}
@@ -1193,7 +1297,10 @@ static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, sp
if (left.is_lvalue) {
spl_type_t bt = spl_type_emit_type(left.type);
if (op == TOK_ASSIGN) {
if (op == TOK_ASSIGN && left.type && !spl_type_is_scalar(left.type) &&
right.is_lvalue) {
spl_emit_copy_addr_to_addr(ctx, spl_type_slot_count(left.type), 0);
} else if (op == TOK_ASSIGN) {
spl_emit(ctx, SPL_STORE, bt, 0);
} else {
spl_emit(ctx, SPL_PICK, SPL_VOID, 1);
@@ -1310,8 +1417,20 @@ void spl_emit_match_value_cmp(spl_comp_t *ctx, int val_offset) {
* ============================================================ */
void spl_emit_ret(spl_comp_t *ctx, spl_type_info_t *ret_type) {
spl_type_t rt = ret_type ? spl_type_emit_type(ret_type) : SPL_VOID;
spl_emit(ctx, SPL_RET, rt, 0);
if (ret_type && !spl_type_is_scalar(ret_type) && spl_type_size(ret_type) > sizeof(spl_val_t)) {
/* Multi-slot struct return: copy to fp[0..nslots-1] (above saved_sp),
* then return address of fp[0]. Caller copies from that address to its
* own temp space (see parse_ident / method call handling). */
usize nslots = spl_type_slot_count(ret_type);
/* Store at fp[1..nslots] (skip fp[0] as gap) so that RET's PUSH
* and caller's LADDR both write to the gap slot, not the data. */
spl_emit_copy_slots(ctx, (int)sizeof(spl_val_t), nslots);
spl_emit(ctx, SPL_LADDR, SPL_PTR, (int)sizeof(spl_val_t));
spl_emit(ctx, SPL_RET, SPL_PTR, 0);
} else {
spl_type_t rt = ret_type ? spl_type_emit_type(ret_type) : SPL_VOID;
spl_emit(ctx, SPL_RET, rt, 0);
}
}
/* ============================================================
@@ -1336,7 +1455,11 @@ void spl_emit_store_init(spl_comp_t *ctx, int var_offset, spl_type_info_t *var_t
for (int i = (int)var_type->array_len - 1; i >= 0; i--) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset + (int)(i * stride));
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, bt, 0);
if (elem && !spl_type_is_scalar(elem)) {
spl_emit_copy_addr_to_addr(ctx, spl_type_slot_count(elem), 0);
} else {
spl_emit(ctx, SPL_STORE, bt, 0);
}
}
} else if (var_type && var_type->kind == TYPE_SLICE) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset + (int)sizeof(spl_val_t));

View File

@@ -86,14 +86,49 @@ static int parse_fn_body(spl_comp_t *ctx, const char *fn_name, spl_type_info_t *
skip_nl(ctx);
}
spl_patch(ctx, alloc_addr, ctx->peak_local_bytes / (int)sizeof(spl_val_t) - nparams);
/* Compute physical param slot count (1 slot = 8 bytes).
* Multi-slot struct params occupy ceil(size/8) slots; scalar params occupy 1 each. */
int total_phys_slots = 0;
for (int i = 0; i < nparams; i++) {
usize psz = spl_type_size(ptypes[i]);
total_phys_slots += (int)((psz + sizeof(spl_val_t) - 1) / sizeof(spl_val_t));
}
/* Ensure ALLOC provides enough space for multi-slot return value
* pre-placed at fp[0..nslots-1] (above saved_sp). */
if (ret_type && !spl_type_is_scalar(ret_type) &&
spl_type_size(ret_type) > sizeof(spl_val_t)) {
usize min_bytes = spl_type_slot_count(ret_type) * sizeof(spl_val_t);
if ((usize)ctx->peak_local_bytes < min_bytes)
ctx->peak_local_bytes = (int)min_bytes;
}
spl_patch(ctx, alloc_addr,
ctx->peak_local_bytes / (int)sizeof(spl_val_t) - total_phys_slots);
expect(ctx, TOK_R_BRACE);
}
spl_emit_defer_epilogue(ctx, ctx->scope_depth);
spl_pop_scope(ctx);
spl_emit(ctx, SPL_RET, SPL_VOID, 0);
/* Store param_types for call-site type checking and multi-slot expansion */
{
spl_func_info_t *f = &vec_at(ctx->funcs, fi);
f->param_types = calloc(nparams, sizeof(spl_type_info_t *));
f->param_names = calloc(nparams, sizeof(char *));
for (int i = 0; i < nparams; i++) {
f->param_types[i] = ptypes[i];
f->param_names[i] = strdup(pnames[i]);
}
}
/* Epilogue return: for multi-slot returns, return address of fp[1]
* (fp[0] is a gap slot that absorbs the RET PUSH, data at fp[1..nslots]). */
if (ctx->current_ret_type && !spl_type_is_scalar(ctx->current_ret_type) &&
spl_type_size(ctx->current_ret_type) > sizeof(spl_val_t)) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, (int)sizeof(spl_val_t));
spl_emit(ctx, SPL_RET, SPL_PTR, 0);
} else {
spl_emit(ctx, SPL_RET, SPL_VOID, 0);
}
spl_prog_end_func(&ctx->prog, fi);
ctx->current_func_idx = -1;
ctx->current_ret_type = NULL;
@@ -183,16 +218,6 @@ static void parse_method_decl(spl_comp_t *ctx, spl_type_info_t *container) {
int fi = parse_fn_body(ctx, qualified, ret_type, nparams, pnames, ptypes, 0);
{
spl_func_info_t *f = &vec_at(ctx->funcs, fi);
f->param_types = calloc(nparams, sizeof(spl_type_info_t *));
f->param_names = calloc(nparams, sizeof(char *));
for (int i = 0; i < nparams; i++) {
f->param_types[i] = ptypes[i];
f->param_names[i] = strdup(pnames[i]);
}
}
spl_type_add_method(container, mname, fi);
}

View File

@@ -451,11 +451,16 @@ static void parse_for_stmt(spl_comp_t *ctx) {
spl_emit(ctx, SPL_PUSH, SPL_U64, elem_byte_size);
spl_emit(ctx, SPL_MUL, SPL_U64, 0);
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
spl_type_t elem_bt = elem_type ? spl_type_emit_type(elem_type) : SPL_I32;
spl_emit(ctx, SPL_LOAD, elem_bt, 0);
spl_emit(ctx, SPL_LADDR, SPL_PTR, val_offset);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, elem_bt, 0);
if (elem_type && !spl_type_is_scalar(elem_type) &&
spl_type_size(elem_type) > sizeof(spl_val_t)) {
spl_emit_copy_slots(ctx, val_offset, spl_type_slot_count(elem_type));
} else {
spl_type_t elem_bt = elem_type ? spl_type_emit_type(elem_type) : SPL_I32;
spl_emit(ctx, SPL_LOAD, elem_bt, 0);
spl_emit(ctx, SPL_LADDR, SPL_PTR, val_offset);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, elem_bt, 0);
}
skip_nl(ctx);
spl_parse_block(ctx); /* body */

View File

@@ -8,6 +8,133 @@ type Pair = struct {
b: i32,
}
type Three = struct {
a: i64,
b: i64,
c: i64,
}
type Tag = enum {
Unknown,
BlockComment,
LineComment,
Ident,
Number,
}
type Token = struct {
loc_start: i32,
loc_end: i32,
tag: Tag,
}
fn make_three(a: i64, b: i64, c: i64) Three {
ret Three { .a = a, .b = b, .c = c };
}
fn sum_three(t: Three) i64 {
ret t.a + t.b + t.c;
}
fn make_token(s: i32, e: i32, t: Tag) Token {
ret Token { .loc_start = s, .loc_end = e, .tag = t };
}
fn test_ret_enum_struct() i32 {
var tok := make_token(10, 20, Tag.BlockComment);
if tok.loc_start != 10 { ret 1; }
if tok.loc_end != 20 { ret 2; }
if tok.tag != Tag.BlockComment { ret 3; }
ret 0;
}
fn test_nested_ret_enum() i32 {
var tok := make_token(1, 5, Tag.Ident);
if tok.tag != Tag.Ident { ret 1; }
var tok2 := make_token(6, 9, Tag.Number);
if tok2.tag != Tag.Number { ret 2; }
if tok.tag != Tag.Ident { ret 3; }
ret 0;
}
fn test_copy_token() i32 {
var tok := make_token(0, 100, Tag.LineComment);
var c := tok;
if c.loc_start != 0 { ret 1; }
if c.tag != Tag.LineComment { ret 2; }
c.tag = Tag.Ident;
if tok.tag != Tag.LineComment { ret 3; }
if c.tag != Tag.Ident { ret 4; }
ret 0;
}
fn test_pass_token(t: Token) i32 {
if t.tag != Tag.Number { ret 1; }
if t.loc_start != 42 { ret 2; }
ret 0;
}
fn test_param_pass_token() i32 {
var tok := make_token(42, 99, Tag.Number);
var r := test_pass_token(tok);
if r != 0 { ret r; }
ret 0;
}
fn test_get_tag(tok: Token) i32 {
ret tok.tag;
}
fn test_chain_func_call() i32 {
var tok := make_token(1, 2, Tag.BlockComment);
var tg := test_get_tag(tok);
if tg != Tag.BlockComment { ret 1; }
var tok2 := make_token(3, 4, Tag.LineComment);
var tg2 := test_get_tag(tok2);
if tg2 != Tag.LineComment { ret 2; }
ret 0;
}
fn test_ret_multi_slot_struct() i32 {
var t := make_three(10, 20, 30);
if t.a != 10 { ret 1; }
if t.b != 20 { ret 2; }
if t.c != 30 { ret 3; }
ret 0;
}
fn test_pass_struct_by_value() i32 {
var t := make_three(10, 20, 30);
var sum := sum_three(t);
if sum != 60 { ret 1; }
ret 0;
}
fn test_copy_struct() i32 {
var t := make_three(10, 20, 30);
var t2: Three;
t2 = t;
if t2.a != 10 { ret 1; }
t2.a = 99;
if t.a != 10 { ret 2; }
if t2.a != 99 { ret 3; }
ret 0;
}
fn test_multi_calls() i32 {
var ta := make_three(5, 10, 15);
var tb := make_three(20, 25, 30);
if ta.a + ta.b + ta.c != 30 { ret 1; }
if tb.a + tb.b + tb.c != 75 { ret 2; }
ret 0;
}
fn test_literal_arg() i32 {
var sum := sum_three(Three { .a = 7, .b = 8, .c = 9 });
if sum != 24 { ret 1; }
ret 0;
}
fn main() i32 {
/* struct 字段访问 */
var p: Pair;
@@ -60,5 +187,29 @@ fn main() i32 {
if o.inner.val != 42 { ret 7; }
if o.extra != 58 { ret 8; }
/* 多 slot 结构体返回值测试 */
var r9 := test_ret_multi_slot_struct();
if r9 != 0 { ret 100; }
var r10 := test_pass_struct_by_value();
if r10 != 0 { ret 101; }
var r11 := test_copy_struct();
if r11 != 0 { ret 102; }
var r12 := test_multi_calls();
if r12 != 0 { ret 103; }
var r13 := test_literal_arg();
if r13 != 0 { ret 104; }
/* 含 enum 字段的结构体返回值测试 */
var r14 := test_ret_enum_struct();
if r14 != 0 { ret 110; }
var r15 := test_nested_ret_enum();
if r15 != 0 { ret 111; }
var r16 := test_copy_token();
if r16 != 0 { ret 112; }
var r17 := test_param_pass_token();
if r17 != 0 { ret 113; }
var r18 := test_chain_func_call();
if r18 != 0 { ret 114; }
ret 0;
}