kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit ae8d0f623877d8dde6a25d8fe458c6bded4fe37c
parent 5bf1e27978b9c3fd2f5900243abaa81bcdbbcf8f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon,  1 Jun 2026 18:29:20 -0700

cg: expose multi-result functions in the public API (Track 5)

Clean break: CfreeCgFuncSig now carries results[]/nresults instead of a single
ret/ret_attrs, with a new CfreeCgFuncResult{type,attrs} mirroring
CfreeCgFuncParam. void is nresults==0. Replaces cfree_cg_type_func_ret/
_ret_attrs with cfree_cg_type_func_nresults + cfree_cg_type_func_result(idx),
and removes cfree_cg_ret_void (a void function returns via cfree_cg_ret with 0
results — one return entry point).

The internal CgTarget contract (CGFuncDesc.result_types/nresults,
CGCallDesc.results/nresults, ret(values,nvalues)) was already multi-result, so
this is a public-API + type-system + value.c change: CgType.func stores a
results array; cfree_cg_call pushes nresults values in declaration order (last
on TOS) and cfree_cg_ret pops the enclosing function's nresults; type interning
and the ABI/source-backend single-result consumers go through new
cg_type_func_nresults/_result_id and a cg_func_ret_type(CgType*) accessor.

Frontends updated to build results[] (C/toy/wasm) and migrate ret queries +
ret_void calls; toy gains a toy_cg_func_ret helper for its single-result model.

Fix a self-host regression the ret_void removal exposed: a 'no value' return on
a NON-void function (UB fall-off-the-end of an aggregate-returning function, or
an already-diagnosed bare 'return;') used to emit a bare ret that ignored the
result count; under the unified cfree_cg_ret it would pop a never-pushed result
and underflow the value stack. The C frontend now terminates such paths with
unreachable. bootstrap-debug reproduces (stage2==stage3, identical sha256);
test-cg-api/test-toy/test-smoke-x64/test-smoke-rv64 green.

Diffstat:
Minclude/cfree/cg.h | 30+++++++++++++++++++++++-------
Mlang/c/parse/cg_adapter.c | 11+++++++++--
Mlang/c/type/type.c | 8+++++++-
Mlang/toy/builtins.c | 2+-
Mlang/toy/decls.c | 11++++++++---
Mlang/toy/expr.c | 4++--
Mlang/toy/internal.h | 3+++
Mlang/toy/parser.c | 4++--
Mlang/toy/parser_core.c | 6++++++
Mlang/toy/types.c | 10++++++++--
Mlang/wasm/cg.c | 43++++++++++++++++++++++++-------------------
Msrc/abi/abi_aapcs64.c | 2+-
Msrc/abi/abi_rv64.c | 2+-
Msrc/abi/abi_sysv_x64.c | 2+-
Msrc/abi/abi_win64_x64.c | 2+-
Msrc/arch/c_target/c_emit.c | 6+++---
Msrc/arch/rv64/emu.c | 6+++++-
Msrc/arch/wasm/abi.c | 2+-
Msrc/cg/call.c | 120+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/cg/debug.c | 4+++-
Msrc/cg/internal.h | 8++++++--
Msrc/cg/session.c | 17+++++++++++------
Msrc/cg/type.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/cg/type.h | 13+++++++++++--
Msrc/cg/wide.c | 7++++++-
Msrc/emu/cpu.c | 6+++++-
Mtest/api/abi_classify_test.c | 16++++++++++++++--
Mtest/api/cg_switch_test.c | 6+++++-
Mtest/api/cg_type_test.c | 82++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mtest/arch/inline_public_test.h | 3+--
Mtest/cg/ir_recorder_test.c | 8++++++--
Mtest/cg/native_direct_target_test.c | 6+++++-
Mtest/opt/tiny_inline_test.c | 12++++++++++--
33 files changed, 402 insertions(+), 165 deletions(-)

diff --git a/include/cfree/cg.h b/include/cfree/cg.h @@ -98,9 +98,17 @@ typedef struct CfreeCgFuncParam { CfreeCgAbiAttrs attrs; } CfreeCgFuncParam; +/* Symmetric with CfreeCgFuncParam. A function declares 0..N results; these are + * semantic multi-results (e.g. wasm multi-value), not ABI-split parts of one + * value. nresults == 0 is void. */ +typedef struct CfreeCgFuncResult { + CfreeCgTypeId type; + CfreeCgAbiAttrs attrs; +} CfreeCgFuncResult; + typedef struct CfreeCgFuncSig { - CfreeCgTypeId ret; - CfreeCgAbiAttrs ret_attrs; + const CfreeCgFuncResult* results; /* nresults entries; NULL/0 == void */ + uint32_t nresults; const CfreeCgFuncParam* params; uint32_t nparams; CfreeCgCallConv call_conv; @@ -180,9 +188,10 @@ CFREE_API uint32_t cfree_cg_type_ptr_address_space(CfreeCompiler*, CFREE_API CfreeCgTypeId cfree_cg_type_array_elem(CfreeCompiler*, CfreeCgTypeId); CFREE_API uint64_t cfree_cg_type_array_count(CfreeCompiler*, CfreeCgTypeId); -CFREE_API CfreeCgTypeId cfree_cg_type_func_ret(CfreeCompiler*, CfreeCgTypeId); -CFREE_API CfreeCgAbiAttrs cfree_cg_type_func_ret_attrs(CfreeCompiler*, - CfreeCgTypeId); +CFREE_API uint32_t cfree_cg_type_func_nresults(CfreeCompiler*, CfreeCgTypeId); +CFREE_API CfreeCgFuncResult cfree_cg_type_func_result(CfreeCompiler*, + CfreeCgTypeId, + uint32_t index); CFREE_API uint32_t cfree_cg_type_func_nparams(CfreeCompiler*, CfreeCgTypeId); CFREE_API CfreeCgFuncParam cfree_cg_type_func_param(CfreeCompiler*, CfreeCgTypeId, @@ -831,13 +840,20 @@ typedef struct CfreeCgCallAttrs { * diagnostic; an ALLOWED tail call that is not realizable silently degrades * to an ordinary call followed by a synthesized return of the result. A * tail call whose callee return type is incompatible with the enclosing - * function's return type is a frontend bug and aborts under any policy. */ + * function's return type is a frontend bug and aborts under any policy. + * + * Results: a non-tail call pushes the callee's nresults values in declaration + * order, so the last result ends up on top of stack (TOS). A void callee + * (nresults == 0) pushes nothing. */ CFREE_API void cfree_cg_call(CfreeCg*, uint32_t nargs, CfreeCgTypeId fn_type, CfreeCgCallAttrs attrs); CFREE_API void cfree_cg_call_symbol(CfreeCg*, CfreeCgSym sym, uint32_t nargs, CfreeCgCallAttrs attrs); +/* Returns from the current function. Pops the enclosing function's nresults + * values, expecting them in declaration order with the last result on top of + * stack (the same order cfree_cg_call pushes them). A void function + * (nresults == 0) pops nothing — this is the single return entry point. */ CFREE_API void cfree_cg_ret(CfreeCg*); -CFREE_API void cfree_cg_ret_void(CfreeCg*); /* ============================================================ * Intrinsics diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -1036,8 +1036,15 @@ void pcg_ret(Parser* p, int has_value) { if (has_value) { if (pcg_emit_enabled(p)) cfree_cg_ret(p->cg); pcg_drop_type(p); - } else { - if (pcg_emit_enabled(p)) cfree_cg_ret_void(p->cg); + } else if (pcg_emit_enabled(p)) { + /* No value supplied. For a void function this is the normal 0-result + * return. For a non-void function it is a UB fall-off-the-end or an + * already-diagnosed bare `return;` — terminate with unreachable rather + * than asking cfree_cg_ret to pop a result that was never pushed. */ + if (p->cur_func_ret && p->cur_func_ret->kind != TY_VOID) + cfree_cg_unreachable(p->cg); + else + cfree_cg_ret(p->cg); } } diff --git a/lang/c/type/type.c b/lang/c/type/type.c @@ -661,9 +661,15 @@ static CfreeCgTypeId type_cg_lower(TypeCgLower* l, const Type* t, t->arr.count); case TY_FUNC: { CfreeCgFuncParam* params = NULL; + CfreeCgFuncResult result; CfreeCgFuncSig sig; memset(&sig, 0, sizeof sig); - sig.ret = type_cg_lower(l, t->fn.ret, TYPE_CG_VALUE); + if (t->fn.ret->kind != TY_VOID) { + memset(&result, 0, sizeof result); + result.type = type_cg_lower(l, t->fn.ret, TYPE_CG_VALUE); + sig.results = &result; + sig.nresults = 1; + } sig.nparams = t->fn.nparams; sig.abi_variadic = t->fn.variadic; sig.call_conv = CFREE_CG_CC_TARGET_C; diff --git a/lang/toy/builtins.c b/lang/toy/builtins.c @@ -380,7 +380,7 @@ static CfreeCgTypeId toy_parse_call_builtin(ToyParser* p) { if (!toy_parse_call_attr_tail(p, &attrs)) return CFREE_CG_TYPE_NONE; if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - ret_ty = cfree_cg_type_func_ret(p->c, fn_ty); + ret_ty = toy_cg_func_ret(p, fn_ty); tail = attrs.tail == CFREE_CG_TAIL_ALLOWED || attrs.tail == CFREE_CG_TAIL_MUST; if (tail && !p->allow_tail_call_expr) { diff --git a/lang/toy/decls.c b/lang/toy/decls.c @@ -426,9 +426,14 @@ int toy_parse_fn(ToyParser* p, int is_extern, int is_pub) { sig_params[i].type = param_types[i]; sig_params[i].attrs = param_attrs[i]; } + CfreeCgFuncResult sig_result; memset(&sig, 0, sizeof sig); - sig.ret = ret_type; - sig.ret_attrs = ret_attrs; + if (cfree_cg_type_kind(p->c, ret_type) != CFREE_CG_TYPE_VOID) { + sig_result.type = ret_type; + sig_result.attrs = ret_attrs; + sig.results = &sig_result; + sig.nresults = 1; + } sig.params = sig_params; sig.nparams = (uint32_t)nparams; sig.call_conv = attrs.has_call_conv ? attrs.call_conv : CFREE_CG_CC_TARGET_C; @@ -501,7 +506,7 @@ int toy_parse_fn(ToyParser* p, int is_extern, int is_pub) { if (!toy_parse_block(p)) return -1; if (ret_type == toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) { - cfree_cg_ret_void(p->cg); + cfree_cg_ret(p->cg); } cfree_cg_func_end(p->cg); diff --git a/lang/toy/expr.c b/lang/toy/expr.c @@ -769,8 +769,8 @@ static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { p->last_type = source_fn_type ? source_fn_type->ret - : toy_type_from_cg(p, cfree_cg_type_func_ret(p->c, fn_ty)); - return cfree_cg_type_func_ret(p->c, fn_ty); + : toy_type_from_cg(p, toy_cg_func_ret(p, fn_ty)); + return toy_cg_func_ret(p, fn_ty); } if (p->cur.kind == TOK_LBRACKET || p->cur.kind == TOK_DOT) { diff --git a/lang/toy/internal.h b/lang/toy/internal.h @@ -284,6 +284,9 @@ typedef struct ToyParser { } ToyParser; CfreeCgTypeId toy_builtin_type(ToyParser* p, CfreeCgBuiltinType ty); +/* The toy frontend models a single return value: the function's first result + * type, or void when it has no results. */ +CfreeCgTypeId toy_cg_func_ret(ToyParser* p, CfreeCgTypeId fn_ty); void toy_parser_init(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, ToyModule* module, const uint8_t* data, size_t len, const char* input_name); diff --git a/lang/toy/parser.c b/lang/toy/parser.c @@ -1360,7 +1360,7 @@ static int toy_parse_return_stmt(ToyParser* p) { toy_error(p, p->cur.loc, "tail call signature mismatch"); return 0; } - if (!fn && cfree_cg_type_func_ret(p->c, fn_ty) != p->cur_fn_ret) { + if (!fn && toy_cg_func_ret(p, fn_ty) != p->cur_fn_ret) { toy_error(p, p->cur.loc, "tail call signature mismatch"); return 0; } @@ -1384,7 +1384,7 @@ static int toy_parse_return_stmt(ToyParser* p) { toy_error(p, p->cur.loc, "return without value in non-void function"); return 0; } - cfree_cg_ret_void(p->cg); + cfree_cg_ret(p->cg); return 1; } if (p->cur_fn_ret != toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) { diff --git a/lang/toy/parser_core.c b/lang/toy/parser_core.c @@ -8,6 +8,12 @@ CfreeCgTypeId toy_builtin_type(ToyParser* p, CfreeCgBuiltinType ty) { return p->types.id[ty]; } +CfreeCgTypeId toy_cg_func_ret(ToyParser* p, CfreeCgTypeId fn_ty) { + if (cfree_cg_type_func_nresults(p->c, fn_ty) == 0) + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + return cfree_cg_type_func_result(p->c, fn_ty, 0).type; +} + void* toy_parser_zalloc(ToyParser* p, size_t count, size_t elem_size, const char* what) { CfreeHeap* h = cfree_compiler_context(p->c)->heap; diff --git a/lang/toy/types.c b/lang/toy/types.c @@ -157,8 +157,14 @@ CfreeCgTypeId toy_parse_type(ToyParser* p) { ret_toy_type = p->last_type; for (i = 0; i < nparams; i++) sig_params[i].type = param_types[i]; + CfreeCgFuncResult sig_result; memset(&sig, 0, sizeof sig); - sig.ret = ret_type; + if (cfree_cg_type_kind(p->c, ret_type) != CFREE_CG_TYPE_VOID) { + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = ret_type; + sig.results = &sig_result; + sig.nresults = 1; + } sig.params = sig_params; sig.nparams = (uint32_t)nparams; sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -485,7 +491,7 @@ ToyTypeId toy_type_from_cg(ToyParser* p, CfreeCgTypeId cg) { break; case CFREE_CG_TYPE_FUNC: type.kind = TOY_TYPE_FUNC; - type.ret = toy_type_from_cg(p, cfree_cg_type_func_ret(p->c, cg)); + type.ret = toy_type_from_cg(p, toy_cg_func_ret(p, cg)); type.count = cfree_cg_type_func_nparams(p->c, cg); break; case CFREE_CG_TYPE_RECORD: diff --git a/lang/wasm/cg.c b/lang/wasm/cg.c @@ -1677,7 +1677,6 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, CfreeCgFuncSig sig; CfreeCgDecl decl; memset(&sig, 0, sizeof sig); - sig.ret = b.id[CFREE_CG_BUILTIN_VOID]; sig.call_conv = CFREE_CG_CC_TARGET_C; rt.trap_func_ty = cfree_cg_type_func(c, sig); if (!rt.trap_func_ty) @@ -1704,7 +1703,6 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, memset(&init_param, 0, sizeof init_param); init_param.type = rt.instance_ptr_ty; memset(&sig, 0, sizeof sig); - sig.ret = b.id[CFREE_CG_BUILTIN_VOID]; sig.params = &init_param; sig.nparams = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -1730,9 +1728,14 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, for (j = 0; j < f->nparams; ++j) { cg_params[j + 1u].type = wasm_cg_type(c, b, f->params[j]); } + CfreeCgFuncResult sig_result; memset(&sig, 0, sizeof sig); - sig.ret = f->nresults ? wasm_cg_type(c, b, f->results[0]) - : b.id[CFREE_CG_BUILTIN_VOID]; + if (f->nresults) { + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = wasm_cg_type(c, b, f->results[0]); + sig.results = &sig_result; + sig.nresults = 1; + } sig.params = cg_params; sig.nparams = f->nparams + 1u; sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -1981,7 +1984,7 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, syms[m->start_func], func_types[m->start_func], instance_local, m->start_func, 0, m->start_field_loc, arena); - cfree_cg_ret_void(cg); + cfree_cg_ret(cg); cfree_cg_func_end(cg); } /* Host-import resolution metadata: emit __cfree_wasm_nimports always, and @@ -2272,9 +2275,14 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, indirect_params[0].type = rt.instance_ptr_ty; for (uint32_t p = 0; p < t->nparams; ++p) indirect_params[p + 1u].type = wasm_cg_type(c, b, t->params[p]); + CfreeCgFuncResult indirect_result; memset(&indirect_sig, 0, sizeof indirect_sig); - indirect_sig.ret = t->nresults ? wasm_cg_type(c, b, t->results[0]) - : b.id[CFREE_CG_BUILTIN_VOID]; + if (t->nresults) { + memset(&indirect_result, 0, sizeof indirect_result); + indirect_result.type = wasm_cg_type(c, b, t->results[0]); + indirect_sig.results = &indirect_result; + indirect_sig.nresults = 1; + } indirect_sig.params = indirect_params; indirect_sig.nparams = t->nparams + 1u; indirect_sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -2457,9 +2465,14 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, ref_params[0].type = rt.instance_ptr_ty; for (uint32_t p = 0; p < t->nparams; ++p) ref_params[p + 1u].type = wasm_cg_type(c, b, t->params[p]); + CfreeCgFuncResult ref_result; memset(&ref_sig, 0, sizeof ref_sig); - ref_sig.ret = t->nresults ? wasm_cg_type(c, b, t->results[0]) - : b.id[CFREE_CG_BUILTIN_VOID]; + if (t->nresults) { + memset(&ref_result, 0, sizeof ref_result); + ref_result.type = wasm_cg_type(c, b, t->results[0]); + ref_sig.results = &ref_result; + ref_sig.nresults = 1; + } ref_sig.params = ref_params; ref_sig.nparams = t->nparams + 1u; ref_sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -2512,10 +2525,7 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, break; } case WASM_INSN_RETURN: - if (f->nresults) - cfree_cg_ret(cg); - else - cfree_cg_ret_void(cg); + cfree_cg_ret(cg); break; case WASM_INSN_DROP: cfree_cg_drop(cg); @@ -3675,12 +3685,7 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, f->insns[last - 1u].kind == WASM_INSN_RETURN_CALL_INDIRECT || f->insns[last - 1u].kind == WASM_INSN_RETURN_CALL_REF || f->insns[last - 1u].kind == WASM_INSN_UNREACHABLE); - if (!body_terminates) { - if (f->nresults) - cfree_cg_ret(cg); - else - cfree_cg_ret_void(cg); - } + if (!body_terminates) cfree_cg_ret(cg); } cfree_cg_func_end(cg); heap->free(heap, control, sizeof(WasmCgControl) * control_cap); diff --git a/src/abi/abi_aapcs64.c b/src/abi/abi_aapcs64.c @@ -123,7 +123,7 @@ ABIFuncInfo* aapcs64_compute_func_info(TargetABI* a, CfreeCgTypeId fn) { const CgType* fnty = cg_type_get(a->c, fn); memset(info, 0, sizeof *info); - classify_one(a, fnty->func.ret, &info->ret, /*is_return=*/1); + classify_one(a, cg_func_ret_type(fnty), &info->ret, /*is_return=*/1); info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; info->variadic = fnty->func.abi_variadic; diff --git a/src/abi/abi_rv64.c b/src/abi/abi_rv64.c @@ -229,7 +229,7 @@ static ABIFuncInfo* rv64_compute_func_info(TargetABI* a, CfreeCgTypeId fn) { const CgType* fnty = cg_type_get(a->c, fn); memset(info, 0, sizeof *info); - classify_one(a, fnty->func.ret, &info->ret, /*is_return=*/1); + classify_one(a, cg_func_ret_type(fnty), &info->ret, /*is_return=*/1); info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; info->variadic = fnty->func.abi_variadic; diff --git a/src/abi/abi_sysv_x64.c b/src/abi/abi_sysv_x64.c @@ -238,7 +238,7 @@ static ABIFuncInfo* sysv_x64_compute_func_info(TargetABI* a, CfreeCgTypeId fn) { const CgType* fnty = cg_type_get(a->c, fn); memset(info, 0, sizeof *info); - classify_one(a, fnty->func.ret, &info->ret, /*is_return=*/1); + classify_one(a, cg_func_ret_type(fnty), &info->ret, /*is_return=*/1); info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; info->variadic = fnty->func.abi_variadic; diff --git a/src/abi/abi_win64_x64.c b/src/abi/abi_win64_x64.c @@ -154,7 +154,7 @@ static ABIFuncInfo* win64_x64_compute_func_info(TargetABI* a, const CgType* fnty = cg_type_get(a->c, fn); memset(info, 0, sizeof *info); - classify_one(a, fnty->func.ret, &info->ret, /*is_return=*/1); + classify_one(a, cg_func_ret_type(fnty), &info->ret, /*is_return=*/1); info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; info->variadic = fnty->func.abi_variadic; diff --git a/src/arch/c_target/c_emit.c b/src/arch/c_target/c_emit.c @@ -230,7 +230,7 @@ static void c_ensure_typedef(CTarget* t, CfreeCgTypeId tid) { static void c_emit_typedef_for_func(CTarget* t, CfreeCgTypeId tid, const CgType* ty) { /* Emit recursively for return and param types if they're composites. */ - CfreeCgTypeId ret = api_unalias_type(t->c, ty->func.ret); + CfreeCgTypeId ret = api_unalias_type(t->c, cg_func_ret_type(ty)); const CgType* rty = cg_type_get(t->c, ret); if (rty && (rty->kind == CFREE_CG_TYPE_RECORD || rty->kind == CFREE_CG_TYPE_ARRAY || @@ -247,7 +247,7 @@ static void c_emit_typedef_for_func(CTarget* t, CfreeCgTypeId tid, } } cbuf_puts(&t->typedefs, "typedef "); - cbuf_puts(&t->typedefs, c_typename(t, ty->func.ret)); + cbuf_puts(&t->typedefs, c_typename(t, cg_func_ret_type(ty))); cbuf_puts(&t->typedefs, " (*__ty_"); cbuf_put_u64(&t->typedefs, (u64)tid); cbuf_puts(&t->typedefs, ")("); @@ -2242,7 +2242,7 @@ void c_emit_call(CTarget* t, const CGCallDesc* d) { if (!fty || fty->kind != CFREE_CG_TYPE_FUNC) { compiler_panic(t->c, loc, "C target: call: bad fn_type"); } - CfreeCgTypeId ret_type = fty->func.ret; + CfreeCgTypeId ret_type = cg_func_ret_type(fty); int is_tail = (d->flags & CG_CALL_TAIL) != 0; CfreeCgTypeId* result_types = NULL; if (d->nresults > 1) { diff --git a/src/arch/rv64/emu.c b/src/arch/rv64/emu.c @@ -58,12 +58,16 @@ static CfreeCgTypeId rv64_emu_func_type(CfreeCompiler* c, CfreeCgTypeId ret, const CfreeCgTypeId* params, u32 nparams) { CfreeCgFuncParam p[5]; + CfreeCgFuncResult result; CfreeCgFuncSig sig; u32 i; memset(p, 0, sizeof(p)); for (i = 0; i < nparams; ++i) p[i].type = params[i]; memset(&sig, 0, sizeof(sig)); - sig.ret = ret; + memset(&result, 0, sizeof(result)); + result.type = ret; + sig.results = &result; + sig.nresults = 1; sig.params = p; sig.nparams = nparams; sig.call_conv = CFREE_CG_CC_TARGET_C; diff --git a/src/arch/wasm/abi.c b/src/arch/wasm/abi.c @@ -127,7 +127,7 @@ static ABIFuncInfo* wasm32_compute_func_info(TargetABI* a, CfreeCgTypeId fn) { const CgType* fnty = cg_type_get(a->c, fn); memset(info, 0, sizeof *info); - classify_one(a, fnty->func.ret, &info->ret, /*is_return=*/1); + classify_one(a, cg_func_ret_type(fnty), &info->ret, /*is_return=*/1); info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; info->variadic = fnty->func.abi_variadic ? 1u : 0u; diff --git a/src/cg/call.c b/src/cg/call.c @@ -112,9 +112,15 @@ static void api_call_clobber_boundary(CfreeCg* g, const CGCallDesc* d) { } static int api_tail_ret_compatible(CfreeCg* g, CfreeCgTypeId callee_fn_type) { - CfreeCgTypeId callee_ret = cg_type_func_ret_id(g->c, callee_fn_type); - return api_unalias_type(g->c, g->fn_ret_type) == - api_unalias_type(g->c, callee_ret); + u32 cn = cg_type_func_nresults(g->c, callee_fn_type); + if (cn != g->fn_nresults) return 0; + for (u32 i = 0; i < cn; ++i) { + CfreeCgTypeId cr = cg_type_func_result_id(g->c, callee_fn_type, i); + if (api_unalias_type(g->c, g->fn_result_types[i]) != + api_unalias_type(g->c, cr)) + return 0; + } + return 1; } static int api_tail_decide(CfreeCg* g, const CGCallDesc* desc, @@ -139,17 +145,9 @@ static int api_tail_decide(CfreeCg* g, const CGCallDesc* desc, return 0; } -static void api_tail_fallback_ret(CfreeCg* g, CfreeCgTypeId ret_ty) { - if (cg_type_is_void(g->c, ret_ty)) - g->target->ret(g->target, NULL, 0); - else - cfree_cg_ret(g); -} - static void api_finish_call(CfreeCg* g, CGCallDesc* desc, CGLocal* args, u32 nargs, Operand callee_op, ApiSValue* callee, - CfreeCgTypeId ret_ty, int has_result, int want_tail, - int emit_tail) { + int want_tail, int emit_tail) { if (emit_tail) api_temp_locals_finish(g); if (!emit_tail) api_call_clobber_boundary(g, desc); g->target->call(g->target, desc); @@ -158,16 +156,21 @@ static void api_finish_call(CfreeCg* g, CGCallDesc* desc, CGLocal* args, if (callee && callee->op.kind != OPK_GLOBAL) { api_release_temp_local(g, callee_op.v.local); } - if (has_result) api_push_call_result(g, desc->results[0], ret_ty); - if (want_tail && !emit_tail) api_tail_fallback_ret(g, ret_ty); + /* Push results in declaration order so the last result ends up on TOS. */ + for (u32 i = 0; i < desc->nresults; ++i) { + CfreeCgTypeId rty = cg_type_func_result_id(g->c, desc->fn_type, i); + api_push_call_result(g, desc->results[i], rty); + } + /* ALLOWED tail call that degraded to an ordinary call: synthesize the + * caller's return of the just-pushed result(s). */ + if (want_tail && !emit_tail) cfree_cg_ret(g); } void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type, CfreeCgCallAttrs attrs) { CgTarget* T; CfreeCgTypeId fty; - CfreeCgTypeId ret_ty; - int has_result; + u32 nresults; CGLocal* args; CGLocal* results = NULL; CGCallDesc desc; @@ -182,7 +185,6 @@ void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type, T = g->target; fty = resolve_type(g->c, fn_type); if (!fty) return; - ret_ty = cg_type_func_ret_id(g->c, fty); if (g->sp < (u32)nargs + 1u) { compiler_panic(g->c, g->cur_loc, "CfreeCg: call stack underflow"); @@ -218,26 +220,30 @@ void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type, desc.inline_policy = inline_policy; emit_tail = want_tail ? api_tail_decide(g, &desc, attrs.tail) : 0; - has_result = !emit_tail && !cg_type_is_void(g->c, ret_ty); desc.flags = emit_tail ? CG_CALL_TAIL : CG_CALL_NONE; - - if (has_result) { - results = arena_array(g->c->tu, CGLocal, 1); - results[0] = api_alloc_call_result(g, ret_ty); + nresults = emit_tail ? 0u : cg_type_func_nresults(g->c, fty); + if (nresults > API_CG_MAX_RESULTS) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: call has more than %u results", + API_CG_MAX_RESULTS); + nresults = API_CG_MAX_RESULTS; + } + if (nresults) { + results = arena_array(g->c->tu, CGLocal, nresults); + for (u32 i = 0; i < nresults; ++i) + results[i] = api_alloc_call_result(g, cg_type_func_result_id(g->c, fty, i)); desc.results = results; - desc.nresults = 1; + desc.nresults = nresults; } (void)T; - api_finish_call(g, &desc, args, nargs, callee_op, &callee, ret_ty, has_result, - want_tail, emit_tail); + api_finish_call(g, &desc, args, nargs, callee_op, &callee, want_tail, + emit_tail); } void api_call_symbol_common(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, CfreeCgCallAttrs attrs) { CfreeCgTypeId fty; - CfreeCgTypeId ret_ty; - int has_result; + u32 nresults; CGLocal* args; CGLocal* results = NULL; CGCallDesc desc; @@ -251,7 +257,6 @@ void api_call_symbol_common(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, attrs.tail == CFREE_CG_TAIL_ALLOWED || attrs.tail == CFREE_CG_TAIL_MUST; fty = api_sym_type(g, sym); if (!fty) return; - ret_ty = cg_type_func_ret_id(g->c, fty); if (g->sp < nargs) { compiler_panic(g->c, g->cur_loc, "CfreeCg: call stack underflow"); return; @@ -277,17 +282,21 @@ void api_call_symbol_common(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, desc.inline_policy = inline_policy; emit_tail = want_tail ? api_tail_decide(g, &desc, attrs.tail) : 0; - has_result = !emit_tail && !cg_type_is_void(g->c, ret_ty); desc.flags = emit_tail ? CG_CALL_TAIL : CG_CALL_NONE; - - if (has_result) { - results = arena_array(g->c->tu, CGLocal, 1); - results[0] = api_alloc_call_result(g, ret_ty); + nresults = emit_tail ? 0u : cg_type_func_nresults(g->c, fty); + if (nresults > API_CG_MAX_RESULTS) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: call has more than %u results", + API_CG_MAX_RESULTS); + nresults = API_CG_MAX_RESULTS; + } + if (nresults) { + results = arena_array(g->c->tu, CGLocal, nresults); + for (u32 i = 0; i < nresults; ++i) + results[i] = api_alloc_call_result(g, cg_type_func_result_id(g->c, fty, i)); desc.results = results; - desc.nresults = 1; + desc.nresults = nresults; } - api_finish_call(g, &desc, args, nargs, callee_op, NULL, ret_ty, has_result, - want_tail, emit_tail); + api_finish_call(g, &desc, args, nargs, callee_op, NULL, want_tail, emit_tail); } void cfree_cg_call_symbol(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, @@ -296,30 +305,31 @@ void cfree_cg_call_symbol(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, } void cfree_cg_ret(CfreeCg* g) { - ApiSValue v; - CfreeCgTypeId rty; - Operand ret_op; - CGLocal value; + ApiSValue popped[API_CG_MAX_RESULTS]; + CGLocal values[API_CG_MAX_RESULTS]; + u32 n; if (!g) return; - rty = g->fn_ret_type; - if (cg_type_is_void(g->c, rty)) { + n = g->fn_nresults; + if (n == 0) { g->target->ret(g->target, NULL, 0); return; } - v = api_pop(g); - if (cg_type_is_aggregate(g->c, rty)) { - value = api_materialize_call_local(g, &v, rty); - } else { - ret_op = api_force_local(g, &v, rty); - value = ret_op.v.local; + /* TOS holds the last result; pop in reverse so values[] is declaration + * order, matching the order cfree_cg_call pushes results. */ + for (u32 k = 0; k < n; ++k) { + u32 idx = n - 1u - k; + CfreeCgTypeId rty = g->fn_result_types[idx]; + ApiSValue v = api_pop(g); + if (cg_type_is_aggregate(g->c, rty)) { + values[idx] = api_materialize_call_local(g, &v, rty); + } else { + Operand ret_op = api_force_local(g, &v, rty); + values[idx] = ret_op.v.local; + } + popped[idx] = v; } - g->target->ret(g->target, &value, 1); - api_release(g, &v); -} - -void cfree_cg_ret_void(CfreeCg* g) { - if (!g) return; - g->target->ret(g->target, NULL, 0); + g->target->ret(g->target, values, n); + for (u32 i = 0; i < n; ++i) api_release(g, &popped[i]); } /* ============================================================ diff --git a/src/cg/debug.c b/src/cg/debug.c @@ -43,7 +43,9 @@ DebugTypeId api_debug_type(CfreeCg* g, CfreeCgTypeId id) { } case CFREE_CG_TYPE_FUNC: { Heap* h = (Heap*)g->c->ctx->heap; - DebugTypeId ret = api_debug_type(g, ty->func.ret); + DebugTypeId ret = ty->func.nresults + ? api_debug_type(g, ty->func.results[0].type) + : DEBUG_TYPE_NONE; DebugTypeId* params = NULL; DebugTypeId fn; if (ret == DEBUG_TYPE_NONE) ret = debug_type_void(g->debug); diff --git a/src/cg/internal.h b/src/cg/internal.h @@ -77,6 +77,10 @@ typedef struct ApiSValue { #define API_CG_STACK_INITIAL 16u +/* Upper bound on a function's declared results (semantic multi-results, e.g. + * wasm multi-value). Real counts are tiny; declaring more is rejected. */ +#define API_CG_MAX_RESULTS 16u + /* Largest scalar the codegen lowers as a native (lock-free) atomic. All * current targets — aa64, x64, rv64, wasm32 — provide 8-byte (i64-width) * atomics, so this is both the legality ceiling and the lock-free ceiling. @@ -131,7 +135,8 @@ struct CfreeCg { u32 locals_cap; CfreeCgTypeId fn_ret_type; - CfreeCgTypeId fn_result_types[1]; + CfreeCgTypeId fn_result_types[API_CG_MAX_RESULTS]; + u32 fn_nresults; SrcLoc cur_loc; CGFuncDesc fn_desc; @@ -246,7 +251,6 @@ void api_call_symbol_common(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, void cfree_cg_call_symbol(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, CfreeCgCallAttrs attrs); void cfree_cg_ret(CfreeCg* g); -void cfree_cg_ret_void(CfreeCg* g); CfreeCgLabel cfree_cg_label_new(CfreeCg* g); void cfree_cg_label_place(CfreeCg* g, CfreeCgLabel label); void cfree_cg_jump(CfreeCg* g, CfreeCgLabel label); diff --git a/src/cg/session.c b/src/cg/session.c @@ -46,6 +46,7 @@ static void cg_free_obj_state(CfreeCg* g) { g->sym_cap = 0; g->fn_ret_type = 0; g->fn_result_types[0] = 0; + g->fn_nresults = 0; memset(&g->fn_desc, 0, sizeof(g->fn_desc)); memset(g->fn_params, 0, sizeof(g->fn_params)); memset(g->scopes, 0, sizeof(g->scopes)); @@ -316,13 +317,16 @@ void cfree_cg_func_begin_attrs(CfreeCg* g, CfreeCgSym cg_sym, g->fn_desc.flags |= CGFD_NORETURN; g->fn_ret_type = cg_type_func_ret_id(c, fty); - if (cg_type_is_void(c, g->fn_ret_type)) { - g->fn_desc.nresults = 0; - g->fn_result_types[0] = CFREE_CG_TYPE_NONE; - } else { - g->fn_desc.nresults = 1; - g->fn_result_types[0] = g->fn_ret_type; + g->fn_nresults = cg_type_func_nresults(c, fty); + if (g->fn_nresults > API_CG_MAX_RESULTS) { + compiler_panic(c, g->cur_loc, + "CfreeCg: function declares more than %u results", + API_CG_MAX_RESULTS); + g->fn_nresults = API_CG_MAX_RESULTS; } + for (u32 i = 0; i < g->fn_nresults; ++i) + g->fn_result_types[i] = cg_type_func_result_id(c, fty, i); + g->fn_desc.nresults = g->fn_nresults; g->nlocals = 0; g->sp = 0; @@ -400,6 +404,7 @@ void cfree_cg_func_end(CfreeCg* g) { if (g->debug) debug_func_end(g->debug); g->fn_ret_type = CFREE_CG_TYPE_NONE; g->fn_result_types[0] = CFREE_CG_TYPE_NONE; + g->fn_nresults = 0; g->nscopes = 0; memset(g->scopes, 0, sizeof g->scopes); } diff --git a/src/cg/type.c b/src/cg/type.c @@ -20,7 +20,8 @@ typedef struct CgApiType { const CfreeCgField* fields; const CfreeCgEnumValue* values; const CfreeCgFuncParam* params; - CfreeCgAbiAttrs ret_attrs; + const CfreeCgFuncResult* results; + u32 nresults; CfreeCgCallConv call_conv; u8 kind; u8 abi_variadic; @@ -265,8 +266,30 @@ CfreeCgTypeId cg_type_func_ret_id(Compiler* c, CfreeCgTypeId id) { const CgType* ty = cg_type_get(c, id); if (ty && ty->kind == CFREE_CG_TYPE_ALIAS) return cg_type_func_ret_id(c, ty->alias.base); - return ty && ty->kind == CFREE_CG_TYPE_FUNC ? ty->func.ret - : CFREE_CG_TYPE_NONE; + if (!ty || ty->kind != CFREE_CG_TYPE_FUNC) return CFREE_CG_TYPE_NONE; + return ty->func.nresults ? ty->func.results[0].type + : builtin_id(CFREE_CG_BUILTIN_VOID); +} + +CfreeCgTypeId cg_func_ret_type(const CgType* fnty) { + return fnty->func.nresults ? fnty->func.results[0].type + : builtin_id(CFREE_CG_BUILTIN_VOID); +} + +uint32_t cg_type_func_nresults(Compiler* c, CfreeCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + if (ty && ty->kind == CFREE_CG_TYPE_ALIAS) + return cg_type_func_nresults(c, ty->alias.base); + return ty && ty->kind == CFREE_CG_TYPE_FUNC ? ty->func.nresults : 0; +} + +CfreeCgTypeId cg_type_func_result_id(Compiler* c, CfreeCgTypeId id, u32 index) { + const CgType* ty = cg_type_get(c, id); + if (ty && ty->kind == CFREE_CG_TYPE_ALIAS) + return cg_type_func_result_id(c, ty->alias.base, index); + if (!ty || ty->kind != CFREE_CG_TYPE_FUNC || index >= ty->func.nresults) + return CFREE_CG_TYPE_NONE; + return ty->func.results[index].type; } CfreeCgTypeId cg_type_func_param_id(Compiler* c, CfreeCgTypeId id, u32 index) { @@ -332,6 +355,16 @@ static int cg_params_eq(const CfreeCgFuncParam* a, const CfreeCgFuncParam* b, return 1; } +static int cg_results_eq(const CfreeCgFuncResult* a, const CfreeCgFuncResult* b, + u32 n) { + for (u32 i = 0; i < n; ++i) + if (a[i].type != b[i].type || + memcmp(&a[i].attrs, &b[i].attrs, sizeof(a[i].attrs)) != 0) { + return 0; + } + return 1; +} + static CfreeCgTypeId find_func_type_id(Compiler* c, CfreeCgFuncSig sig) { CgApiState* s; u32 n; @@ -341,10 +374,10 @@ static CfreeCgTypeId find_func_type_id(Compiler* c, CfreeCgFuncSig sig) { for (u32 i = 0; i < n; ++i) { CgApiType* e = CgApiTypes_at(&s->types, i); if (!e || e->kind != CG_API_TYPE_FUNC) continue; - if (e->base != sig.ret || e->count != sig.nparams) continue; + if (e->nresults != sig.nresults || e->count != sig.nparams) continue; if (e->abi_variadic != (sig.abi_variadic != 0)) continue; if (e->call_conv != sig.call_conv) continue; - if (memcmp(&e->ret_attrs, &sig.ret_attrs, sizeof(e->ret_attrs)) != 0) { + if (sig.nresults && !cg_results_eq(e->results, sig.results, sig.nresults)) { continue; } if (sig.nparams && !cg_params_eq(e->params, sig.params, sig.nparams)) { @@ -392,6 +425,17 @@ static CfreeCgFuncParam* copy_cg_params(Compiler* c, return dst; } +static CfreeCgFuncResult* copy_cg_results(Compiler* c, + const CfreeCgFuncResult* src, u32 n) { + CfreeCgFuncResult* dst; + if (!n) return NULL; + if (!src) return NULL; + dst = arena_array(&c->global->arena, CfreeCgFuncResult, n); + if (!dst) return NULL; + memcpy(dst, src, sizeof(*dst) * n); + return dst; +} + static CgTypeField* copy_cg_fields(Compiler* c, const CfreeCgField* src, u32 n) { CgTypeField* dst; @@ -583,8 +627,11 @@ static int cg_type_set_enum(Compiler* c, CgApiType* e, CfreeSym tag, } static int cg_type_set_func(Compiler* c, CgApiType* e, CfreeCgFuncSig sig, - CfreeCgFuncParam* params) { - if (!cg_type_get(c, sig.ret)) return 0; + CfreeCgFuncParam* params, + CfreeCgFuncResult* results) { + for (u32 i = 0; i < sig.nresults; ++i) { + if (!cg_type_get(c, sig.results[i].type)) return 0; + } for (u32 i = 0; i < sig.nparams; ++i) { if (!cg_type_get(c, sig.params[i].type)) return 0; } @@ -592,12 +639,12 @@ static int cg_type_set_func(Compiler* c, CgApiType* e, CfreeCgFuncSig sig, e->cg.kind = CFREE_CG_TYPE_FUNC; e->cg.size = 1; e->cg.align = 1; - e->cg.func.ret = sig.ret; + e->cg.func.results = results; + e->cg.func.nresults = sig.nresults; e->cg.func.params = params; e->cg.func.nparams = sig.nparams; e->cg.func.call_conv = sig.call_conv; e->cg.func.abi_variadic = sig.abi_variadic != 0; - e->cg.func.ret_attrs = sig.ret_attrs; return 1; } @@ -732,14 +779,22 @@ CfreeCgTypeId cfree_cg_type_enum(CfreeCompiler* c, CfreeSym tag, CfreeCgTypeId cfree_cg_type_func(CfreeCompiler* c, CfreeCgFuncSig sig) { CfreeCgFuncParam* copied = NULL; + CfreeCgFuncResult* copied_results = NULL; CfreeCgTypeId id; CgApiType* e; - if (!c || !cg_type_get(c, sig.ret) || (sig.nparams && !sig.params) || - sig.nparams > UINT16_MAX) { + if (!c || (sig.nresults && !sig.results) || (sig.nparams && !sig.params) || + sig.nparams > UINT16_MAX || sig.nresults > UINT16_MAX) { return CFREE_CG_TYPE_NONE; } + for (u32 i = 0; i < sig.nresults; ++i) { + if (!cg_type_get(c, sig.results[i].type)) return CFREE_CG_TYPE_NONE; + } id = find_func_type_id(c, sig); if (id != CFREE_CG_TYPE_NONE) return id; + if (sig.nresults) { + copied_results = copy_cg_results(c, sig.results, sig.nresults); + if (!copied_results) return CFREE_CG_TYPE_NONE; + } if (sig.nparams) { copied = copy_cg_params(c, sig.params, sig.nparams); if (!copied) return CFREE_CG_TYPE_NONE; @@ -749,14 +804,14 @@ CfreeCgTypeId cfree_cg_type_func(CfreeCompiler* c, CfreeCgFuncSig sig) { } e = type_alloc(c, &id); if (!e) return CFREE_CG_TYPE_NONE; - e->base = sig.ret; e->count = sig.nparams; e->params = copied; - e->ret_attrs = sig.ret_attrs; + e->results = copied_results; + e->nresults = sig.nresults; e->call_conv = sig.call_conv; e->abi_variadic = sig.abi_variadic != 0; e->kind = CG_API_TYPE_FUNC; - if (!cg_type_set_func(c, e, sig, copied)) { + if (!cg_type_set_func(c, e, sig, copied, copied_results)) { return CFREE_CG_TYPE_NONE; } return id; @@ -822,23 +877,25 @@ uint64_t cfree_cg_type_array_count(CfreeCompiler* c, CfreeCgTypeId id) { return (ty && ty->kind == CFREE_CG_TYPE_ARRAY) ? ty->array.count : 0u; } -CfreeCgTypeId cfree_cg_type_func_ret(CfreeCompiler* c, CfreeCgTypeId id) { +uint32_t cfree_cg_type_func_nresults(CfreeCompiler* c, CfreeCgTypeId id) { const CgType* ty = cg_type_get(c, id); - return (ty && ty->kind == CFREE_CG_TYPE_FUNC) ? ty->func.ret - : CFREE_CG_TYPE_NONE; + return (ty && ty->kind == CFREE_CG_TYPE_FUNC) ? ty->func.nresults : 0; } -uint32_t cfree_cg_type_func_nparams(CfreeCompiler* c, CfreeCgTypeId id) { +CfreeCgFuncResult cfree_cg_type_func_result(CfreeCompiler* c, CfreeCgTypeId id, + uint32_t index) { const CgType* ty = cg_type_get(c, id); - return (ty && ty->kind == CFREE_CG_TYPE_FUNC) ? ty->func.nparams : 0; + CfreeCgFuncResult empty; + memset(&empty, 0, sizeof(empty)); + if (!ty || ty->kind != CFREE_CG_TYPE_FUNC || index >= ty->func.nresults) { + return empty; + } + return ty->func.results[index]; } -CfreeCgAbiAttrs cfree_cg_type_func_ret_attrs(CfreeCompiler* c, - CfreeCgTypeId id) { +uint32_t cfree_cg_type_func_nparams(CfreeCompiler* c, CfreeCgTypeId id) { const CgType* ty = cg_type_get(c, id); - CfreeCgAbiAttrs empty; - memset(&empty, 0, sizeof(empty)); - return (ty && ty->kind == CFREE_CG_TYPE_FUNC) ? ty->func.ret_attrs : empty; + return (ty && ty->kind == CFREE_CG_TYPE_FUNC) ? ty->func.nparams : 0; } CfreeCgFuncParam cfree_cg_type_func_param(CfreeCompiler* c, CfreeCgTypeId id, diff --git a/src/cg/type.h b/src/cg/type.h @@ -39,12 +39,12 @@ typedef struct CgType { u64 count; } array; struct { - CfreeCgTypeId ret; + CfreeCgFuncResult* results; + u32 nresults; CfreeCgFuncParam* params; u32 nparams; CfreeCgCallConv call_conv; int abi_variadic; - CfreeCgAbiAttrs ret_attrs; } func; struct { CfreeSym tag; @@ -90,7 +90,16 @@ int cg_type_is_void(Compiler*, CfreeCgTypeId); int cg_type_is_aggregate(Compiler*, CfreeCgTypeId); CfreeCgTypeId cg_type_ptr_to(Compiler*, CfreeCgTypeId); CfreeCgTypeId cg_type_pointee(Compiler*, CfreeCgTypeId); +/* The first result's type, or VOID when the function has no results. Kept for + * the common single-result paths (tail-return compatibility, source backends); + * multi-result callers use cg_type_func_nresults + cg_type_func_result_id. */ CfreeCgTypeId cg_type_func_ret_id(Compiler*, CfreeCgTypeId); +uint32_t cg_type_func_nresults(Compiler*, CfreeCgTypeId); +CfreeCgTypeId cg_type_func_result_id(Compiler*, CfreeCgTypeId, uint32_t); +/* First-result type (or VOID) given a resolved CFREE_CG_TYPE_FUNC CgType. For + * the single-result/void consumers (ABI classification, source backends) that + * already hold the CgType. */ +CfreeCgTypeId cg_func_ret_type(const CgType* fnty); CfreeCgTypeId cg_type_func_param_id(Compiler*, CfreeCgTypeId, uint32_t); #endif diff --git a/src/cg/wide.c b/src/cg/wide.c @@ -179,13 +179,18 @@ ApiSValue api_wide16_materialize_lvalue(CfreeCg* g, ApiSValue* v, CfreeCgSym api_runtime_helper(CfreeCg* g, const char* name, CfreeCgTypeId ret, const CfreeCgTypeId* params, u32 nparams) { CfreeCgFuncParam ps[3]; + CfreeCgFuncResult result; CfreeCgFuncSig sig; CfreeCgDecl decl; if (nparams > 3) return CFREE_CG_SYM_NONE; memset(ps, 0, sizeof ps); for (u32 i = 0; i < nparams; ++i) ps[i].type = params[i]; memset(&sig, 0, sizeof sig); - sig.ret = ret; + /* Runtime helpers always return a single value. */ + memset(&result, 0, sizeof result); + result.type = ret; + sig.results = &result; + sig.nresults = 1; sig.params = ps; sig.nparams = nparams; sig.call_conv = CFREE_CG_CC_TARGET_C; diff --git a/src/emu/cpu.c b/src/emu/cpu.c @@ -150,12 +150,16 @@ CfreeCgTypeId emu_cpu_type(Compiler* c) { return emu_thread_type(c); } CfreeCgTypeId emu_block_fn_type(Compiler* c) { CfreeCgBuiltinTypes bi; CfreeCgFuncParam param; + CfreeCgFuncResult result; CfreeCgFuncSig sig; bi = cfree_cg_builtin_types((CfreeCompiler*)c); memset(&param, 0, sizeof(param)); param.type = emu_thread_type(c); memset(&sig, 0, sizeof(sig)); - sig.ret = bi.id[CFREE_CG_BUILTIN_I64]; + memset(&result, 0, sizeof(result)); + result.type = bi.id[CFREE_CG_BUILTIN_I64]; + sig.results = &result; + sig.nresults = 1; sig.params = &param; sig.nparams = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; diff --git a/test/api/abi_classify_test.c b/test/api/abi_classify_test.c @@ -44,10 +44,16 @@ static const ABIFuncInfo* classify_fn(CfreeCompiler* c, CfreeCgTypeId ret_ty, CfreeCgFuncParam param; CfreeCgFuncSig sig; CfreeCgTypeId fn; + CfreeCgFuncResult sig_result; memset(&param, 0, sizeof param); param.type = arg_ty; memset(&sig, 0, sizeof sig); - sig.ret = ret_ty; + if (ret_ty != cfree_cg_builtin_types(c).id[CFREE_CG_BUILTIN_VOID]) { + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = ret_ty; + sig.results = &sig_result; + sig.nresults = 1; + } sig.params = &param; sig.nparams = 1; fn = cfree_cg_type_func(c, sig); @@ -324,11 +330,17 @@ static const ABIFuncInfo* classify_fn_n(CfreeCompiler* c, CfreeCgTypeId ret_ty, CfreeCgFuncParam params[8]; CfreeCgFuncSig sig; CfreeCgTypeId fn; + CfreeCgFuncResult sig_result; if (nargs > 8) exit(2); memset(params, 0, sizeof params); for (u32 i = 0; i < nargs; ++i) params[i].type = arg_types[i]; memset(&sig, 0, sizeof sig); - sig.ret = ret_ty; + if (ret_ty != cfree_cg_builtin_types(c).id[CFREE_CG_BUILTIN_VOID]) { + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = ret_ty; + sig.results = &sig_result; + sig.nresults = 1; + } sig.params = params; sig.nparams = nargs; sig.abi_variadic = variadic ? true : false; diff --git a/test/api/cg_switch_test.c b/test/api/cg_switch_test.c @@ -98,7 +98,11 @@ static void build_switch_fn(CfreeCompiler* c, CfreeCgTypeId i32_ty, memset(&param_desc, 0, sizeof param_desc); param_desc.type = sh->selector_type; memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.params = &param_desc; sig.nparams = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; diff --git a/test/api/cg_type_test.c b/test/api/cg_type_test.c @@ -108,7 +108,11 @@ static void exercise_cg_handles(CfreeCompiler* c, CfreeCgTypeId i32_ty, memset(&param_desc, 0, sizeof(param_desc)); param_desc.type = i32_ty; memset(&sig, 0, sizeof(sig)); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.params = &param_desc; sig.nparams = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -184,7 +188,11 @@ static void exercise_cg_scalar_local(CfreeCompiler* c, CfreeCgTypeId i32_ty, } memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; snprintf(name_buf, sizeof name_buf, "cg_scalar_local_o%d", opt_level); @@ -250,7 +258,11 @@ static void exercise_cg_late_local_addr(CfreeCompiler* c, CfreeCgTypeId i32_ty, } memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; snprintf(name_buf, sizeof name_buf, "cg_late_local_addr_o%d", opt_level); @@ -393,7 +405,11 @@ static CfreeCgSym begin_i32_func(CfreeCompiler* c, CfreeCg* cg, CfreeCgFuncSig sig; CfreeCgDecl decl; memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; memset(&decl, 0, sizeof decl); @@ -505,7 +521,11 @@ static uint32_t cg_emit_delayed_chain(CfreeCompiler* c, CfreeCgTypeId i32_ty, memset(&param_desc, 0, sizeof param_desc); param_desc.type = i32_ty; memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.params = &param_desc; sig.nparams = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -574,7 +594,11 @@ static uint32_t cg_emit_unary_chain(CfreeCompiler* c, CfreeCgTypeId i32_ty, memset(&param_desc, 0, sizeof param_desc); param_desc.type = i32_ty; memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.params = &param_desc; sig.nparams = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -638,7 +662,11 @@ static uint32_t cg_emit_local_shadow(CfreeCompiler* c, CfreeCgTypeId i32_ty, } memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; memset(&decl, 0, sizeof decl); @@ -706,7 +734,11 @@ static uint32_t cg_emit_delayed_cmp(CfreeCompiler* c, CfreeCgTypeId i32_ty, memset(&param_desc, 0, sizeof param_desc); param_desc.type = i32_ty; memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.params = &param_desc; sig.nparams = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -778,7 +810,11 @@ static uint32_t cg_emit_delayed_store(CfreeCompiler* c, CfreeCgTypeId i32_ty, memset(&param_desc, 0, sizeof param_desc); param_desc.type = i32_ty; memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.params = &param_desc; sig.nparams = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -855,7 +891,11 @@ static uint32_t cg_emit_delayed_pressure(CfreeCompiler* c, CfreeCgTypeId i32_ty, memset(param_desc, 0, sizeof param_desc); for (uint32_t i = 0; i < NPARAMS; ++i) param_desc[i].type = i32_ty; memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.params = param_desc; sig.nparams = NPARAMS; sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -941,7 +981,11 @@ static uint32_t cg_emit_local_shadow_boundary(CfreeCompiler* c, } memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; memset(&decl, 0, sizeof decl); @@ -1043,7 +1087,11 @@ static uint32_t cg_emit_local_shadow_partial_store(CfreeCompiler* c, } memset(&sig, 0, sizeof sig); - sig.ret = i32_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; memset(&decl, 0, sizeof decl); @@ -1180,7 +1228,6 @@ static CfreeCg* cg_begin_bad_store_func(CfreeCompiler* c, const char* name) { } memset(&sig, 0, sizeof sig); - sig.ret = cfree_cg_builtin_types(c).id[CFREE_CG_BUILTIN_VOID]; sig.call_conv = CFREE_CG_CC_TARGET_C; memset(&decl, 0, sizeof decl); decl.kind = CFREE_CG_DECL_FUNC; @@ -1457,7 +1504,11 @@ int main(void) { params[0].type = i32_ty; params[1].type = ptr_i32; memset(&sig, 0, sizeof(sig)); - sig.ret = i64_ty; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i64_ty; + sig.results = &sig_result; + sig.nresults = 1; sig.params = params; sig.nparams = 2; sig.abi_variadic = 1; @@ -1466,7 +1517,8 @@ int main(void) { EXPECT(fn != CFREE_CG_TYPE_NONE, "function type failed"); EXPECT(cfree_cg_type_func(c, sig) == fn, "function type id should be interned"); - EXPECT(cfree_cg_type_func_ret(c, fn) == i64_ty, "function return mismatch"); + EXPECT(cfree_cg_type_func_result(c, fn, 0).type == i64_ty, + "function return mismatch"); EXPECT(cfree_cg_type_func_nparams(c, fn) == 2, "function param count mismatch"); EXPECT(cfree_cg_type_func_param(c, fn, 1).type == ptr_i32, diff --git a/test/arch/inline_public_test.h b/test/arch/inline_public_test.h @@ -59,7 +59,6 @@ static inline CfreeStatus it_emit_func(CfreeCompiler* c, void* user) { bi = cfree_cg_builtin_types(c); memset(&sig, 0, sizeof sig); - sig.ret = bi.id[CFREE_CG_BUILTIN_VOID]; sig.call_conv = CFREE_CG_CC_TARGET_C; memset(&decl, 0, sizeof decl); @@ -74,7 +73,7 @@ static inline CfreeStatus it_emit_func(CfreeCompiler* c, void* user) { cfree_cg_func_begin(cg, sym); emit->body(c, cg, bi.id[CFREE_CG_BUILTIN_I64]); - cfree_cg_ret_void(cg); + cfree_cg_ret(cg); cfree_cg_func_end(cg); if (cfree_cg_end_obj(cg) != CFREE_OK) return CFREE_ERR; cfree_cg_free(cg); diff --git a/test/cg/ir_recorder_test.c b/test/cg/ir_recorder_test.c @@ -52,7 +52,7 @@ static Operand op_local(CGLocal local, CfreeCgTypeId type) { return o; } -static Operand op_imm(i64 value, CfreeCgTypeId type) { +static __attribute__((unused)) Operand op_imm(i64 value, CfreeCgTypeId type) { Operand o; memset(&o, 0, sizeof o); o.kind = OPK_IMM; @@ -83,9 +83,13 @@ static CGLocal local_new(CgTarget* t, CfreeCgTypeId type, const char* name) { static CGFuncDesc fn_desc(TestCtx* tc) { CGFuncDesc fd; CfreeCgFuncSig sig; + CfreeCgFuncResult sig_result; memset(&fd, 0, sizeof fd); memset(&sig, 0, sizeof sig); - sig.ret = tc->i32; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = tc->i32; + sig.results = &sig_result; + sig.nresults = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; fd.fn_type = cfree_cg_type_func(tc->c, sig); fd.loc.line = 3; diff --git a/test/cg/native_direct_target_test.c b/test/cg/native_direct_target_test.c @@ -358,9 +358,13 @@ static int event_index(const MockNative* m, MockEventKind kind, u32 from) { static CGFuncDesc fn_desc(TestCtx* tc) { CGFuncDesc fd; CfreeCgFuncSig sig; + CfreeCgFuncResult sig_result; memset(&fd, 0, sizeof fd); memset(&sig, 0, sizeof sig); - sig.ret = tc->i32; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = tc->i32; + sig.results = &sig_result; + sig.nresults = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; fd.fn_type = cfree_cg_type_func(tc->c, sig); fd.nresults = 1; diff --git a/test/opt/tiny_inline_test.c b/test/opt/tiny_inline_test.c @@ -57,7 +57,11 @@ static void tc_init(TestCtx* tc) { memset(&sig, 0, sizeof sig); memset(params, 0, sizeof params); params[0].type = tc->i32; - sig.ret = tc->i32; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = tc->i32; + sig.results = &sig_result; + sig.nresults = 1; sig.params = params; sig.nparams = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; @@ -150,7 +154,11 @@ static CgIrFunc* build_caller(TestCtx* tc, ObjSymId callee_sym, memset(&fd, 0, sizeof fd); fd.sym = callee_sym + 1u; memset(&sig, 0, sizeof sig); - sig.ret = tc->i32; + CfreeCgFuncResult sig_result; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = tc->i32; + sig.results = &sig_result; + sig.nresults = 1; sig.call_conv = CFREE_CG_CC_TARGET_C; fd.fn_type = cfree_cg_type_func((CfreeCompiler*)tc->c, sig); t->func_begin(t, &fd);