From 490dd656640d1daafbbaec676f030f1ee0563620 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Tue, 3 Aug 2021 11:24:57 +0200 Subject: [PATCH] Add attributes to call site. @inline, @noinline to calls #250 --- src/compiler/compiler_internal.h | 3 ++ src/compiler/enums.h | 1 + src/compiler/llvm_codegen.c | 2 + src/compiler/llvm_codegen_expr.c | 16 +++++- src/compiler/llvm_codegen_function.c | 5 +- src/compiler/llvm_codegen_internal.h | 1 + src/compiler/parse_expr.c | 1 + src/compiler/parse_global.c | 35 +++++-------- src/compiler/parser_internal.h | 1 + src/compiler/sema_decls.c | 15 +++--- src/compiler/sema_expr.c | 26 ++++++++++ src/compiler/sema_internal.h | 1 + test/test_suite/expressions/call_inline.c3t | 49 +++++++++++++++++++ test/test_suite/functions/test_regression.c3t | 4 +- 14 files changed, 121 insertions(+), 39 deletions(-) create mode 100644 test/test_suite/expressions/call_inline.c3t diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index f41c776a5..ea1c7efb1 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -616,6 +616,8 @@ typedef struct bool is_type_method : 1; bool is_pointer_call : 1; bool unsplat_last : 1; + bool force_inline : 1; + bool force_noinline : 1; union { Expr *function; @@ -624,6 +626,7 @@ typedef struct Expr **arguments; Decl **body_arguments; Ast *body; + Attr **attributes; } ExprCall; typedef struct diff --git a/src/compiler/enums.h b/src/compiler/enums.h index b0274c870..1d83a0eed 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -596,6 +596,7 @@ typedef enum ATTR_TYPEDEF = 1 << 7, ATTR_MEMBER = 1 << 8, ATTR_INTERFACE = 1 << 9, + ATTR_CALL = 1 << 10, } AttributeDomain; typedef enum diff --git a/src/compiler/llvm_codegen.c b/src/compiler/llvm_codegen.c index 2572b19a6..80a4cecfd 100644 --- a/src/compiler/llvm_codegen.c +++ b/src/compiler/llvm_codegen.c @@ -585,6 +585,7 @@ unsigned intrinsic_id_lifetime_end; unsigned attribute_noinline; +unsigned attribute_optnone; unsigned attribute_alwaysinline; unsigned attribute_inlinehint; unsigned attribute_noreturn; @@ -665,6 +666,7 @@ void llvm_codegen_setup() intrinsic_id_umin = lookup_intrinsic("llvm.umin"); attribute_noinline = lookup_attribute("noinline"); + attribute_optnone = lookup_attribute("optnone"); attribute_alwaysinline = lookup_attribute("alwaysinline"); attribute_inlinehint = lookup_attribute("inlinehint"); attribute_noreturn = lookup_attribute("noreturn"); diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 74ae76ba5..66efecf54 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -2720,6 +2720,8 @@ void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr) LLVMValueRef func; BEValue temp_value; + bool always_inline = false; + // 1. Call through a pointer. if (expr->call_expr.is_pointer_call) { @@ -2745,7 +2747,7 @@ void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr) { // 2a. Get the function declaration Decl *function_decl = expr->call_expr.func_ref; - + always_inline = function_decl->func_decl.attr_inline; if (function_decl->func_decl.is_builtin) { gencontext_emit_call_intrinsic_expr(c, result_value, expr); @@ -2907,7 +2909,17 @@ void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr) // 10. Create the actual call (remember to emit a loc, because we might have shifted loc emitting the params) EMIT_LOC(c, expr); LLVMValueRef call_value = LLVMBuildCall2(c->builder, func_type, func, values, vec_size(values), ""); - + if (expr->call_expr.force_noinline) + { + llvm_attribute_add_call(c, call_value, attribute_noinline, -1, 0); + } + else + { + if (expr->call_expr.force_inline || always_inline) + { + llvm_attribute_add_call(c, call_value, attribute_alwaysinline, -1, 0); + } + } // 11. Process the return value. switch (ret_info->kind) { diff --git a/src/compiler/llvm_codegen_function.c b/src/compiler/llvm_codegen_function.c index 22e34809f..62ec20760 100644 --- a/src/compiler/llvm_codegen_function.c +++ b/src/compiler/llvm_codegen_function.c @@ -557,10 +557,7 @@ void llvm_emit_function_decl(GenContext *c, Decl *decl) ABIArgInfo *info = param->var.abi_info; llvm_emit_param_attributes(c, function, info, false, info->param_index_start + 1, info->param_index_end); } - if (decl->func_decl.attr_inline) - { - llvm_attribute_add(c, function, attribute_alwaysinline, -1); - } + // We ignore decl->func_decl.attr_inline and place it in every call instead. if (decl->func_decl.attr_noinline) { llvm_attribute_add(c, function, attribute_noinline, -1); diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index 8645b3f9c..181eec778 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -162,6 +162,7 @@ extern unsigned intrinsic_id_lifetime_end; // LLVM Attributes extern unsigned attribute_noinline; // No function inlining +extern unsigned attribute_optnone; // No optimization extern unsigned attribute_alwaysinline; // Force inlining extern unsigned attribute_inlinehint; // "Inline possibly" extern unsigned attribute_noreturn; // No function return diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index 44664dde6..932a2a3c2 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -449,6 +449,7 @@ static Expr *parse_call_expr(Context *context, Expr *left) { call->call_expr.body = TRY_AST_OR(parse_compound_stmt(context), poisoned_expr); } + if (!parse_attributes(context, &call->call_expr.attributes)) return false; return call; } diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 6ec455b80..a139e0c57 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -960,9 +960,9 @@ bool parse_next_is_case_type(Context *context) * * @return true if parsing succeeded, false if recovery is needed */ -static inline bool parse_attributes(Context *context, Decl *parent_decl) +bool parse_attributes(Context *context, Attr ***attributes_ref) { - parent_decl->attributes = NULL; + *attributes_ref = NULL; while (try_consume(context, TOKEN_AT)) { @@ -982,16 +982,16 @@ static inline bool parse_attributes(Context *context, Decl *parent_decl) attr->expr = TRY_EXPR_OR(parse_const_paren_expr(context), false); } const char *name = TOKSTR(attr->name); - VECEACH(parent_decl->attributes, i) + VECEACH(*attributes_ref, i) { - Attr *other_attr = parent_decl->attributes[i]; + Attr *other_attr = *attributes_ref[i]; if (TOKSTR(other_attr->name) == name) { SEMA_TOKID_ERROR(attr->name, "Repeat of attribute '%s' here.", name); return false; } } - parent_decl->attributes = VECADD(parent_decl->attributes, attr); + *attributes_ref = VECADD(*attributes_ref, attr); } return true; } @@ -1029,7 +1029,7 @@ static inline Decl *parse_global_declaration(Context *context, Visibility visibi CONSUME_OR(TOKEN_IDENT, poisoned_decl); } - if (!parse_attributes(context, decl)) return poisoned_decl; + if (!parse_attributes(context, &decl->attributes)) return poisoned_decl; if (try_consume(context, TOKEN_EQ)) { decl->var.init_expr = TRY_EXPR_OR(parse_initializer(context), poisoned_decl); @@ -1272,7 +1272,7 @@ bool parse_struct_body(Context *context, Decl *parent) member->span.loc = context->prev_tok; advance_and_verify(context, TOKEN_IDENT); } - if (!parse_attributes(context, member)) return false; + if (!parse_attributes(context, &member->attributes)) return false; if (!parse_struct_body(context, member)) { decl_poison(parent); @@ -1317,7 +1317,7 @@ bool parse_struct_body(Context *context, Decl *parent) return false; } advance(context); - if (!parse_attributes(context, member)) return false; + if (!parse_attributes(context, &member->attributes)) return false; if (!try_consume(context, TOKEN_COMMA)) break; if (was_inline) { @@ -1351,7 +1351,7 @@ static inline Decl *parse_struct_declaration(Context *context, Visibility visibi if (!consume_type_name(context, type_name)) return poisoned_decl; Decl *decl = decl_new_with_type(name, decl_from_token(type), visibility); - if (!parse_attributes(context, decl)) + if (!parse_attributes(context, &decl->attributes)) { return poisoned_decl; } @@ -1614,19 +1614,6 @@ static inline Decl *parse_define(Context *context, Visibility visibility) } -static AttributeDomain TOKEN_TO_ATTR[TOKEN_EOF + 1] = { - [TOKEN_FUNC] = ATTR_FUNC, - [TOKEN_VAR] = ATTR_VAR, - [TOKEN_ENUM] = ATTR_ENUM, - [TOKEN_STRUCT] = ATTR_STRUCT, - [TOKEN_INTERFACE] = ATTR_INTERFACE, - [TOKEN_UNION] = ATTR_UNION, - [TOKEN_CONST] = ATTR_CONST, - [TOKEN_DEFINE] = ATTR_TYPEDEF, - [TOKEN_ERRTYPE] = ATTR_ERROR, -}; - - /** * func_header ::= type '!'? (type '.')? IDENT * macro_header ::= (type '!'?)? (type '.')? IDENT @@ -1895,7 +1882,7 @@ static inline Decl *parse_func_definition(Context *context, Visibility visibilit RANGE_EXTEND_PREV(func); if (!parse_parameter_list(context, visibility, &(func->func_decl.function_signature), is_interface)) return poisoned_decl; - if (!parse_attributes(context, func)) return poisoned_decl; + if (!parse_attributes(context, &func->attributes)) return poisoned_decl; // TODO remove is_interface = TOKEN_IS(TOKEN_EOS); @@ -1934,7 +1921,7 @@ static inline Decl *parse_interface_declaration(Context *context, Visibility vis if (!consume_type_name(context, "interface")) return poisoned_decl; Decl *decl = decl_new_with_type(name, DECL_INTERFACE, visibility); - if (!parse_attributes(context, decl)) + if (!parse_attributes(context, &decl->attributes)) { return poisoned_decl; } diff --git a/src/compiler/parser_internal.h b/src/compiler/parser_internal.h index 2ac32b4f9..cbbad956d 100644 --- a/src/compiler/parser_internal.h +++ b/src/compiler/parser_internal.h @@ -46,6 +46,7 @@ void recover_top_level(Context *context); Expr *parse_decl_expr_list(Context *context); Ast* parse_compound_stmt(Context *context); Ast *parse_jump_stmt_no_eos(Context *context); +bool parse_attributes(Context *context, Attr ***attributes_ref); bool parse_switch_body(Context *context, Ast ***cases, TokenType case_type, TokenType default_type, bool allow_multiple_values); diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 3af43a0e5..ca980c309 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -5,7 +5,6 @@ #include "sema_internal.h" -static AttributeType sema_analyse_attribute(Context *context, Attr *attr, AttributeDomain domain); static bool sema_analyse_struct_union(Context *context, Decl *decl); @@ -713,10 +712,12 @@ static const char *attribute_domain_to_string(AttributeDomain domain) return "error type"; case ATTR_TYPEDEF: return "typedef"; + case ATTR_CALL: + return "call"; } UNREACHABLE } -static AttributeType sema_analyse_attribute(Context *context, Attr *attr, AttributeDomain domain) +AttributeType sema_analyse_attribute(Context *context, Attr *attr, AttributeDomain domain) { AttributeType type = attribute_by_name(attr); if (type == ATTRIBUTE_NONE) @@ -726,16 +727,16 @@ static AttributeType sema_analyse_attribute(Context *context, Attr *attr, Attrib } static AttributeDomain attribute_domain[NUMBER_OF_ATTRIBUTES] = { [ATTRIBUTE_WEAK] = ATTR_FUNC | ATTR_CONST | ATTR_VAR, - [ATTRIBUTE_EXTNAME] = ~0, + [ATTRIBUTE_EXTNAME] = ~ATTR_CALL, [ATTRIBUTE_SECTION] = ATTR_FUNC | ATTR_CONST | ATTR_VAR, [ATTRIBUTE_PACKED] = ATTR_STRUCT | ATTR_UNION | ATTR_ERROR, [ATTRIBUTE_NORETURN] = ATTR_FUNC, [ATTRIBUTE_ALIGN] = ATTR_FUNC | ATTR_CONST | ATTR_VAR | ATTR_STRUCT | ATTR_UNION | ATTR_MEMBER, - [ATTRIBUTE_INLINE] = ATTR_FUNC, - [ATTRIBUTE_NOINLINE] = ATTR_FUNC, + [ATTRIBUTE_INLINE] = ATTR_FUNC | ATTR_CALL, + [ATTRIBUTE_NOINLINE] = ATTR_FUNC | ATTR_CALL, [ATTRIBUTE_OPAQUE] = ATTR_STRUCT | ATTR_UNION, - [ATTRIBUTE_USED] = ~0, - [ATTRIBUTE_UNUSED] = ~0, + [ATTRIBUTE_USED] = ~ATTR_CALL, + [ATTRIBUTE_UNUSED] = ~ATTR_CALL, [ATTRIBUTE_NAKED] = ATTR_FUNC, [ATTRIBUTE_CDECL] = ATTR_FUNC, [ATTRIBUTE_STDCALL] = ATTR_FUNC, diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 2408891e4..c1d833b87 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -1621,6 +1621,29 @@ static bool sema_analyse_body_expansion(Context *context, Expr *call) bool sema_expr_analyse_general_call(Context *context, Type *to, Expr *expr, Decl *decl, Expr *struct_var, bool is_macro) { + int force_inline = -1; + VECEACH(expr->call_expr.attributes, i) + { + Attr *attr = expr->call_expr.attributes[i]; + AttributeType attribute = sema_analyse_attribute(context, attr, ATTR_CALL); + if (attribute == ATTRIBUTE_NONE) return expr_poison(expr); + + bool had = false; + switch (attribute) + { + case ATTRIBUTE_INLINE: + case ATTRIBUTE_NOINLINE: + if (decl->decl_kind != DECL_FUNC) + { + SEMA_TOKID_ERROR(attr->name, "Inline / noinline attribute is only allowed for direct function/method calls"); + return expr_poison(expr); + } + force_inline = attribute == ATTRIBUTE_INLINE ? 1 : 0; + break; + default: + UNREACHABLE; + } + } expr->call_expr.is_type_method = struct_var != NULL; switch (decl->decl_kind) { @@ -1647,6 +1670,8 @@ bool sema_expr_analyse_general_call(Context *context, Type *to, Expr *expr, Decl return false; } expr->call_expr.func_ref = decl; + expr->call_expr.force_inline = force_inline == 1; + expr->call_expr.force_noinline = force_inline == 0; return sema_expr_analyse_func_call(context, to, expr, decl, struct_var); case DECL_GENERIC: if (is_macro) @@ -1670,6 +1695,7 @@ static inline bool sema_expr_analyse_call(Context *context, Type *to, Expr *expr expr->constant = false; expr->pure = false; Expr *func_expr = expr->call_expr.function; + if (!sema_analyse_expr_value(context, NULL, func_expr)) return false; if (func_expr->expr_kind == EXPR_MACRO_BODY_EXPANSION) { diff --git a/src/compiler/sema_internal.h b/src/compiler/sema_internal.h index d17b3e78d..b7e915f56 100644 --- a/src/compiler/sema_internal.h +++ b/src/compiler/sema_internal.h @@ -52,3 +52,4 @@ void context_change_scope_with_flags(Context *context, ScopeFlags flags); #define POP_BREAKCONT() POP_CONTINUE(); POP_BREAK() void c_abi_func_create(FunctionSignature *signature); +AttributeType sema_analyse_attribute(Context *context, Attr *attr, AttributeDomain domain); diff --git a/test/test_suite/expressions/call_inline.c3t b/test/test_suite/expressions/call_inline.c3t new file mode 100644 index 000000000..3b816aa22 --- /dev/null +++ b/test/test_suite/expressions/call_inline.c3t @@ -0,0 +1,49 @@ +module inlineme; +import std::io; + +func void test1() @inline +{ + io::println("Inline!"); +} + +func void test2() @noinline +{ + io::println("No inline!"); +} + +func void test3() +{ + io::println("Plain"); +} + +func void main() +{ + test1() @inline; + test2() @inline; + test3() @inline; + test1() @noinline; + test2() @noinline; + test3() @noinline; + test1(); + test2(); + test3(); +} +// #expect: inlineme.ll + +entry: + call void @inlineme.test1() #2 + call void @inlineme.test2() #2 + call void @inlineme.test3() #2 + call void @inlineme.test1() #3 + call void @inlineme.test2() #3 + call void @inlineme.test3() #3 + call void @inlineme.test1() #2 + call void @inlineme.test2() + call void @inlineme.test3() + ret void +} + +attributes #0 = { nounwind } +attributes #1 = { noinline nounwind } +attributes #2 = { alwaysinline } +attributes #3 = { noinline } \ No newline at end of file diff --git a/test/test_suite/functions/test_regression.c3t b/test/test_suite/functions/test_regression.c3t index 64b5a2cee..e446626f1 100644 --- a/test/test_suite/functions/test_regression.c3t +++ b/test/test_suite/functions/test_regression.c3t @@ -341,9 +341,9 @@ define i32 @test.test1(i32 %0, i32 %1) ret i32 %6 } -declare void @test_virtual3(i64, i8*) #1 +declare void @test_virtual3(i64, i8*) -declare void @test_virtual2(i8*, i8*) #1 +declare void @test_virtual2(i8*, i8*) define i32 @test.sum_us(i64 %0, i8* %1) %x = alloca %"int[]", align 8