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:
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(¶m, 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 = ¶m;
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(¶m, 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 = ¶m;
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(¶m_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 = ¶m_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(¶m_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 = ¶m_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(¶m_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 = ¶m_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(¶m_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 = ¶m_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(¶m_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 = ¶m_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(¶m_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 = ¶m_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);