Add #must_tail (similar syntax to #force_inline

This commit is contained in:
gingerBill
2026-01-15 16:32:52 +00:00
parent 7f509c01f1
commit 0366cd3304
6 changed files with 105 additions and 24 deletions

View File

@@ -8210,7 +8210,7 @@ gb_internal void check_objc_call_expr(CheckerContext *c, Operand *operand, Ast *
add_objc_proc_type(c, call, return_type, param_types);
}
gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice<Ast *> const &args, ProcInlining inlining, Type *type_hint) {
gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice<Ast *> const &args, ProcInlining inlining, ProcTailing tailing, Type *type_hint) {
if (proc != nullptr &&
proc->kind == Ast_BasicDirective) {
ast_node(bd, BasicDirective, proc);
@@ -8241,7 +8241,10 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
return Expr_Expr;
}
if (inlining != ProcInlining_none) {
error(call, "Inlining operators are not allowed on built-in procedures");
error(call, "Inlining directives are not allowed on built-in procedures");
}
if (tailing != ProcTailing_none) {
error(call, "Tailing directives are not allowed on built-in procedures");
}
} else {
if (proc != nullptr) {
@@ -8383,6 +8386,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
}
bool is_call_inlined = false;
bool is_call_tailed = true;
switch (inlining) {
case ProcInlining_inline:
@@ -8417,6 +8421,20 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
}
}
switch (tailing) {
case ProcTailing_none:
break;
case ProcTailing_must_tail:
is_call_tailed = true;
if (proc != nullptr) {
Entity *e = entity_from_expr(proc);
if (e != nullptr && e->kind == Entity_Procedure) {
// TODO(bill): `preserve_none`
}
}
break;
}
{
String invalid;
if (pt->kind == Type_Proc && pt->Proc.require_target_feature.len != 0) {
@@ -11825,7 +11843,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast
case_end;
case_ast_node(ce, CallExpr, node);
return check_call_expr(c, o, node, ce->proc, ce->args, ce->inlining, type_hint);
return check_call_expr(c, o, node, ce->proc, ce->args, ce->inlining, ce->tailing, type_hint);
case_end;
case_ast_node(de, DerefExpr, node);
@@ -12566,6 +12584,12 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan
case_end;
case_ast_node(ce, CallExpr, node);
switch (ce->tailing) {
case ProcTailing_must_tail:
str = gb_string_appendc(str, "#must_tail ");
break;
}
switch (ce->inlining) {
case ProcInlining_inline:
str = gb_string_appendc(str, "#force_inline ");

View File

@@ -2111,7 +2111,7 @@ gb_internal void lb_create_startup_runtime_generate_body(lbModule *m, lbProcedur
for (Entity *e : info->init_procedures) {
lbValue value = lb_find_procedure_value_from_entity(m, e);
lb_emit_call(p, value, {}, ProcInlining_none);
lb_emit_call(p, value, {}, ProcInlining_none, ProcTailing_none);
}
@@ -2157,7 +2157,7 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C
for (Entity *e : info->fini_procedures) {
lbValue value = lb_find_procedure_value_from_entity(main_module, e);
lb_emit_call(p, value, {}, ProcInlining_none);
lb_emit_call(p, value, {}, ProcInlining_none, ProcTailing_none);
}
lb_end_procedure_body(p);
@@ -2850,7 +2850,7 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star
}
lbValue startup_runtime_value = {startup_runtime->value, startup_runtime->type};
lb_emit_call(p, startup_runtime_value, {}, ProcInlining_none);
lb_emit_call(p, startup_runtime_value, {}, ProcInlining_none, ProcTailing_none);
if (build_context.command_kind == Command_test) {
Type *t_Internal_Test = find_type_in_pkg(m->info, str_lit("testing"), str_lit("Internal_Test"));
@@ -2917,16 +2917,16 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star
auto exit_args = array_make<lbValue>(temporary_allocator(), 1);
exit_args[0] = lb_emit_select(p, result, lb_const_int(m, t_int, 0), lb_const_int(m, t_int, 1));
lb_emit_call(p, exit_runner, exit_args, ProcInlining_none);
lb_emit_call(p, exit_runner, exit_args, ProcInlining_none, ProcTailing_none);
} else {
if (m->info->entry_point != nullptr) {
lbValue entry_point = lb_find_procedure_value_from_entity(m, m->info->entry_point);
lb_emit_call(p, entry_point, {}, ProcInlining_no_inline);
lb_emit_call(p, entry_point, {}, ProcInlining_no_inline, ProcTailing_none);
}
if (call_cleanup) {
lbValue cleanup_runtime_value = {cleanup_runtime->value, cleanup_runtime->type};
lb_emit_call(p, cleanup_runtime_value, {}, ProcInlining_none);
lb_emit_call(p, cleanup_runtime_value, {}, ProcInlining_none, ProcTailing_none);
}
if (is_dll_main) {

View File

@@ -345,6 +345,7 @@ struct lbProcedure {
Ast * body;
u64 tags;
ProcInlining inlining;
ProcTailing tailing;
bool is_foreign;
bool is_export;
bool is_entry_point;
@@ -484,7 +485,7 @@ gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlo
gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, Ast *node);
gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t);
gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue right);
gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args, ProcInlining inlining = ProcInlining_none);
gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args, ProcInlining inlining = ProcInlining_none, ProcTailing tailing = ProcTailing_none);
gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t);
gb_internal lbValue lb_emit_comp_against_nil(lbProcedure *p, TokenKind op_kind, lbValue x);

View File

@@ -117,6 +117,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i
p->type_expr = decl->type_expr;
p->body = pl->body;
p->inlining = pl->inlining;
p->tailing = pl->tailing;
p->is_foreign = entity->Procedure.is_foreign;
p->is_export = entity->Procedure.is_export;
p->is_entry_point = false;
@@ -178,6 +179,12 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i
}
}
switch (p->tailing) {
case ProcTailing_must_tail:
lb_add_attribute_to_proc(m, p->value, "preserve_none");
break;
}
switch (entity->Procedure.optimization_mode) {
case ProcedureOptimizationMode_None:
lb_add_attribute_to_proc(m, p->value, "optnone");
@@ -387,6 +394,7 @@ gb_internal lbProcedure *lb_create_dummy_procedure(lbModule *m, String link_name
p->body = nullptr;
p->tags = 0;
p->inlining = ProcInlining_none;
p->tailing = ProcTailing_none;
p->is_foreign = false;
p->is_export = false;
p->is_entry_point = false;
@@ -855,7 +863,7 @@ gb_internal Array<lbValue> lb_value_to_array(lbProcedure *p, gbAllocator const &
gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr, Array<lbValue> const &processed_args, Type *abi_rt, lbAddr context_ptr, ProcInlining inlining) {
gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr, Array<lbValue> const &processed_args, Type *abi_rt, lbAddr context_ptr, ProcInlining inlining, ProcTailing tailing) {
GB_ASSERT(p->module->ctx == LLVMGetTypeContext(LLVMTypeOf(value.value)));
unsigned arg_count = cast(unsigned)processed_args.count;
@@ -972,6 +980,15 @@ gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue
break;
}
switch (tailing) {
case ProcTailing_none:
break;
case ProcTailing_must_tail:
LLVMSetTailCall(ret, true);
LLVMSetTailCallKind(ret, LLVMTailCallKindMustTail);
break;
}
lbValue res = {};
res.value = ret;
res.type = abi_rt;
@@ -1045,7 +1062,7 @@ gb_internal lbValue lb_emit_conjugate(lbProcedure *p, lbValue val, Type *type) {
return lb_emit_load(p, res);
}
gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args, ProcInlining inlining) {
gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args, ProcInlining inlining, ProcTailing tailing) {
lbModule *m = p->module;
Type *pt = base_type(value.type);
@@ -1168,10 +1185,10 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> c
if (return_by_pointer) {
lbValue return_ptr = lb_add_local_generated(p, rt, true).addr;
lb_emit_call_internal(p, value, return_ptr, processed_args, nullptr, context_ptr, inlining);
lb_emit_call_internal(p, value, return_ptr, processed_args, nullptr, context_ptr, inlining, tailing);
result = lb_emit_load(p, return_ptr);
} else if (rt != nullptr) {
result = lb_emit_call_internal(p, value, {}, processed_args, rt, context_ptr, inlining);
result = lb_emit_call_internal(p, value, {}, processed_args, rt, context_ptr, inlining, tailing);
if (ft->ret.cast_type) {
result.value = OdinLLVMBuildTransmute(p, result.value, ft->ret.cast_type);
}
@@ -1184,7 +1201,7 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> c
result = lb_emit_conv(p, result, rt);
}
} else {
lb_emit_call_internal(p, value, {}, processed_args, nullptr, context_ptr, inlining);
lb_emit_call_internal(p, value, {}, processed_args, nullptr, context_ptr, inlining, tailing);
}
if (original_rt != rt) {
@@ -4402,6 +4419,25 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) {
return lb_handle_objc_auto_send(p, expr, slice(call_args, 0, call_args.count));
}
return lb_emit_call(p, value, call_args, ce->inlining);
ProcInlining inlining = ce->inlining;
ProcTailing tailing = ce->tailing;
if (tailing == ProcTailing_none &&
proc_entity && proc_entity->kind == Entity_Procedure &&
proc_entity->decl_info &&
proc_entity->decl_info->proc_lit) {
ast_node(pl, ProcLit, proc_entity->decl_info->proc_lit);
if (pl->inlining != ProcInlining_none) {
inlining = pl->inlining;
}
if (pl->tailing != ProcTailing_none) {
tailing = pl->tailing;
}
}
return lb_emit_call(p, value, call_args, inlining, tailing);
}

View File

@@ -2176,7 +2176,7 @@ gb_internal bool ast_on_same_line(Token const &x, Ast *yp) {
return x.pos.line == y.pos.line;
}
gb_internal Ast *parse_force_inlining_operand(AstFile *f, Token token) {
gb_internal Ast *parse_inlining_or_tailing_operand(AstFile *f, Token token) {
Ast *expr = parse_unary_expr(f, false);
Ast *e = strip_or_return_expr(expr);
if (e == nullptr) {
@@ -2187,11 +2187,14 @@ gb_internal Ast *parse_force_inlining_operand(AstFile *f, Token token) {
return ast_bad_expr(f, token, f->curr_token);
}
ProcInlining pi = ProcInlining_none;
ProcTailing pt = ProcTailing_none;
if (token.kind == Token_Ident) {
if (token.string == "force_inline") {
pi = ProcInlining_inline;
} else if (token.string == "force_no_inline") {
pi = ProcInlining_no_inline;
} else if (token.string == "must_tail") {
pt = ProcTailing_must_tail;
}
}
@@ -2211,6 +2214,14 @@ gb_internal Ast *parse_force_inlining_operand(AstFile *f, Token token) {
}
}
if (pt != ProcTailing_none) {
if (e->kind == Ast_ProcLit) {
e->ProcLit.tailing = pt;
} else if (e->kind == Ast_CallExpr) {
e->CallExpr.tailing = pt;
}
}
return expr;
}
@@ -2507,8 +2518,9 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
syntax_error(tag, "#relative types have now been removed in favour of \"core:relative\"");
return ast_relative_type(f, tag, type);
} else if (name.string == "force_inline" ||
name.string == "force_no_inline") {
return parse_force_inlining_operand(f, name);
name.string == "force_no_inline" ||
name.string == "must_tail") {
return parse_inlining_or_tailing_operand(f, name);
}
return ast_basic_directive(f, token, name);
}
@@ -5399,8 +5411,9 @@ gb_internal Ast *parse_stmt(AstFile *f) {
expect_semicolon(f);
return stmt;
} else if (name.string == "force_inline" ||
name.string == "force_no_inline") {
Ast *expr = parse_force_inlining_operand(f, name);
name.string == "force_no_inline" ||
name.string == "must_tail") {
Ast *expr = parse_inlining_or_tailing_operand(f, name);
Ast *stmt = ast_expr_stmt(f, expr);
expect_semicolon(f);
return stmt;

View File

@@ -263,12 +263,17 @@ struct ForeignFileWorkerData {
enum ProcInlining {
ProcInlining_none = 0,
ProcInlining_inline = 1,
enum ProcInlining : u8 {
ProcInlining_none = 0,
ProcInlining_inline = 1,
ProcInlining_no_inline = 2,
};
enum ProcTailing : u8 {
ProcTailing_none = 0,
ProcTailing_must_tail = 1,
};
enum ProcTag {
ProcTag_bounds_check = 1<<0,
ProcTag_no_bounds_check = 1<<1,
@@ -441,6 +446,7 @@ struct AstSplitArgs {
Ast *body; \
u64 tags; \
ProcInlining inlining; \
ProcTailing tailing; \
Token where_token; \
Slice<Ast *> where_clauses; \
DeclInfo *decl; \
@@ -486,6 +492,7 @@ AST_KIND(_ExprBegin, "", bool) \
Token close; \
Token ellipsis; \
ProcInlining inlining; \
ProcTailing tailing; \
bool optional_ok_one; \
bool was_selector; \
AstSplitArgs *split_args; \