diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index a8c198476..6755ca4d0 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -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, diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index f4beddcbb..719cb0374 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -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) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index cd2307c6d..74ae02f94 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -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 const &args, ProcInlining inlining, Type *type_hint) { +gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice 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 "); diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 1cde65640..0bf3d1125 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -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(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) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index da5d91f2e..e10471527 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -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 const &args, ProcInlining inlining = ProcInlining_none); +gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array 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 { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 216d600da..9ac51bba3 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -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 lb_value_to_array(lbProcedure *p, gbAllocator const & -gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr, Array 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 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 const &args, ProcInlining inlining) { +gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array 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 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 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); } diff --git a/src/parser.cpp b/src/parser.cpp index 823021e04..fc55dae97 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -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; diff --git a/src/parser.hpp b/src/parser.hpp index 71b61d95f..011330438 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -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 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; \ diff --git a/src/types.cpp b/src/types.cpp index 18e3b56ac..911cd4448 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -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);