commit df65c98be9835c3b071228f110f27524af18a964
parent 14ba583ce257058b9eeccf580e289a3487c8f371
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 16 May 2026 09:45:35 -0700
Add Toy function pointer calls
Diffstat:
4 files changed, 216 insertions(+), 80 deletions(-)
diff --git a/lang/toy/toy.c b/lang/toy/toy.c
@@ -529,6 +529,67 @@ static CfreeCgSym toy_find_decl_sym(ToyParser* p, CfreeSym name) {
* ============================================================ */
static CfreeCgTypeId toy_parse_type(ToyParser* p) {
+ if (toy_parser_match(p, TOK_FN)) {
+ CfreeCgTypeId param_types[TOY_MAX_PARAMS];
+ CfreeCgFuncParam sig_params[TOY_MAX_PARAMS];
+ CfreeCgFuncSig sig;
+ CfreeCgTypeId ret_type;
+ size_t nparams = 0;
+ int variadic = 0;
+ size_t i;
+
+ if (!toy_parser_expect(p, TOK_LPAREN)) {
+ toy_error(p, p->cur.loc, "expected '(' after function type");
+ return CFREE_CG_TYPE_NONE;
+ }
+ if (p->cur.kind != TOK_RPAREN) {
+ for (;;) {
+ if (p->cur.kind == TOK_DOTDOTDOT) {
+ variadic = 1;
+ toy_parser_advance(p);
+ break;
+ }
+ if (nparams >= TOY_MAX_PARAMS) {
+ toy_error(p, p->cur.loc, "too many function type parameters");
+ return CFREE_CG_TYPE_NONE;
+ }
+ param_types[nparams] = toy_parse_type(p);
+ if (param_types[nparams] == CFREE_CG_TYPE_NONE)
+ return CFREE_CG_TYPE_NONE;
+ nparams++;
+ if (p->cur.kind == TOK_COMMA) {
+ toy_parser_advance(p);
+ if (p->cur.kind == TOK_DOTDOTDOT) {
+ variadic = 1;
+ toy_parser_advance(p);
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ if (!toy_parser_expect(p, TOK_RPAREN)) {
+ toy_error(p, p->cur.loc, "expected ')' after function type parameters");
+ return CFREE_CG_TYPE_NONE;
+ }
+ if (!toy_parser_expect(p, TOK_COLON)) {
+ toy_error(p, p->cur.loc, "expected ':' before function return type");
+ return CFREE_CG_TYPE_NONE;
+ }
+ ret_type = toy_parse_type(p);
+ if (ret_type == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE;
+
+ memset(sig_params, 0, sizeof sig_params);
+ for (i = 0; i < nparams; i++) sig_params[i].type = param_types[i];
+ memset(&sig, 0, sizeof sig);
+ sig.ret = ret_type;
+ sig.params = sig_params;
+ sig.nparams = (uint32_t)nparams;
+ sig.call_conv = CFREE_CG_CC_TARGET_C;
+ sig.abi_variadic = variadic;
+ return cfree_cg_type_func(p->c, sig);
+ }
if (toy_parser_match(p, TOK_LBRACKET)) {
uint64_t count;
CfreeCgTypeId elem;
@@ -614,6 +675,84 @@ static CfreeCgTypeId toy_parse_type(ToyParser* p) {
* ============================================================ */
static CfreeCgTypeId toy_parse_expr(ToyParser* p);
+static CfreeCgMemAccess toy_mem_access(ToyParser* p, CfreeCgTypeId type);
+
+static CfreeCgTypeId toy_ptr_pointee_func_type(ToyParser* p,
+ CfreeCgTypeId ptr_ty) {
+ CfreeCgTypeId fn_ty;
+ if (cfree_cg_type_kind(p->c, ptr_ty) != CFREE_CG_TYPE_PTR)
+ return CFREE_CG_TYPE_NONE;
+ fn_ty = cfree_cg_type_ptr_pointee(p->c, ptr_ty);
+ if (cfree_cg_type_kind(p->c, fn_ty) != CFREE_CG_TYPE_FUNC)
+ return CFREE_CG_TYPE_NONE;
+ return fn_ty;
+}
+
+static CfreeCgTypeId toy_push_named_rvalue(ToyParser* p, CfreeSym name) {
+ ToyVar* v = toy_find_var(p, name);
+ if (v) {
+ toy_push_var_lvalue(p, v);
+ cfree_cg_load(p->cg, toy_mem_access(p, v->type));
+ return v->type;
+ }
+ {
+ ToyGlobal* g = toy_find_global(p, name);
+ if (g) {
+ cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0);
+ cfree_cg_load(p->cg, toy_mem_access(p, g->type));
+ return g->type;
+ }
+ }
+ {
+ ToyFn* fn = toy_find_fn(p, name);
+ if (fn) {
+ cfree_cg_push_symbol_addr(p->cg, fn->sym, 0);
+ return cfree_cg_type_ptr(p->c, fn->type, 0);
+ }
+ }
+ return CFREE_CG_TYPE_NONE;
+}
+
+static int toy_parse_call_args(ToyParser* p, ToyToken call_tok,
+ CfreeCgTypeId fn_ty, size_t* out_nargs) {
+ uint32_t nparams = cfree_cg_type_func_nparams(p->c, fn_ty);
+ int variadic = cfree_cg_type_func_is_variadic(p->c, fn_ty);
+ size_t nargs = 0;
+ if (p->cur.kind != TOK_RPAREN) {
+ for (;;) {
+ CfreeCgTypeId arg_ty;
+ if (nargs >= TOY_MAX_PARAMS) {
+ toy_error(p, p->cur.loc, "too many arguments");
+ return 0;
+ }
+ arg_ty = toy_parse_expr(p);
+ if (arg_ty == CFREE_CG_TYPE_NONE) return 0;
+ if (nargs < nparams) {
+ CfreeCgFuncParam param =
+ cfree_cg_type_func_param(p->c, fn_ty, (uint32_t)nargs);
+ if (arg_ty != param.type) {
+ toy_error(p, call_tok.loc, "function argument type mismatch");
+ return 0;
+ }
+ } else if (!variadic) {
+ toy_error(p, call_tok.loc, "too many arguments");
+ return 0;
+ }
+ nargs++;
+ if (!toy_parser_match(p, TOK_COMMA)) break;
+ }
+ }
+ if (!toy_parser_expect(p, TOK_RPAREN)) {
+ toy_error(p, p->cur.loc, "expected ')' after arguments");
+ return 0;
+ }
+ if (nargs < nparams) {
+ toy_error(p, call_tok.loc, "too few arguments");
+ return 0;
+ }
+ *out_nargs = nargs;
+ return 1;
+}
static int toy_sym_is(ToyParser* p, CfreeSym sym, const char* name) {
return sym == cfree_sym_intern(p->c, name);
@@ -1752,67 +1891,36 @@ static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) {
if (recognized) return builtin_ty;
ToyFn* fn = toy_find_fn(p, name);
- if (!fn) {
- toy_error(p, ident_tok.loc, "undefined function '%s'",
- (const char*)ident_tok.text);
- return CFREE_CG_TYPE_NONE;
- }
toy_parser_advance(p); /* ( */
- CfreeCgTypeId arg_types[TOY_MAX_PARAMS];
size_t nargs = 0;
- if (p->cur.kind != TOK_RPAREN) {
- for (;;) {
- if (nargs >= TOY_MAX_PARAMS) {
- toy_error(p, p->cur.loc, "too many arguments");
- return CFREE_CG_TYPE_NONE;
- }
- CfreeCgTypeId arg_ty = toy_parse_expr(p);
- if (arg_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE;
- arg_types[nargs++] = arg_ty;
- if (p->cur.kind == TOK_COMMA) {
- toy_parser_advance(p);
- } else {
- break;
- }
- }
- }
- if (!toy_parser_expect(p, TOK_RPAREN)) {
- toy_error(p, p->cur.loc, "expected ')' after arguments");
- return CFREE_CG_TYPE_NONE;
+ if (fn) {
+ if (!toy_parse_call_args(p, ident_tok, fn->type, &nargs))
+ return CFREE_CG_TYPE_NONE;
+ cfree_cg_call_symbol_default(p->cg, fn->sym, (uint32_t)nargs);
+ return fn->ret;
}
- /* Verify argument count */
- if ((!fn->variadic && nargs != fn->nparams) ||
- (fn->variadic && nargs < fn->nparams)) {
- toy_error(p, ident_tok.loc,
- "function '%s' expects %zu arguments, got %zu",
- (const char*)ident_tok.text, fn->nparams, nargs);
+ CfreeCgTypeId callee_ty = toy_push_named_rvalue(p, name);
+ if (callee_ty == CFREE_CG_TYPE_NONE) {
+ toy_error(p, ident_tok.loc, "undefined function '%s'",
+ (const char*)ident_tok.text);
return CFREE_CG_TYPE_NONE;
}
- for (size_t i = 0; i < fn->nparams; ++i) {
- if (arg_types[i] != fn->params[i]) {
- toy_error(p, ident_tok.loc, "function argument type mismatch");
- return CFREE_CG_TYPE_NONE;
- }
+ CfreeCgTypeId fn_ty = toy_ptr_pointee_func_type(p, callee_ty);
+ if (fn_ty == CFREE_CG_TYPE_NONE) {
+ toy_error(p, ident_tok.loc, "callee is not a function pointer");
+ return CFREE_CG_TYPE_NONE;
}
-
- cfree_cg_call_symbol_default(p->cg, fn->sym, (uint32_t)nargs);
- return fn->ret;
+ if (!toy_parse_call_args(p, ident_tok, fn_ty, &nargs))
+ return CFREE_CG_TYPE_NONE;
+ cfree_cg_call_default(p->cg, (uint32_t)nargs, fn_ty);
+ return cfree_cg_type_func_ret(p->c, fn_ty);
}
- /* Variable reference */
- ToyVar* v = toy_find_var(p, name);
- if (v) {
- toy_push_var_lvalue(p, v);
- cfree_cg_load(p->cg, toy_mem_access(p, v->type));
- return v->type;
- }
- ToyGlobal* g = toy_find_global(p, name);
- if (g) {
- cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0);
- cfree_cg_load(p->cg, toy_mem_access(p, g->type));
- return g->type;
+ {
+ CfreeCgTypeId ty = toy_push_named_rvalue(p, name);
+ if (ty != CFREE_CG_TYPE_NONE) return ty;
}
toy_error(p, ident_tok.loc, "undefined variable '%s'",
(const char*)ident_tok.text);
@@ -1886,12 +1994,18 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) {
return cfree_cg_type_ptr(p->c, v->type, 0);
} else {
ToyGlobal* g = toy_find_global(p, name);
- if (!g) {
- toy_error(p, p->cur.loc, "undefined variable");
- return CFREE_CG_TYPE_NONE;
+ if (g) {
+ cfree_cg_push_symbol_addr(p->cg, g->sym, 0);
+ return cfree_cg_type_ptr(p->c, g->type, 0);
+ } else {
+ ToyFn* fn = toy_find_fn(p, name);
+ if (fn) {
+ cfree_cg_push_symbol_addr(p->cg, fn->sym, 0);
+ return cfree_cg_type_ptr(p->c, fn->type, 0);
+ }
}
- cfree_cg_push_symbol_addr(p->cg, g->sym, 0);
- return cfree_cg_type_ptr(p->c, g->type, 0);
+ toy_error(p, p->cur.loc, "undefined variable");
+ return CFREE_CG_TYPE_NONE;
}
}
@@ -2370,41 +2484,45 @@ static int toy_parse_return_stmt(ToyParser* p) {
if (toy_parser_match(p, TOK_TAIL)) {
CfreeSym name;
ToyFn* fn;
+ ToyToken call_tok;
+ CfreeCgTypeId fn_ty;
size_t nargs = 0;
if (p->cur.kind != TOK_IDENT) {
toy_error(p, p->cur.loc, "expected function name after tail");
return 0;
}
+ call_tok = p->cur;
name = toy_tok_sym(p, p->cur);
fn = toy_find_fn(p, name);
- if (!fn) {
- toy_error(p, p->cur.loc, "undefined function in tail call");
- return 0;
- }
- if (fn->variadic) {
- toy_error(p, p->cur.loc, "tail call to variadic function unsupported");
- return 0;
- }
toy_parser_advance(p);
if (!toy_parser_expect(p, TOK_LPAREN)) return 0;
- if (p->cur.kind != TOK_RPAREN) {
- for (;;) {
- CfreeCgTypeId arg_ty = toy_parse_expr(p);
- if (arg_ty == CFREE_CG_TYPE_NONE) return 0;
- if (nargs >= fn->nparams || arg_ty != fn->params[nargs]) {
- toy_error(p, p->cur.loc, "tail call argument mismatch");
- return 0;
- }
- nargs++;
- if (!toy_parser_match(p, TOK_COMMA)) break;
+ if (fn) {
+ fn_ty = fn->type;
+ } else {
+ CfreeCgTypeId callee_ty = toy_push_named_rvalue(p, name);
+ if (callee_ty == CFREE_CG_TYPE_NONE) {
+ toy_error(p, call_tok.loc, "undefined function in tail call");
+ return 0;
}
+ fn_ty = toy_ptr_pointee_func_type(p, callee_ty);
+ if (fn_ty == CFREE_CG_TYPE_NONE) {
+ toy_error(p, call_tok.loc, "tail callee is not a function pointer");
+ return 0;
+ }
+ }
+ if (cfree_cg_type_func_is_variadic(p->c, fn_ty)) {
+ toy_error(p, call_tok.loc, "tail call to variadic function unsupported");
+ return 0;
}
- if (!toy_parser_expect(p, TOK_RPAREN)) return 0;
- if (nargs != fn->nparams || fn->ret != p->cur_fn_ret) {
+ if (!toy_parse_call_args(p, call_tok, fn_ty, &nargs)) return 0;
+ if (cfree_cg_type_func_ret(p->c, fn_ty) != p->cur_fn_ret) {
toy_error(p, p->cur.loc, "tail call signature mismatch");
return 0;
}
- cfree_cg_tail_call_symbol(p->cg, fn->sym, (uint32_t)nargs);
+ if (fn)
+ cfree_cg_tail_call_symbol(p->cg, fn->sym, (uint32_t)nargs);
+ else
+ cfree_cg_tail_call(p->cg, (uint32_t)nargs, fn_ty);
if (!toy_parser_expect(p, TOK_SEMI)) return 0;
return 1;
}
diff --git a/src/api/cg.c b/src/api/cg.c
@@ -5262,7 +5262,9 @@ void cfree_cg_call(CfreeCg *g, uint32_t nargs, CfreeCgTypeId fn_type,
avs[idx].size = abi_cg_sizeof(g->c->abi, aty);
} else {
avs[idx].storage =
- api_is_lvalue_sv(&arg) ? api_force_reg(g, &arg, aty) : arg.op;
+ (api_is_lvalue_sv(&arg) || arg.op.kind == OPK_GLOBAL)
+ ? api_force_reg(g, &arg, aty)
+ : arg.op;
}
}
@@ -5298,6 +5300,8 @@ void cfree_cg_call(CfreeCg *g, uint32_t nargs, CfreeCgTypeId fn_type,
Reg r = api_alloc_reg_or_spill(g, api_type_class(ret_ty), ret_ty);
desc.ret.storage = api_op_reg(r, ret_ty);
}
+ } else {
+ desc.ret.storage = api_op_imm(0, builtin_id(CFREE_CG_BUILTIN_VOID));
}
T->call(T, &desc);
@@ -5356,7 +5360,9 @@ static void api_cg_tail_call(CfreeCg *g, uint32_t nargs,
avs[idx].type = aty;
avs[idx].abi = idx < abi->nparams ? &abi->params[idx] : NULL;
avs[idx].storage =
- api_is_lvalue_sv(&arg) ? api_force_reg(g, &arg, aty) : arg.op;
+ (api_is_lvalue_sv(&arg) || arg.op.kind == OPK_GLOBAL)
+ ? api_force_reg(g, &arg, aty)
+ : arg.op;
}
callee = api_pop(g);
api_ensure_reg(g, &callee);
@@ -5434,7 +5440,9 @@ static void api_call_symbol_common(CfreeCg *g, CfreeCgSym sym, uint32_t nargs,
avs[idx].size = abi_cg_sizeof(g->c->abi, aty);
} else {
avs[idx].storage =
- api_is_lvalue_sv(&arg) ? api_force_reg(g, &arg, aty) : arg.op;
+ (api_is_lvalue_sv(&arg) || arg.op.kind == OPK_GLOBAL)
+ ? api_force_reg(g, &arg, aty)
+ : arg.op;
}
}
callee_op = api_op_global((ObjSymId)sym, 0, cg_type_ptr_to(g->c, fty));
diff --git a/test/toy/cases/31_fn_pointer_call.expected b/test/toy/cases/31_fn_pointer_call.expected
@@ -0,0 +1 @@
+44
diff --git a/test/toy/cases/31_fn_pointer_call.toy b/test/toy/cases/31_fn_pointer_call.toy
@@ -0,0 +1,9 @@
+fn add2(x: int): int {
+ return x + 2;
+}
+
+fn main(): int {
+ let fp: *fn(int): int = add2;
+ let fp2: *fn(int): int = &add2;
+ return fp(20) + fp2(20);
+}