Merge pull request #6135 from odin-lang/bill/must-tail

`#must_tail`
This commit is contained in:
gingerBill
2026-01-15 18:03:09 +00:00
committed by GitHub
9 changed files with 144 additions and 62 deletions

View File

@@ -17,6 +17,11 @@ Proc_Inlining :: enum u32 {
No_Inline = 2,
}
Proc_Tailing :: enum u32 {
None = 0,
Must_Tail = 1,
}
Proc_Calling_Convention_Extra :: enum i32 {
Foreign_Block_Default,
}
@@ -147,6 +152,7 @@ Proc_Lit :: struct {
body: ^Stmt, // nil when it represents a foreign procedure
tags: Proc_Tags,
inlining: Proc_Inlining,
tailing: Proc_Tailing,
where_token: tokenizer.Token,
where_clauses: []^Expr,
}
@@ -243,6 +249,7 @@ Matrix_Index_Expr :: struct {
Call_Expr :: struct {
using node: Expr,
inlining: Proc_Inlining,
tailing: Proc_Tailing,
expr: ^Expr,
open: tokenizer.Pos,
args: []^Expr,

View File

@@ -1528,8 +1528,8 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
es.expr = ce
return es
case "force_inline", "force_no_inline":
expr := parse_inlining_operand(p, true, tag)
case "force_inline", "force_no_inline", "must_tail":
expr := parse_inlining_or_tailing_operand(p, true, tag)
es := ast.new(ast.Expr_Stmt, expr.pos, expr)
es.expr = expr
return es
@@ -2235,10 +2235,11 @@ parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type {
return pt
}
parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ast.Expr {
parse_inlining_or_tailing_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ast.Expr {
expr := parse_unary_expr(p, lhs)
pi := ast.Proc_Inlining.None
pt := ast.Proc_Tailing.None
#partial switch tok.kind {
case .Inline:
pi = .Inline
@@ -2250,6 +2251,8 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^
pi = .Inline
case "force_no_inline":
pi = .No_Inline
case "must_tail":
pt = .Must_Tail
}
}
@@ -2259,13 +2262,19 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^
if e.inlining != .None && e.inlining != pi {
error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal")
}
if pt != .None {
error(p, expr.pos, "'#must_tail' can only be applied to a procedure call, not the procedure literal")
}
e.inlining = pi
e.tailing = pt
return expr
case ^ast.Call_Expr:
if e.inlining != .None && e.inlining != pi {
error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call")
}
e.inlining = pi
e.tailing = pt
return expr
}
}
@@ -2451,7 +2460,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
return rt
case "force_inline", "force_no_inline":
return parse_inlining_operand(p, lhs, name)
return parse_inlining_or_tailing_operand(p, lhs, name)
case:
expr := parse_expr(p, lhs)
end := expr.pos if expr != nil else end_pos(tok)
@@ -2464,7 +2473,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
case .Inline, .No_Inline:
tok := advance_token(p)
return parse_inlining_operand(p, lhs, tok)
return parse_inlining_or_tailing_operand(p, lhs, tok)
case .Proc:
tok := expect_token(p, .Proc)

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,23 @@ 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 (c->curr_proc_sig == nullptr || !are_types_identical(c->curr_proc_sig, pt)) {
ERROR_BLOCK();
gbString a = type_to_string(pt);
gbString b = type_to_string(c->curr_proc_sig);
error(call, "Use of '#must_tail' of a procedure must have the same type as the procedure it was called within");
error_line("\tCall type: %s, parent type: %s", a, b);
gb_string_free(b);
gb_string_free(a);
}
break;
}
{
String invalid;
if (pt->kind == Type_Proc && pt->Proc.require_target_feature.len != 0) {
@@ -11825,7 +11846,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 +12587,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);
@@ -670,6 +671,7 @@ enum lbCallingConventionKind : unsigned {
lbCallingConvention_PreserveAll = 15,
lbCallingConvention_Swift = 16,
lbCallingConvention_CXX_FAST_TLS = 17,
lbCallingConvention_PreserveNone = 21,
lbCallingConvention_FirstTargetCC = 64,
lbCallingConvention_X86_StdCall = 64,
lbCallingConvention_X86_FastCall = 65,
@@ -723,6 +725,10 @@ lbCallingConventionKind const lb_calling_convention_map[ProcCC_MAX] = {
lbCallingConvention_Win64, // ProcCC_Win64,
lbCallingConvention_X86_64_SysV, // ProcCC_SysV,
lbCallingConvention_PreserveNone, // ProcCC_PreserveNone,
lbCallingConvention_PreserveMost, // ProcCC_PreserveMost,
lbCallingConvention_PreserveAll, // ProcCC_PreserveAll,
};
enum : LLVMDWARFTypeEncoding {

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;
@@ -387,6 +388,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 +857,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 +974,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 +1056,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 +1179,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 +1195,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 +4413,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,15 @@ gb_internal Ast *parse_force_inlining_operand(AstFile *f, Token token) {
}
}
if (pt != ProcTailing_none) {
if (e->kind == Ast_ProcLit) {
syntax_error(expr, "'#must_call' can only be applied to a procedure call, not the procedure literal");
e->ProcLit.tailing = pt;
} else if (e->kind == Ast_CallExpr) {
e->CallExpr.tailing = pt;
}
}
return expr;
}
@@ -2507,8 +2519,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);
}
@@ -4008,6 +4021,10 @@ gb_internal ProcCallingConvention string_to_calling_convention(String const &s)
if (s == "win64") return ProcCC_Win64;
if (s == "sysv") return ProcCC_SysV;
if (s == "preserve/none") return ProcCC_PreserveNone;
if (s == "preserve/most") return ProcCC_PreserveMost;
if (s == "preserve/all") return ProcCC_PreserveAll;
if (s == "system") {
if (build_context.metrics.os == TargetOs_windows) {
return ProcCC_StdCall;
@@ -5399,8 +5416,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,
@@ -296,6 +301,9 @@ enum ProcCallingConvention : i32 {
ProcCC_Win64 = 9,
ProcCC_SysV = 10,
ProcCC_PreserveNone = 11,
ProcCC_PreserveMost = 12,
ProcCC_PreserveAll = 13,
ProcCC_MAX,
@@ -315,6 +323,9 @@ gb_global char const *proc_calling_convention_strings[ProcCC_MAX] = {
"inlineasm",
"win64",
"sysv",
"preserve/none",
"preserve/most",
"preserve/all",
};
gb_internal ProcCallingConvention default_calling_convention(void) {
@@ -441,6 +452,7 @@ struct AstSplitArgs {
Ast *body; \
u64 tags; \
ProcInlining inlining; \
ProcTailing tailing; \
Token where_token; \
Slice<Ast *> where_clauses; \
DeclInfo *decl; \
@@ -486,6 +498,7 @@ AST_KIND(_ExprBegin, "", bool) \
Token close; \
Token ellipsis; \
ProcInlining inlining; \
ProcTailing tailing; \
bool optional_ok_one; \
bool was_selector; \
AstSplitArgs *split_args; \

View File

@@ -5210,40 +5210,12 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
case Type_Proc:
str = gb_string_appendc(str, "proc");
switch (type->Proc.calling_convention) {
case ProcCC_Odin:
if (default_calling_convention() != ProcCC_Odin) {
str = gb_string_appendc(str, " \"odin\" ");
}
break;
case ProcCC_Contextless:
if (default_calling_convention() != ProcCC_Contextless) {
str = gb_string_appendc(str, " \"contextless\" ");
}
break;
case ProcCC_CDecl:
str = gb_string_appendc(str, " \"c\" ");
break;
case ProcCC_StdCall:
str = gb_string_appendc(str, " \"std\" ");
break;
case ProcCC_FastCall:
str = gb_string_appendc(str, " \"fastcall\" ");
break;
break;
case ProcCC_None:
str = gb_string_appendc(str, " \"none\" ");
break;
case ProcCC_Naked:
str = gb_string_appendc(str, " \"naked\" ");
break;
// case ProcCC_VectorCall:
// str = gb_string_appendc(str, " \"vectorcall\" ");
// break;
// case ProcCC_ClrCall:
// str = gb_string_appendc(str, " \"clrcall\" ");
// break;
if (type->Proc.calling_convention != default_calling_convention()) {
str = gb_string_appendc(str, " \"");
str = gb_string_appendc(str, proc_calling_convention_strings[type->Proc.calling_convention]);
str = gb_string_appendc(str, "\" ");
}
str = gb_string_appendc(str, "(");
if (type->Proc.params) {
str = write_type_to_string(str, type->Proc.params, shorthand, allow_polymorphic);