kit

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

commit f9d2f3384870b09083786f88b7ecdf5209f68794
parent 627d98f8eb8716464ededb979972b695f94a4579
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 22 May 2026 03:59:16 -0700

Add CG inline policy and Toy call attrs

Diffstat:
Minclude/cfree/cg.h | 17+++++++++++++++++
Mlang/c/decl/decl.c | 8++++++++
Mlang/c/decl/decl.h | 4++--
Mlang/c/parse/cg_adapter.c | 8+++++++-
Mlang/c/parse/cg_public_compat.h | 1+
Mlang/c/parse/parse.c | 6++++++
Mlang/toy/attrs.c | 15+++++++++++++++
Mlang/toy/builtins.c | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlang/toy/decls.c | 2+-
Mlang/toy/expr.c | 54+++++++++++++++++++++++++++++++++++++++++++++++++-----
Mlang/toy/internal.h | 3+++
Mlang/toy/parser.c | 22++++++++++++++++++++++
Mlang/toy/parser_core.c | 6++++++
Msrc/arch/arch.h | 2++
Msrc/cg/call.c | 18++++++++++++++++++
Msrc/cg/internal.h | 2++
Msrc/cg/session.c | 14+++++++++++++-
Msrc/opt/pass_inline.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++------
Mtest/opt/opt_test.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/toy/cases/136_call_builtin_attrs.expected | 1+
Atest/toy/cases/136_call_builtin_attrs.toy | 12++++++++++++
Atest/toy/cases/137_call_builtin_tail.expected | 1+
Atest/toy/cases/137_call_builtin_tail.toy | 9+++++++++
Atest/toy/cases/138_inline_attrs.expected | 1+
Atest/toy/cases/138_inline_attrs.toy | 15+++++++++++++++
Atest/toy/err/call_builtin_tail_direct_return.expected | 1+
Atest/toy/err/call_builtin_tail_direct_return.toy | 11+++++++++++
Atest/toy/err/call_builtin_tail_requires_return.expected | 1+
Atest/toy/err/call_builtin_tail_requires_return.toy | 8++++++++
29 files changed, 518 insertions(+), 16 deletions(-)

diff --git a/include/cfree/cg.h b/include/cfree/cg.h @@ -289,11 +289,19 @@ typedef enum CfreeCgFuncFlag { CFREE_CG_FUNC_NO_RED_ZONE = 1u << 6, } CfreeCgFuncFlag; +typedef enum CfreeCgInlinePolicy { + CFREE_CG_INLINE_DEFAULT, + CFREE_CG_INLINE_HINT, + CFREE_CG_INLINE_ALWAYS, + CFREE_CG_INLINE_NEVER, +} CfreeCgInlinePolicy; + typedef struct CfreeCgFuncAttrs { uint32_t flags; /* CfreeCgFuncFlag */ uint32_t stack_align; /* 0 = ABI default */ CfreeSym section; /* 0 = target default */ CfreeSym target_features; + CfreeCgInlinePolicy inline_policy; } CfreeCgFuncAttrs; typedef enum CfreeCgTlsModel { @@ -386,6 +394,8 @@ void cfree_cg_set_loc(CfreeCg*, CfreeSrcLoc); * ============================================================ */ void cfree_cg_func_begin(CfreeCg*, CfreeCgSym sym); +void cfree_cg_func_begin_attrs(CfreeCg*, CfreeCgSym sym, + CfreeCgFuncAttrs attrs); void cfree_cg_func_end(CfreeCg*); typedef enum CfreeCgLocalFlag { @@ -725,6 +735,7 @@ typedef enum CfreeCgCallFlag { typedef struct CfreeCgCallAttrs { CfreeCgTailPolicy tail; uint32_t flags; /* CfreeCgCallFlag */ + CfreeCgInlinePolicy inline_policy; } CfreeCgCallAttrs; /* cfree_cg_call pops a computed function pointer plus nargs arguments. @@ -993,6 +1004,7 @@ static inline void cfree_cg_call_default(CfreeCg* cg, uint32_t nargs, CfreeCgCallAttrs attrs; attrs.tail = CFREE_CG_TAIL_DEFAULT; attrs.flags = 0; + attrs.inline_policy = CFREE_CG_INLINE_DEFAULT; cfree_cg_call(cg, nargs, fn_type, attrs); } @@ -1001,6 +1013,7 @@ static inline void cfree_cg_call_symbol_default(CfreeCg* cg, CfreeCgSym sym, CfreeCgCallAttrs attrs; attrs.tail = CFREE_CG_TAIL_DEFAULT; attrs.flags = 0; + attrs.inline_policy = CFREE_CG_INLINE_DEFAULT; cfree_cg_call_symbol(cg, sym, nargs, attrs); } @@ -1009,6 +1022,7 @@ static inline void cfree_cg_tail_call(CfreeCg* cg, uint32_t nargs, CfreeCgCallAttrs attrs; attrs.tail = CFREE_CG_TAIL_ALLOWED; attrs.flags = 0; + attrs.inline_policy = CFREE_CG_INLINE_DEFAULT; cfree_cg_call(cg, nargs, fn_type, attrs); } @@ -1017,6 +1031,7 @@ static inline void cfree_cg_tail_call_symbol(CfreeCg* cg, CfreeCgSym sym, CfreeCgCallAttrs attrs; attrs.tail = CFREE_CG_TAIL_ALLOWED; attrs.flags = 0; + attrs.inline_policy = CFREE_CG_INLINE_DEFAULT; cfree_cg_call_symbol(cg, sym, nargs, attrs); } @@ -1025,6 +1040,7 @@ static inline void cfree_cg_musttail_call(CfreeCg* cg, uint32_t nargs, CfreeCgCallAttrs attrs; attrs.tail = CFREE_CG_TAIL_MUST; attrs.flags = 0; + attrs.inline_policy = CFREE_CG_INLINE_DEFAULT; cfree_cg_call(cg, nargs, fn_type, attrs); } @@ -1033,6 +1049,7 @@ static inline void cfree_cg_musttail_call_symbol(CfreeCg* cg, CfreeCgSym sym, CfreeCgCallAttrs attrs; attrs.tail = CFREE_CG_TAIL_MUST; attrs.flags = 0; + attrs.inline_policy = CFREE_CG_INLINE_DEFAULT; cfree_cg_call_symbol(cg, sym, nargs, attrs); } diff --git a/lang/c/decl/decl.c b/lang/c/decl/decl.c @@ -78,6 +78,13 @@ static CfreeCgSymbolAttrs decl_sym_attrs(const Decl* d) { return a; } +static CfreeCgInlinePolicy decl_inline_policy(const Decl* d) { + if (d->flags & DF_NOINLINE) return CFREE_CG_INLINE_NEVER; + if (d->flags & DF_ALWAYS_INLINE) return CFREE_CG_INLINE_ALWAYS; + if (d->flags & DF_INLINE) return CFREE_CG_INLINE_HINT; + return CFREE_CG_INLINE_DEFAULT; +} + DeclId decl_declare(DeclTable* t, const Decl* in) { DeclId id; Decl* slot; @@ -106,6 +113,7 @@ DeclId decl_declare(DeclTable* t, const Decl* in) { if (decl.kind == CFREE_CG_DECL_FUNC) { if (slot->flags & DF_NORETURN) decl.as.func.flags |= CFREE_CG_FUNC_NORETURN; + decl.as.func.inline_policy = decl_inline_policy(slot); decl.as.func.section = slot->section_id; } else { if (slot->flags & DF_THREAD) decl.as.object.flags |= CFREE_CG_OBJ_TLS; diff --git a/lang/c/decl/decl.h b/lang/c/decl/decl.h @@ -61,8 +61,8 @@ typedef enum DeclFlag { DF_WEAK = 1u << 4, DF_STATIC_LOCAL = 1u << 5, /* Phase 2 attribute-honoring flags. DF_NORETURN is the unified bit for - * _Noreturn and __attribute__((noreturn)); the inline-policy flags are - * recorded but not yet consulted (cfree has no inliner). */ + * _Noreturn and __attribute__((noreturn)); the inline-policy flags flow to + * CG/opt as function and direct-call inline policy. */ DF_NORETURN = 1u << 6, DF_ALWAYS_INLINE = 1u << 7, DF_NOINLINE = 1u << 8, diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -339,7 +339,13 @@ void pcg_param(Parser* p, const CGParamDesc* pd) { } void pcg_func_begin(Parser* p, const CGFuncDesc* fd) { - if (pcg_emit_enabled(p)) cfree_cg_func_begin(p->cg, fd->sym); + if (pcg_emit_enabled(p)) { + CfreeCgFuncAttrs attrs; + memset(&attrs, 0, sizeof attrs); + attrs.inline_policy = fd->inline_policy; + if (fd->flags & CGFD_NORETURN) attrs.flags |= CFREE_CG_FUNC_NORETURN; + cfree_cg_func_begin_attrs(p->cg, fd->sym, attrs); + } } void pcg_push_int(Parser* p, i64 v, const Type* ty) { diff --git a/lang/c/parse/cg_public_compat.h b/lang/c/parse/cg_public_compat.h @@ -163,6 +163,7 @@ typedef struct CGFuncDesc { u32 nparams; SrcLoc loc; u32 flags; + CfreeCgInlinePolicy inline_policy; } CGFuncDesc; typedef struct Parser Parser; diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -1130,6 +1130,12 @@ static void parse_function_body(Parser* p, ObjSymId fsym, const Type* fn_ty, fd.nparams = nparams; fd.loc = fname_loc; if (decl_flags & DF_NORETURN) fd.flags |= CGFD_NORETURN; + if (decl_flags & DF_NOINLINE) + fd.inline_policy = CFREE_CG_INLINE_NEVER; + else if (decl_flags & DF_ALWAYS_INLINE) + fd.inline_policy = CFREE_CG_INLINE_ALWAYS; + else if (decl_flags & DF_INLINE) + fd.inline_policy = CFREE_CG_INLINE_HINT; if (nparams) { pds = (CGParamDesc*)arena_array(p->pool->arena, CGParamDesc, nparams); diff --git a/lang/toy/attrs.c b/lang/toy/attrs.c @@ -164,6 +164,21 @@ static int toy_parse_attr_one(ToyParser* p, ToyAttrSet* attrs) { attrs->func.flags |= CFREE_CG_FUNC_NO_RED_ZONE; return 1; } + if (toy_sym_is(p, name, "inline")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + attrs->func.inline_policy = CFREE_CG_INLINE_HINT; + return 1; + } + if (toy_sym_is(p, name, "always_inline")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + attrs->func.inline_policy = CFREE_CG_INLINE_ALWAYS; + return 1; + } + if (toy_sym_is(p, name, "noinline")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + attrs->func.inline_policy = CFREE_CG_INLINE_NEVER; + return 1; + } if (toy_sym_is(p, name, "stack_align")) { attrs->attr_kinds |= TOY_ATTR_FUNC; if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; diff --git a/lang/toy/builtins.c b/lang/toy/builtins.c @@ -218,6 +218,176 @@ static int toy_parse_cmpxchg_strength(ToyParser* p, int* weak_out) { return 1; } +static int toy_parse_call_attr(ToyParser* p, CfreeCgCallAttrs* attrs) { + CfreeSym name; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (toy_sym_is(p, name, "cold")) { + attrs->flags |= CFREE_CG_CALL_COLD; + } else if (toy_sym_is(p, name, "tail") || + toy_sym_is(p, name, "tail_allowed")) { + attrs->tail = CFREE_CG_TAIL_ALLOWED; + } else if (toy_sym_is(p, name, "musttail")) { + attrs->tail = CFREE_CG_TAIL_MUST; + } else if (toy_sym_is(p, name, "notail") || + toy_sym_is(p, name, "tail_never")) { + attrs->tail = CFREE_CG_TAIL_NEVER; + } else if (toy_sym_is(p, name, "inline")) { + attrs->inline_policy = CFREE_CG_INLINE_HINT; + } else if (toy_sym_is(p, name, "always_inline")) { + attrs->inline_policy = CFREE_CG_INLINE_ALWAYS; + } else if (toy_sym_is(p, name, "noinline")) { + attrs->inline_policy = CFREE_CG_INLINE_NEVER; + } else { + toy_error(p, p->cur.loc, "unknown call attribute"); + return 0; + } + return 1; +} + +static int toy_parse_call_attr_tail(ToyParser* p, CfreeCgCallAttrs* attrs) { + while (toy_parser_match(p, TOK_COMMA)) { + if (p->cur.kind != TOK_DOT) { + toy_error(p, p->cur.loc, "expected call attribute"); + return 0; + } + if (!toy_parse_call_attr(p, attrs)) return 0; + } + return 1; +} + +static int toy_reject_tail_call_operand(ToyParser* p) { + if (!p->tail_call_expr) return 0; + toy_error(p, p->cur.loc, "tail @call must be returned directly"); + return 1; +} + +static int toy_parse_call_braced_args(ToyParser* p, ToyToken call_tok, + CfreeCgTypeId fn_ty, + const ToyTypeId* toy_params, + size_t toy_nparams, + 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 (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected braced call arguments"); + return 0; + } + if (p->cur.kind != TOK_RBRACE) { + for (;;) { + CfreeCgTypeId arg_ty = toy_parse_expr(p); + if (arg_ty == CFREE_CG_TYPE_NONE) return 0; + if (toy_reject_tail_call_operand(p)) return 0; + if (nargs < nparams) { + CfreeCgFuncParam param = + cfree_cg_type_func_param(p->c, fn_ty, (uint32_t)nargs); + ToyTypeId expected = + (toy_params && nargs < toy_nparams) ? toy_params[nargs] + : TOY_TYPE_NONE; + ToyTypeId actual = p->last_type; + if (expected != TOY_TYPE_NONE && + !toy_type_accepts_type(p, expected, actual)) { + toy_error(p, call_tok.loc, "function argument type mismatch"); + return 0; + } + if (expected == TOY_TYPE_NONE && 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 (p->cur.kind == TOK_RBRACE) break; + } + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after call arguments"); + return 0; + } + if (nargs < nparams) { + toy_error(p, call_tok.loc, "too few arguments"); + return 0; + } + *out_nargs = nargs; + return 1; +} + +static CfreeCgTypeId toy_parse_call_builtin(ToyParser* p) { + CfreeCgCallAttrs attrs; + ToyFn* fn = NULL; + CfreeCgTypeId fn_ty = CFREE_CG_TYPE_NONE; + CfreeCgTypeId ret_ty; + const ToyType* source_fn_type = NULL; + ToyTypeId ret_toy_type = TOY_TYPE_NONE; + ToyToken call_tok; + int direct = 0; + size_t nargs = 0; + int tail; + memset(&attrs, 0, sizeof attrs); + if (!toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + call_tok = p->cur; + if (p->cur.kind == TOK_IDENT) { + CfreeSym callee_name = toy_tok_sym(p, p->cur); + fn = toy_find_fn(p, callee_name); + if (fn) { + direct = 1; + fn_ty = fn->type; + ret_toy_type = fn->toy_ret; + toy_parser_advance(p); + } + } + if (!direct) { + CfreeCgTypeId callee_ty = toy_parse_expr(p); + ToyTypeId callee_toy_type = p->last_type; + if (callee_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_reject_tail_call_operand(p)) return CFREE_CG_TYPE_NONE; + fn_ty = toy_ptr_pointee_func_type(p, callee_ty); + if (fn_ty == CFREE_CG_TYPE_NONE) { + toy_error(p, call_tok.loc, "@call callee is not a function pointer"); + return CFREE_CG_TYPE_NONE; + } + source_fn_type = toy_type_get(p, toy_type_pointee(p, callee_toy_type)); + if (source_fn_type && source_fn_type->kind != TOY_TYPE_FUNC) + source_fn_type = NULL; + if (source_fn_type) ret_toy_type = source_fn_type->ret; + } + if (!toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + if (!toy_parse_call_braced_args( + p, call_tok, fn_ty, + direct ? fn->toy_params + : (source_fn_type ? source_fn_type->params : NULL), + direct ? fn->nparams + : (source_fn_type ? source_fn_type->nparams : 0), + &nargs)) { + return CFREE_CG_TYPE_NONE; + } + 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); + tail = attrs.tail == CFREE_CG_TAIL_ALLOWED || + attrs.tail == CFREE_CG_TAIL_MUST; + if (tail && !p->allow_tail_call_expr) { + toy_error(p, call_tok.loc, "tail @call requires return"); + return CFREE_CG_TYPE_NONE; + } + + if (direct) + cfree_cg_call_symbol(p->cg, fn->sym, (uint32_t)nargs, attrs); + else + cfree_cg_call(p->cg, (uint32_t)nargs, fn_ty, attrs); + if (!ret_toy_type) ret_toy_type = toy_type_from_cg(p, ret_ty); + if (tail) { + p->tail_call_expr = 1; + p->tail_call_ret_toy = ret_toy_type; + } + p->last_type = ret_toy_type; + return ret_ty; +} + static int toy_parse_barrier_scope(ToyParser* p, CfreeCgBarrierScope* out) { CfreeSym name; if (!toy_parse_attr_dot_name(p, &name)) return 0; @@ -246,6 +416,8 @@ CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name, int* recognized) { *recognized = 1; + if (toy_sym_is(p, name, "call")) return toy_parse_call_builtin(p); + if (toy_sym_is(p, name, "popcount") || toy_sym_is(p, name, "ctz") || toy_sym_is(p, name, "clz") || toy_sym_is(p, name, "bswap")) { CfreeCgIntrinsic intrin = CFREE_CG_INTRIN_POPCOUNT; diff --git a/lang/toy/decls.c b/lang/toy/decls.c @@ -488,7 +488,7 @@ int toy_parse_fn(ToyParser* p, int is_extern, int is_pub) { return 1; } - cfree_cg_func_begin(p->cg, fn_entry->sym); + cfree_cg_func_begin_attrs(p->cg, fn_entry->sym, fn_entry->func_attrs); p->nvars = 0; p->nlabels = 0; diff --git a/lang/toy/expr.c b/lang/toy/expr.c @@ -31,6 +31,12 @@ static void toy_note_builtin_result_type(ToyParser* p, CfreeCgTypeId ty) { if (last_cg != ty) toy_note_cg_result_type(p, ty); } +static int toy_reject_tail_call_operand(ToyParser* p) { + if (!p->tail_call_expr) return 0; + toy_error(p, p->cur.loc, "tail @call must be returned directly"); + return 1; +} + CfreeCgTypeId toy_push_named_rvalue(ToyParser* p, CfreeSym name) { ToyVar* v = toy_find_var(p, name); if (v) { @@ -110,6 +116,7 @@ int toy_parse_call_args(ToyParser* p, ToyToken call_tok, arg_ty = toy_parse_expr(p); } if (arg_ty == CFREE_CG_TYPE_NONE) return 0; + if (toy_reject_tail_call_operand(p)) return 0; if (nargs < nparams) { CfreeCgFuncParam param = cfree_cg_type_func_param(p->c, fn_ty, (uint32_t)nargs); @@ -679,7 +686,12 @@ static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { if (!toy_parse_call_args(p, ident_tok, fn->type, fn->toy_params, fn->nparams, &nargs)) return CFREE_CG_TYPE_NONE; - cfree_cg_call_symbol_default(p->cg, fn->sym, (uint32_t)nargs); + { + CfreeCgCallAttrs attrs; + memset(&attrs, 0, sizeof attrs); + attrs.inline_policy = fn->func_attrs.inline_policy; + cfree_cg_call_symbol(p->cg, fn->sym, (uint32_t)nargs, attrs); + } p->last_type = fn->toy_ret; return fn->ret; } @@ -777,6 +789,7 @@ static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { static CfreeCgTypeId toy_parse_expr_postfix(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_primary(p); ToyTypeId toy_ty = p->last_type; + if (p->tail_call_expr) return ty; while (ty != CFREE_CG_TYPE_NONE) { if (toy_parser_match(p, TOK_DOTSTAR)) { if (cfree_cg_type_kind(p->c, ty) != CFREE_CG_TYPE_PTR) { @@ -958,6 +971,10 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { if (toy_parser_match(p, TOK_MINUS)) { CfreeCgTypeId ty = toy_parse_expr_unary(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (p->tail_call_expr) { + toy_error(p, p->cur.loc, "tail @call must be returned directly"); + return CFREE_CG_TYPE_NONE; + } if (toy_type_is_float(p, ty)) { cfree_cg_fp_unop(p->cg, CFREE_CG_FP_NEG, 0); return ty; @@ -973,6 +990,10 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { if (toy_parser_match(p, TOK_BANG)) { CfreeCgTypeId ty = toy_parse_expr_unary(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (p->tail_call_expr) { + toy_error(p, p->cur.loc, "tail @call must be returned directly"); + return CFREE_CG_TYPE_NONE; + } if (!toy_type_is_intlike(p, ty)) { toy_error(p, p->cur.loc, "invalid operand for '!'"); return CFREE_CG_TYPE_NONE; @@ -984,6 +1005,10 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { if (toy_parser_match(p, TOK_TILDE)) { CfreeCgTypeId ty = toy_parse_expr_unary(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (p->tail_call_expr) { + toy_error(p, p->cur.loc, "tail @call must be returned directly"); + return CFREE_CG_TYPE_NONE; + } if (!toy_type_is_intlike(p, ty)) { toy_error(p, p->cur.loc, "invalid operand for '~'"); return CFREE_CG_TYPE_NONE; @@ -1197,7 +1222,8 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { static CfreeCgTypeId toy_parse_expr_cast(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_unary(p); - while (ty != CFREE_CG_TYPE_NONE && toy_parser_match(p, TOK_AS)) { + while (ty != CFREE_CG_TYPE_NONE && !p->tail_call_expr && + toy_parser_match(p, TOK_AS)) { CfreeCgTypeId dst = toy_parse_type(p); if (dst == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; if (!toy_emit_cast(p, ty, dst)) return CFREE_CG_TYPE_NONE; @@ -1211,6 +1237,7 @@ static CfreeCgTypeId toy_parse_expr_mul(ToyParser* p) { if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; while (p->cur.kind == TOK_STAR || p->cur.kind == TOK_SLASH || p->cur.kind == TOK_PERCENT) { + if (p->tail_call_expr) break; ToyTokenKind op = p->cur.kind; CfreeCgIntBinOp binop; if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_ARITH)) @@ -1218,6 +1245,7 @@ static CfreeCgTypeId toy_parse_expr_mul(ToyParser* p) { toy_parser_advance(p); CfreeCgTypeId ty2 = toy_parse_expr_cast(p); if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_reject_tail_call_operand(p)) return CFREE_CG_TYPE_NONE; if (ty != ty2 || (!toy_type_is_intlike(p, ty) && !toy_type_is_float(p, ty))) { toy_error(p, p->cur.loc, "arithmetic operands must have same numeric type"); return CFREE_CG_TYPE_NONE; @@ -1266,6 +1294,7 @@ static CfreeCgTypeId toy_parse_expr_add(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_mul(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; while (p->cur.kind == TOK_PLUS || p->cur.kind == TOK_MINUS) { + if (p->tail_call_expr) break; ToyTokenKind op = p->cur.kind; CfreeCgIntBinOp binop = (op == TOK_PLUS) ? CFREE_CG_INT_ADD : CFREE_CG_INT_SUB; @@ -1274,6 +1303,7 @@ static CfreeCgTypeId toy_parse_expr_add(ToyParser* p) { toy_parser_advance(p); CfreeCgTypeId ty2 = toy_parse_expr_mul(p); if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_reject_tail_call_operand(p)) return CFREE_CG_TYPE_NONE; if (ty != ty2 || (!toy_type_is_intlike(p, ty) && !toy_type_is_float(p, ty))) { toy_error(p, p->cur.loc, "arithmetic operands must have same numeric type"); return CFREE_CG_TYPE_NONE; @@ -1292,9 +1322,10 @@ static CfreeCgTypeId toy_parse_expr_add(ToyParser* p) { static CfreeCgTypeId toy_parse_expr_cmp(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_add(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (p->cur.kind == TOK_EQEQ || p->cur.kind == TOK_NE || - p->cur.kind == TOK_LT || p->cur.kind == TOK_GT || - p->cur.kind == TOK_LE || p->cur.kind == TOK_GE) { + if (!p->tail_call_expr && + (p->cur.kind == TOK_EQEQ || p->cur.kind == TOK_NE || + p->cur.kind == TOK_LT || p->cur.kind == TOK_GT || + p->cur.kind == TOK_LE || p->cur.kind == TOK_GE)) { ToyTokenKind op = p->cur.kind; CfreeCgIntCmpOp cmp; if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_CMP)) @@ -1302,6 +1333,7 @@ static CfreeCgTypeId toy_parse_expr_cmp(ToyParser* p) { toy_parser_advance(p); CfreeCgTypeId ty2 = toy_parse_expr_add(p); if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_reject_tail_call_operand(p)) return CFREE_CG_TYPE_NONE; if (ty != ty2) { toy_error(p, p->cur.loc, "comparison operands must have same type"); return CFREE_CG_TYPE_NONE; @@ -1370,6 +1402,7 @@ static CfreeCgTypeId toy_parse_expr_shift(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_cmp(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; while (p->cur.kind == TOK_SHL || p->cur.kind == TOK_SHR) { + if (p->tail_call_expr) break; ToyTokenKind op = p->cur.kind; CfreeCgIntBinOp binop = (op == TOK_SHL) ? CFREE_CG_INT_SHL : CFREE_CG_INT_ASHR; @@ -1378,6 +1411,7 @@ static CfreeCgTypeId toy_parse_expr_shift(ToyParser* p) { toy_parser_advance(p); CfreeCgTypeId ty2 = toy_parse_expr_cmp(p); if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_reject_tail_call_operand(p)) return CFREE_CG_TYPE_NONE; if (ty != p->int_type || ty2 != p->int_type) { toy_error(p, p->cur.loc, "shift operands must be int"); return CFREE_CG_TYPE_NONE; @@ -1391,11 +1425,13 @@ static CfreeCgTypeId toy_parse_expr_band(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_shift(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; while (p->cur.kind == TOK_AMPERSAND) { + if (p->tail_call_expr) break; if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_BIT_AND)) return CFREE_CG_TYPE_NONE; toy_parser_advance(p); CfreeCgTypeId ty2 = toy_parse_expr_shift(p); if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_reject_tail_call_operand(p)) return CFREE_CG_TYPE_NONE; if (ty != p->int_type || ty2 != p->int_type) { toy_error(p, p->cur.loc, "bitwise operands must be int"); return CFREE_CG_TYPE_NONE; @@ -1409,11 +1445,13 @@ static CfreeCgTypeId toy_parse_expr_bxor(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_band(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; while (p->cur.kind == TOK_CARET) { + if (p->tail_call_expr) break; if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_BIT_XOR)) return CFREE_CG_TYPE_NONE; toy_parser_advance(p); CfreeCgTypeId ty2 = toy_parse_expr_band(p); if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_reject_tail_call_operand(p)) return CFREE_CG_TYPE_NONE; if (ty != p->int_type || ty2 != p->int_type) { toy_error(p, p->cur.loc, "bitwise operands must be int"); return CFREE_CG_TYPE_NONE; @@ -1427,11 +1465,13 @@ static CfreeCgTypeId toy_parse_expr_bor(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_bxor(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; while (p->cur.kind == TOK_PIPE) { + if (p->tail_call_expr) break; if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_BIT_OR)) return CFREE_CG_TYPE_NONE; toy_parser_advance(p); CfreeCgTypeId ty2 = toy_parse_expr_bxor(p); if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_reject_tail_call_operand(p)) return CFREE_CG_TYPE_NONE; if (ty != p->int_type || ty2 != p->int_type) { toy_error(p, p->cur.loc, "bitwise operands must be int"); return CFREE_CG_TYPE_NONE; @@ -1445,6 +1485,7 @@ static CfreeCgTypeId toy_parse_expr_and(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_bor(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; while (p->cur.kind == TOK_AND) { + if (p->tail_call_expr) break; CfreeCgTypeId bool_ty = toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); CfreeCgLabel false_label; CfreeCgLabel end_label; @@ -1459,6 +1500,7 @@ static CfreeCgTypeId toy_parse_expr_and(ToyParser* p) { cfree_cg_branch_false(p->cg, false_label); ty = toy_parse_expr_bor(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_reject_tail_call_operand(p)) return CFREE_CG_TYPE_NONE; if (!toy_emit_truthy(p, ty)) return CFREE_CG_TYPE_NONE; cfree_cg_branch_false(p->cg, false_label); cfree_cg_push_local(p->cg, result_slot); @@ -1482,6 +1524,7 @@ static CfreeCgTypeId toy_parse_expr_or(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_and(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; while (p->cur.kind == TOK_OR) { + if (p->tail_call_expr) break; CfreeCgTypeId bool_ty = toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); CfreeCgLabel true_label; CfreeCgLabel end_label; @@ -1496,6 +1539,7 @@ static CfreeCgTypeId toy_parse_expr_or(ToyParser* p) { cfree_cg_branch_true(p->cg, true_label); ty = toy_parse_expr_and(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_reject_tail_call_operand(p)) return CFREE_CG_TYPE_NONE; if (!toy_emit_truthy(p, ty)) return CFREE_CG_TYPE_NONE; cfree_cg_branch_true(p->cg, true_label); cfree_cg_push_local(p->cg, result_slot); diff --git a/lang/toy/internal.h b/lang/toy/internal.h @@ -220,6 +220,9 @@ typedef struct ToyParser { uint32_t static_counter; uint32_t expr_island_mask; ToyTypeId last_type; + int allow_tail_call_expr; + int tail_call_expr; + ToyTypeId tail_call_ret_toy; CfreeFrontendInputKind input_kind; } ToyParser; diff --git a/lang/toy/parser.c b/lang/toy/parser.c @@ -1416,8 +1416,26 @@ static int toy_parse_return_stmt(ToyParser* p) { return 1; } } + p->allow_tail_call_expr = 1; + p->tail_call_expr = 0; + p->tail_call_ret_toy = TOY_TYPE_NONE; ty = toy_parse_expr(p); + p->allow_tail_call_expr = 0; if (ty == CFREE_CG_TYPE_NONE) return 0; + if (p->tail_call_expr) { + ToyTypeId tail_toy = p->tail_call_ret_toy; + if (!tail_toy) tail_toy = p->last_type; + if (!toy_check_source_value(p, p->cur_fn_ret, p->cur_fn_ret_toy, ty, + tail_toy, "return type mismatch")) + return 0; + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after return"); + return 0; + } + p->tail_call_expr = 0; + p->tail_call_ret_toy = TOY_TYPE_NONE; + return 1; + } if (!toy_check_source_value(p, p->cur_fn_ret, p->cur_fn_ret_toy, ty, p->last_type, "return type mismatch")) return 0; @@ -1433,6 +1451,10 @@ static int toy_parse_return_stmt(ToyParser* p) { static int toy_parse_expr_stmt(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr(p); if (ty == CFREE_CG_TYPE_NONE) return 0; + if (p->tail_call_expr) { + toy_error(p, p->cur.loc, "tail @call requires return"); + return 0; + } if (!toy_parser_expect(p, TOK_SEMI)) { toy_error(p, p->cur.loc, "expected ';' after expression"); return 0; diff --git a/lang/toy/parser_core.c b/lang/toy/parser_core.c @@ -88,6 +88,9 @@ void toy_parser_init(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, p->static_counter = 0; p->expr_island_mask = 0; p->last_type = TOY_TYPE_NONE; + p->allow_tail_call_expr = 0; + p->tail_call_expr = 0; + p->tail_call_ret_toy = TOY_TYPE_NONE; p->input_kind = CFREE_FRONTEND_INPUT_TRANSLATION_UNIT; } @@ -116,6 +119,9 @@ void toy_parser_reinit(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, p->has_error = 0; p->expr_island_mask = 0; p->last_type = TOY_TYPE_NONE; + p->allow_tail_call_expr = 0; + p->tail_call_expr = 0; + p->tail_call_ret_toy = TOY_TYPE_NONE; p->input_kind = input_kind; } diff --git a/src/arch/arch.h b/src/arch/arch.h @@ -374,6 +374,7 @@ typedef struct CGFuncDesc { u32 nparams; SrcLoc loc; u32 flags; /* CGFuncDescFlag */ + CfreeCgInlinePolicy inline_policy; } CGFuncDesc; typedef struct CGKnownFrameDesc { @@ -404,6 +405,7 @@ typedef struct CGCallDesc { u32 nargs; u16 flags; /* CGCallFlag */ u16 pad; + CfreeCgInlinePolicy inline_policy; CGABIValue ret; } CGCallDesc; diff --git a/src/cg/call.c b/src/cg/call.c @@ -151,6 +151,14 @@ void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type, Operand callee_op = (callee.op.kind == OPK_GLOBAL) ? callee.op : api_force_reg(g, &callee, fty); + CfreeCgInlinePolicy inline_policy = attrs.inline_policy; + if (inline_policy == CFREE_CG_INLINE_DEFAULT && + callee_op.kind == OPK_GLOBAL && callee_op.v.global.addend == 0) { + CfreeCgDecl callee_attrs = + api_sym_attrs(g, (CfreeCgSym)callee_op.v.global.sym); + if (callee_attrs.kind == CFREE_CG_DECL_FUNC) + inline_policy = callee_attrs.as.func.inline_policy; + } memset(&desc, 0, sizeof desc); desc.fn_type = fty; @@ -159,6 +167,7 @@ void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type, desc.args = avs; desc.nargs = nargs; desc.flags = tail ? CG_CALL_TAIL : CG_CALL_NONE; + desc.inline_policy = inline_policy; desc.ret.type = ret_ty; desc.ret.abi = &abi->ret; @@ -227,6 +236,7 @@ void api_cg_tail_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type) { desc.args = avs; desc.nargs = nargs; desc.flags = CG_CALL_TAIL; + desc.inline_policy = CFREE_CG_INLINE_DEFAULT; desc.ret.type = cg_type_func_ret_id(g->c, fty); desc.ret.abi = &abi->ret; desc.ret.storage = api_op_imm(0, builtin_id(CFREE_CG_BUILTIN_VOID)); @@ -250,6 +260,7 @@ void api_call_symbol_common(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, CGABIValue* avs; CGCallDesc desc; Operand callee_op; + CfreeCgInlinePolicy inline_policy; if (!g) return; api_local_const_memory_boundary(g); int tail = @@ -270,6 +281,12 @@ void api_call_symbol_common(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, api_pack_call_arg(g, &avs[idx], fty, abi, idx); } callee_op = api_op_global((ObjSymId)sym, 0, cg_type_ptr_to(g->c, fty)); + inline_policy = attrs.inline_policy; + if (inline_policy == CFREE_CG_INLINE_DEFAULT) { + CfreeCgDecl callee_attrs = api_sym_attrs(g, sym); + if (callee_attrs.kind == CFREE_CG_DECL_FUNC) + inline_policy = callee_attrs.as.func.inline_policy; + } memset(&desc, 0, sizeof desc); desc.fn_type = fty; desc.abi = abi; @@ -277,6 +294,7 @@ void api_call_symbol_common(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, desc.args = avs; desc.nargs = nargs; desc.flags = tail ? CG_CALL_TAIL : CG_CALL_NONE; + desc.inline_policy = inline_policy; desc.ret.type = ret_ty; desc.ret.abi = &abi->ret; if (has_result) { diff --git a/src/cg/internal.h b/src/cg/internal.h @@ -366,6 +366,8 @@ void cfree_cg_set_loc(CfreeCg* g, CfreeSrcLoc loc); CfreeCgSym cfree_cg_decl(CfreeCg* g, CfreeCgDecl decl); CfreeCgSym cfree_cg_alias(CfreeCg* g, CfreeCgAlias alias); void cfree_cg_func_begin(CfreeCg* g, CfreeCgSym cg_sym); +void cfree_cg_func_begin_attrs(CfreeCg* g, CfreeCgSym cg_sym, + CfreeCgFuncAttrs attrs); void cfree_cg_func_end(CfreeCg* g); SymBind api_map_bind(CfreeSymBind b); SymVis api_map_vis(CfreeCgVisibility v); diff --git a/src/cg/session.c b/src/cg/session.c @@ -250,7 +250,8 @@ CfreeCgSym cfree_cg_alias(CfreeCg* g, CfreeCgAlias alias) { return (CfreeCgSym)sym; } -void cfree_cg_func_begin(CfreeCg* g, CfreeCgSym cg_sym) { +void cfree_cg_func_begin_attrs(CfreeCg* g, CfreeCgSym cg_sym, + CfreeCgFuncAttrs begin_attrs) { Compiler* c; ObjBuilder* ob; CGTarget* T; @@ -286,6 +287,11 @@ void cfree_cg_func_begin(CfreeCg* g, CfreeCgSym cg_sym) { if (attrs.as.func.flags & CFREE_CG_FUNC_NORETURN) { g->fn_desc.flags |= CGFD_NORETURN; } + g->fn_desc.inline_policy = attrs.as.func.inline_policy; + if (begin_attrs.inline_policy != CFREE_CG_INLINE_DEFAULT) + g->fn_desc.inline_policy = begin_attrs.inline_policy; + if (begin_attrs.flags & CFREE_CG_FUNC_NORETURN) + g->fn_desc.flags |= CGFD_NORETURN; g->fn_ret_type = cg_type_func_ret_id(c, fty); g->fn_abi = abi; @@ -303,6 +309,12 @@ void cfree_cg_func_begin(CfreeCg* g, CfreeCgSym cg_sym) { api_regalloc_begin(g); } +void cfree_cg_func_begin(CfreeCg* g, CfreeCgSym cg_sym) { + CfreeCgFuncAttrs attrs; + memset(&attrs, 0, sizeof attrs); + cfree_cg_func_begin_attrs(g, cg_sym, attrs); +} + void cfree_cg_func_end(CfreeCg* g) { if (!g) return; api_regalloc_finish(g); diff --git a/src/opt/pass_inline.c b/src/opt/pass_inline.c @@ -32,7 +32,9 @@ typedef struct InlineOrderCtx { } InlineOrderCtx; #define INLINE_NORMAL_COST_LIMIT 20u +#define INLINE_HINT_COST_LIMIT 40u #define INLINE_ABS_GROWTH_LIMIT 64u +#define INLINE_HINT_ABS_GROWTH_LIMIT 128u static u32 func_inline_cost(Func* f) { u32 cost = 0; @@ -90,6 +92,29 @@ static Func* direct_callee(FuncSet* fs, const Inst* in) { return funcset_find(fs, aux->desc.callee.v.global.sym); } +static CfreeCgInlinePolicy call_inline_policy(const Inst* in) { + if (!in || (IROp)in->op != IR_CALL) return CFREE_CG_INLINE_DEFAULT; + IRCallAux* aux = (IRCallAux*)in->extra.aux; + return aux ? aux->desc.inline_policy : CFREE_CG_INLINE_DEFAULT; +} + +static CfreeCgInlinePolicy effective_inline_policy(const Inst* in, + const Func* callee) { + CfreeCgInlinePolicy call_policy = call_inline_policy(in); + CfreeCgInlinePolicy callee_policy = + callee ? callee->desc.inline_policy : CFREE_CG_INLINE_DEFAULT; + if (call_policy == CFREE_CG_INLINE_NEVER || + callee_policy == CFREE_CG_INLINE_NEVER) + return CFREE_CG_INLINE_NEVER; + if (call_policy == CFREE_CG_INLINE_ALWAYS || + callee_policy == CFREE_CG_INLINE_ALWAYS) + return CFREE_CG_INLINE_ALWAYS; + if (call_policy == CFREE_CG_INLINE_HINT || + callee_policy == CFREE_CG_INLINE_HINT) + return CFREE_CG_INLINE_HINT; + return CFREE_CG_INLINE_DEFAULT; +} + static int func_reaches(FuncSet* fs, Func* from, Func* target, u8* seen) { int idx = funcset_index(fs, from); if (idx < 0) return 0; @@ -136,7 +161,8 @@ static int op_supported_in_straightline_inline(IROp op) { } } -static int callee_inline_shape(Func* callee, u32* cost_out) { +static int callee_inline_shape(Func* callee, CfreeCgInlinePolicy policy, + u32* cost_out) { if (!callee || callee->opt_reg_ssa || callee->opt_rewritten) return 0; if (cfree_cg_type_func_is_variadic((CfreeCompiler*)callee->c, callee->type)) { metrics_count(callee->c, "opt.inline.refuse_shape_variadic", 1); @@ -174,7 +200,10 @@ static int callee_inline_shape(Func* callee, u32* cost_out) { return 0; } if (nret > 1) cost += (nret - 1u) * 4u; - if (cost > INLINE_NORMAL_COST_LIMIT) { + u32 cost_limit = policy == CFREE_CG_INLINE_ALWAYS ? 0xffffffffu + : policy == CFREE_CG_INLINE_HINT ? INLINE_HINT_COST_LIMIT + : INLINE_NORMAL_COST_LIMIT; + if (cost > cost_limit) { metrics_count(callee->c, "opt.inline.refuse_shape_budget", 1); return 0; } @@ -553,12 +582,20 @@ static int inline_call_site(Func* caller, u32 block_idx, u32 inst_idx, } static int caller_growth_ok(FuncSet* fs, Func* caller, const u32* base_cost, - u32 callee_cost) { + u32 callee_cost, CfreeCgInlinePolicy policy) { + if (policy == CFREE_CG_INLINE_ALWAYS) return 1; int idx = funcset_index(fs, caller); u32 base = idx >= 0 ? base_cost[idx] : func_inline_cost(caller); u32 growth_limit = base / 4u; if (growth_limit < INLINE_NORMAL_COST_LIMIT) growth_limit = INLINE_NORMAL_COST_LIMIT; - if (growth_limit > INLINE_ABS_GROWTH_LIMIT) growth_limit = INLINE_ABS_GROWTH_LIMIT; + if (policy == CFREE_CG_INLINE_HINT) { + growth_limit *= 2u; + if (growth_limit < INLINE_HINT_COST_LIMIT) growth_limit = INLINE_HINT_COST_LIMIT; + if (growth_limit > INLINE_HINT_ABS_GROWTH_LIMIT) + growth_limit = INLINE_HINT_ABS_GROWTH_LIMIT; + } else if (growth_limit > INLINE_ABS_GROWTH_LIMIT) { + growth_limit = INLINE_ABS_GROWTH_LIMIT; + } return func_inline_cost(caller) + callee_cost <= base + growth_limit; } @@ -567,17 +604,23 @@ static int try_inline_call(FuncSet* fs, Func* caller, u32 b, u32 i, Inst* in = &caller->blocks[b].insts[i]; Func* callee = direct_callee(fs, in); u32 cost = 0; + CfreeCgInlinePolicy policy; if (!callee) return 0; + policy = effective_inline_policy(in, callee); metrics_count(caller->c, "opt.inline.candidates", 1); + if (policy == CFREE_CG_INLINE_NEVER) { + metrics_count(caller->c, "opt.inline.refuse_policy", 1); + return 0; + } if (recursive_or_scc(fs, caller, callee)) { metrics_count(caller->c, "opt.inline.refuse_scc", 1); return 0; } - if (!callee_inline_shape(callee, &cost)) { + if (!callee_inline_shape(callee, policy, &cost)) { metrics_count(caller->c, "opt.inline.refuse_shape", 1); return 0; } - if (!caller_growth_ok(fs, caller, base_cost, cost)) { + if (!caller_growth_ok(fs, caller, base_cost, cost, policy)) { metrics_count(caller->c, "opt.inline.refuse_growth", 1); return 0; } diff --git a/test/opt/opt_test.c b/test/opt/opt_test.c @@ -6291,6 +6291,70 @@ static void opt_inline_direct_wrapper(void) { tc_fini(&tc); } +static void opt_inline_policy_controls_decisions(void) { + TestCtx tc; + tc_init(&tc); + + Func* noinline_callee = new_named_func(&tc, (ObjSymId)31, tc.i32, NULL, 0, 0); + noinline_callee->desc.inline_policy = CFREE_CG_INLINE_NEVER; + PReg nr = add_preg(noinline_callee, tc.i32); + emit_preg_load_imm(noinline_callee, noinline_callee->entry, nr, tc.i32, 7); + emit_preg_ret(noinline_callee, noinline_callee->entry, nr, tc.i32); + + Func* always_callee = new_named_func(&tc, (ObjSymId)32, tc.i32, NULL, 0, 0); + always_callee->desc.inline_policy = CFREE_CG_INLINE_ALWAYS; + PReg ar = PREG_NONE; + for (u32 i = 0; i < 30; ++i) { + ar = add_preg(always_callee, tc.i32); + emit_preg_load_imm(always_callee, always_callee->entry, ar, tc.i32, i); + } + emit_preg_ret(always_callee, always_callee->entry, ar, tc.i32); + + Func* call_never_callee = new_named_func(&tc, (ObjSymId)33, tc.i32, NULL, 0, 0); + PReg cr = add_preg(call_never_callee, tc.i32); + emit_preg_load_imm(call_never_callee, call_never_callee->entry, cr, tc.i32, 9); + emit_preg_ret(call_never_callee, call_never_callee->entry, cr, tc.i32); + + Func* hint_callee = new_named_func(&tc, (ObjSymId)34, tc.i32, NULL, 0, 0); + PReg hr = PREG_NONE; + for (u32 i = 0; i < 30; ++i) { + hr = add_preg(hint_callee, tc.i32); + emit_preg_load_imm(hint_callee, hint_callee->entry, hr, tc.i32, i); + } + emit_preg_ret(hint_callee, hint_callee->entry, hr, tc.i32); + + Func* caller = new_named_func(&tc, (ObjSymId)35, tc.i32, NULL, 0, 0); + PReg r1 = add_preg(caller, tc.i32); + PReg r2 = add_preg(caller, tc.i32); + PReg r3 = add_preg(caller, tc.i32); + PReg r4 = add_preg(caller, tc.i32); + emit_direct_call(&tc, caller, caller->entry, (ObjSymId)31, + noinline_callee->type, NULL, 0, op_reg_(r1, tc.i32)); + Inst* call_hint = + emit_direct_call(&tc, caller, caller->entry, (ObjSymId)34, + hint_callee->type, NULL, 0, op_reg_(r4, tc.i32)); + ((IRCallAux*)call_hint->extra.aux)->desc.inline_policy = + CFREE_CG_INLINE_HINT; + Inst* call_never = + emit_direct_call(&tc, caller, caller->entry, (ObjSymId)33, + call_never_callee->type, NULL, 0, op_reg_(r3, tc.i32)); + ((IRCallAux*)call_never->extra.aux)->desc.inline_policy = + CFREE_CG_INLINE_NEVER; + emit_direct_call(&tc, caller, caller->entry, (ObjSymId)32, + always_callee->type, NULL, 0, op_reg_(r2, tc.i32)); + emit_preg_ret(caller, caller->entry, r2, tc.i32); + + Func* funcs[5] = {caller, noinline_callee, always_callee, call_never_callee, + hint_callee}; + FuncSet fs = {tc.c, tc.c->tu, funcs, 5, 5}; + opt_inline(&fs, 1); + EXPECT(count_op(caller, IR_CALL) == 2, + "noinline function and noinline call site should remain calls"); + EXPECT(count_op(caller, IR_LOAD_IMM) >= 60, + "always and hint policies should inline over normal cost budget"); + tc_fini(&tc); +} + static void opt_inline_two_return_scalar(void) { TestCtx tc; tc_init(&tc); @@ -6620,6 +6684,7 @@ int main(void) { opt_local_addr_taken_uses_frame_and_replays_addr_of(); opt_register_local_addr_frame_homes(); opt_inline_direct_wrapper(); + opt_inline_policy_controls_decisions(); opt_inline_two_return_scalar(); opt_inline_bottom_up_chain_single_iter(); opt_inline_refuses_recursive_and_unsupported(); diff --git a/test/toy/cases/136_call_builtin_attrs.expected b/test/toy/cases/136_call_builtin_attrs.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/136_call_builtin_attrs.toy b/test/toy/cases/136_call_builtin_attrs.toy @@ -0,0 +1,12 @@ +fn add2(a: i64, b: i64): i64 { + return a + b; +} + +fn __user_main(): i64 { + let direct: i64 = @call(add2, { 20, 22 }, .cold, .always_inline); + let fp: *fn(i64, i64): i64 = add2; + let indirect: i64 = @call(fp, { 10, 5 }, .noinline, .notail); + return direct + indirect - 15; +} + +fn main(): i32 { return __user_main() as i32; } diff --git a/test/toy/cases/137_call_builtin_tail.expected b/test/toy/cases/137_call_builtin_tail.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/137_call_builtin_tail.toy b/test/toy/cases/137_call_builtin_tail.toy @@ -0,0 +1,9 @@ +fn add1(x: i64): i64 { + return x + 1; +} + +fn caller(x: i64): i64 { + return @call(add1, { x }, .tail); +} + +fn main(): i32 { return caller(41) as i32; } diff --git a/test/toy/cases/138_inline_attrs.expected b/test/toy/cases/138_inline_attrs.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/138_inline_attrs.toy b/test/toy/cases/138_inline_attrs.toy @@ -0,0 +1,15 @@ +fn @[.always_inline] always_add(a: i64, b: i64): i64 { + return a + b; +} + +fn hinted_add(a: i64, b: i64): i64 { + return a + b; +} + +fn __user_main(): i64 { + let from_def: i64 = always_add(20, 1); + let from_call: i64 = @call(hinted_add, { from_def, 21 }, .inline); + return from_call; +} + +fn main(): i32 { return __user_main() as i32; } diff --git a/test/toy/err/call_builtin_tail_direct_return.expected b/test/toy/err/call_builtin_tail_direct_return.expected @@ -0,0 +1 @@ +tail @call must be returned directly diff --git a/test/toy/err/call_builtin_tail_direct_return.toy b/test/toy/err/call_builtin_tail_direct_return.toy @@ -0,0 +1,11 @@ +fn add1(x: i64): i64 { + return x + 1; +} + +fn add2(a: i64, b: i64): i64 { + return a + b; +} + +fn main(): i32 { + return add2(1, @call(add1, { 41 }, .tail)) as i32; +} diff --git a/test/toy/err/call_builtin_tail_requires_return.expected b/test/toy/err/call_builtin_tail_requires_return.expected @@ -0,0 +1 @@ +tail @call requires return diff --git a/test/toy/err/call_builtin_tail_requires_return.toy b/test/toy/err/call_builtin_tail_requires_return.toy @@ -0,0 +1,8 @@ +fn add1(x: i64): i64 { + return x + 1; +} + +fn main(): i32 { + @call(add1, { 41 }, .tail); + return 0; +}