From 4629754f7ced5df477eb017872ef65539db64a27 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 24 Oct 2020 16:32:37 +0100 Subject: [PATCH] Inline asm expression (-llvm-api) See https://llvm.org/docs/LangRef.html#inline-assembler-expressions Example: ``` x := asm(i32) -> i32 { "bswap $0", "=r,r", }(123); ``` Allowed directives `#side_effect`, `#align_stack`, `#att`, `#intel` e.g. `asm() #side_effect #intel {...}` --- src/check_decl.cpp | 6 ++- src/check_expr.cpp | 92 ++++++++++++++++++++++++++++++++++--- src/check_type.cpp | 9 +++- src/llvm_backend.cpp | 37 +++++++++++++++ src/parser.cpp | 105 +++++++++++++++++++++++++++++++++++++++++++ src/parser.hpp | 31 +++++++++++-- src/tokenizer.cpp | 1 + src/types.cpp | 4 ++ 8 files changed, 271 insertions(+), 14 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 639bf7d4f..1aafa6e1c 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -79,7 +79,11 @@ Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *operand, Stri } t = default_type(t); } - if (is_type_polymorphic(t)) { + if (is_type_asm_proc(t)) { + error(e->token, "Invalid use of inline asm in %.*s", LIT(context_name)); + e->type = t_invalid; + return nullptr; + } else if (is_type_polymorphic(t)) { gbString str = type_to_string(t); defer (gb_string_free(str)); error(e->token, "Invalid use of a polymorphic type '%s' in %.*s", str, LIT(context_name)); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index fe43a12a6..02b54c80a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -4260,11 +4260,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 if (o.mode == Addressing_Invalid || o.mode == Addressing_Builtin) { return false; } - if (o.type == nullptr || o.type == t_invalid) { - error(o.expr, "Invalid argument to 'type_of'"); - return false; - } - if (o.type == nullptr || o.type == t_invalid) { + if (o.type == nullptr || o.type == t_invalid || is_type_asm_proc(o.type)) { error(o.expr, "Invalid argument to 'type_of'"); return false; } @@ -4300,7 +4296,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 return false; } Type *t = o.type; - if (t == nullptr || t == t_invalid || is_type_polymorphic(t)) { + if (t == nullptr || t == t_invalid || is_type_asm_proc(o.type) || is_type_polymorphic(t)) { if (is_type_polymorphic(t)) { error(ce->args[0], "Invalid argument for 'type_info_of', unspecialized polymorphic type"); } else { @@ -4339,7 +4335,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 return false; } Type *t = o.type; - if (t == nullptr || t == t_invalid || is_type_polymorphic(operand->type)) { + if (t == nullptr || t == t_invalid || is_type_asm_proc(o.type) || is_type_polymorphic(operand->type)) { error(ce->args[0], "Invalid argument for 'typeid_of'"); return false; } @@ -10010,6 +10006,57 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type } case_end; + case_ast_node(ia, InlineAsmExpr, node); + if (c->curr_proc_decl == nullptr) { + error(node, "Inline asm expressions are only allowed within a procedure body"); + } + + if (!build_context.use_llvm_api) { + error(node, "Inline asm expressions are only currently allowed with -llvm-api"); + } + + auto param_types = array_make(heap_allocator(), ia->param_types.count); + Type *return_type = nullptr; + for_array(i, ia->param_types) { + param_types[i] = check_type(c, ia->param_types[i]); + } + if (ia->return_type != nullptr) { + return_type = check_type(c, ia->return_type); + } + Operand x = {}; + check_expr(c, &x, ia->asm_string); + if (x.mode != Addressing_Constant || !is_type_string(x.type)) { + error(x.expr, "Expected a constant string for the inline asm main parameter"); + } + check_expr(c, &x, ia->constraints_string); + if (x.mode != Addressing_Constant || !is_type_string(x.type)) { + error(x.expr, "Expected a constant string for the inline asm constraints parameter"); + } + + Scope *scope = create_scope(c->scope, heap_allocator()); + scope->flags |= ScopeFlag_Proc; + + Type *params = alloc_type_tuple(); + Type *results = alloc_type_tuple(); + if (param_types.count != 0) { + array_init(¶ms->Tuple.variables, heap_allocator(), param_types.count); + for_array(i, param_types) { + params->Tuple.variables[i] = alloc_entity_param(scope, blank_token, param_types[i], false, true); + } + } + if (return_type != nullptr) { + array_init(&results->Tuple.variables, heap_allocator(), 1); + results->Tuple.variables[0] = alloc_entity_param(scope, blank_token, return_type, false, true); + } + + + Type *pt = alloc_type_proc(scope, params, param_types.count, results, return_type != nullptr ? 1 : 0, false, ProcCC_InlineAsm); + o->type = pt; + o->mode = Addressing_Value; + o->expr = node; + return Expr_Expr; + case_end; + case Ast_TypeidType: case Ast_PolyType: case Ast_ProcType: @@ -10539,6 +10586,37 @@ gbString write_expr_to_string(gbString str, Ast *node) { str = gb_string_appendc(str, "" ); str = write_expr_to_string(str, rt->type); case_end; + + + case_ast_node(ia, InlineAsmExpr, node); + str = gb_string_appendc(str, "asm("); + for_array(i, ia->param_types) { + if (i > 0) { + str = gb_string_appendc(str, ", "); + } + str = write_expr_to_string(str, ia->param_types[i]); + } + str = gb_string_appendc(str, ")"); + if (ia->return_type != nullptr) { + str = gb_string_appendc(str, " -> "); + str = write_expr_to_string(str, ia->return_type); + } + if (ia->has_side_effects) { + str = gb_string_appendc(str, " #side_effects"); + } + if (ia->is_align_stack) { + str = gb_string_appendc(str, " #stack_align"); + } + if (ia->dialect) { + str = gb_string_appendc(str, " #"); + str = gb_string_appendc(str, inline_asm_dialect_strings[ia->dialect]); + } + str = gb_string_appendc(str, " {"); + str = write_expr_to_string(str, ia->asm_string); + str = gb_string_appendc(str, ", "); + str = write_expr_to_string(str, ia->constraints_string); + str = gb_string_appendc(str, "}"); + case_end; } return str; diff --git a/src/check_type.cpp b/src/check_type.cpp index 4d1359848..93040e493 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2209,7 +2209,7 @@ Type *type_to_abi_compat_param_type(gbAllocator a, Type *original_type, ProcCall return new_type; } - if (cc == ProcCC_None || cc == ProcCC_PureNone) { + if (cc == ProcCC_None || cc == ProcCC_PureNone || cc == ProcCC_InlineAsm) { return new_type; } @@ -2328,10 +2328,15 @@ Type *type_to_abi_compat_result_type(gbAllocator a, Type *original_type, ProcCal Type *single_type = reduce_tuple_to_single_type(original_type); + if (cc == ProcCC_InlineAsm) { + return new_type; + } + if (is_type_simd_vector(single_type)) { return new_type; } + if (build_context.ODIN_OS == "windows") { if (build_context.ODIN_ARCH == "amd64") { if (is_type_integer_128bit(single_type)) { @@ -2401,7 +2406,7 @@ bool abi_compat_return_by_pointer(gbAllocator a, ProcCallingConvention cc, Type if (abi_return_type == nullptr) { return false; } - if (cc == ProcCC_None || cc == ProcCC_PureNone) { + if (cc == ProcCC_None || cc == ProcCC_PureNone || cc == ProcCC_InlineAsm) { return false; } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 65586d3a6..4abc65ab4 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -9563,6 +9563,43 @@ lbValue lb_build_expr(lbProcedure *p, Ast *expr) { case_ast_node(ie, IndexExpr, expr); return lb_addr_load(p, lb_build_addr(p, expr)); case_end; + + case_ast_node(ia, InlineAsmExpr, expr); + Type *t = type_of_expr(expr); + GB_ASSERT(is_type_asm_proc(t)); + + String asm_string = {}; + String constraints_string = {}; + + TypeAndValue tav; + tav = type_and_value_of_expr(ia->asm_string); + GB_ASSERT(is_type_string(tav.type)); + GB_ASSERT(tav.value.kind == ExactValue_String); + asm_string = tav.value.value_string; + + tav = type_and_value_of_expr(ia->constraints_string); + GB_ASSERT(is_type_string(tav.type)); + GB_ASSERT(tav.value.kind == ExactValue_String); + constraints_string = tav.value.value_string; + + + LLVMInlineAsmDialect dialect = LLVMInlineAsmDialectATT; + switch (ia->dialect) { + case InlineAsmDialect_Default: dialect = LLVMInlineAsmDialectATT; break; + case InlineAsmDialect_ATT: dialect = LLVMInlineAsmDialectATT; break; + case InlineAsmDialect_Intel: dialect = LLVMInlineAsmDialectIntel; break; + default: GB_PANIC("Unhandled inline asm dialect"); break; + } + + LLVMTypeRef func_type = LLVMGetElementType(lb_type(p->module, t)); + LLVMValueRef the_asm = LLVMGetInlineAsm(func_type, + cast(char *)asm_string.text, cast(size_t)asm_string.len, + cast(char *)constraints_string.text, cast(size_t)constraints_string.len, + ia->has_side_effects, ia->is_align_stack, dialect + ); + GB_ASSERT(the_asm != nullptr); + return {the_asm, t}; + case_end; } GB_PANIC("lb_build_expr: %.*s", LIT(ast_strings[expr->kind])); diff --git a/src/parser.cpp b/src/parser.cpp index de5c48d20..330d78263 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -45,6 +45,7 @@ Token ast_token(Ast *node) { case Ast_TypeAssertion: return ast_token(node->TypeAssertion.expr); case Ast_TypeCast: return node->TypeCast.token; case Ast_AutoCast: return node->AutoCast.token; + case Ast_InlineAsmExpr: return node->InlineAsmExpr.token; case Ast_BadStmt: return node->BadStmt.begin; case Ast_EmptyStmt: return node->EmptyStmt.token; @@ -232,6 +233,13 @@ Ast *clone_ast(Ast *node) { n->AutoCast.expr = clone_ast(n->AutoCast.expr); break; + case Ast_InlineAsmExpr: + n->InlineAsmExpr.param_types = clone_ast_array(n->InlineAsmExpr.param_types); + n->InlineAsmExpr.return_type = clone_ast(n->InlineAsmExpr.return_type); + n->InlineAsmExpr.asm_string = clone_ast(n->InlineAsmExpr.asm_string); + n->InlineAsmExpr.constraints_string = clone_ast(n->InlineAsmExpr.constraints_string); + break; + case Ast_BadStmt: break; case Ast_EmptyStmt: break; case Ast_ExprStmt: @@ -715,6 +723,28 @@ Ast *ast_auto_cast(AstFile *f, Token token, Ast *expr) { return result; } +Ast *ast_inline_asm_expr(AstFile *f, Token token, Token open, Token close, + Array const ¶m_types, + Ast *return_type, + Ast *asm_string, + Ast *constraints_string, + bool has_side_effects, + bool is_align_stack, + InlineAsmDialectKind dialect) { + + Ast *result = alloc_ast_node(f, Ast_InlineAsmExpr); + result->InlineAsmExpr.token = token; + result->InlineAsmExpr.open = open; + result->InlineAsmExpr.close = close; + result->InlineAsmExpr.param_types = param_types; + result->InlineAsmExpr.return_type = return_type; + result->InlineAsmExpr.asm_string = asm_string; + result->InlineAsmExpr.constraints_string = constraints_string; + result->InlineAsmExpr.has_side_effects = has_side_effects; + result->InlineAsmExpr.is_align_stack = is_align_stack; + result->InlineAsmExpr.dialect = dialect; + return result; +} @@ -2316,6 +2346,80 @@ Ast *parse_operand(AstFile *f, bool lhs) { return ast_bit_set_type(f, token, elem, underlying); } + case Token_asm: { + Token token = expect_token(f, Token_asm); + + Array param_types = {}; + Ast *return_type = nullptr; + if (allow_token(f, Token_OpenParen)) { + param_types = array_make(heap_allocator()); + while (f->curr_token.kind != Token_CloseParen && f->curr_token.kind != Token_EOF) { + Ast *t = parse_type(f); + array_add(¶m_types, t); + if (f->curr_token.kind != Token_Comma || + f->curr_token.kind == Token_EOF) { + break; + } + advance_token(f); + } + expect_token(f, Token_CloseParen); + + if (allow_token(f, Token_ArrowRight)) { + return_type = parse_type(f); + } + } + + bool has_side_effects = false; + bool is_align_stack = false; + InlineAsmDialectKind dialect = InlineAsmDialect_Default; + + while (f->curr_token.kind == Token_Hash) { + advance_token(f); + if (f->curr_token.kind == Token_Ident) { + Token token = advance_token(f); + String name = token.string; + if (name == "side_effects") { + if (has_side_effects) { + syntax_error(token, "Duplicate directive on inline asm expression: '#side_effects'"); + } + has_side_effects = true; + } else if (name == "align_stack") { + if (is_align_stack) { + syntax_error(token, "Duplicate directive on inline asm expression: '#align_stack'"); + } + is_align_stack = true; + } else if (name == "att") { + if (dialect == InlineAsmDialect_ATT) { + syntax_error(token, "Duplicate directive on inline asm expression: '#att'"); + } else if (dialect != InlineAsmDialect_Default) { + syntax_error(token, "Conflicting asm dialects"); + } else { + dialect = InlineAsmDialect_ATT; + } + } else if (name == "intel") { + if (dialect == InlineAsmDialect_Intel) { + syntax_error(token, "Duplicate directive on inline asm expression: '#intel'"); + } else if (dialect != InlineAsmDialect_Default) { + syntax_error(token, "Conflicting asm dialects"); + } else { + dialect = InlineAsmDialect_Intel; + } + } + } else { + syntax_error(f->curr_token, "Expected an identifier after hash"); + } + } + + Token open = expect_token(f, Token_OpenBrace); + Ast *asm_string = parse_expr(f, false); + expect_token(f, Token_Comma); + Ast *constraints_string = parse_expr(f, false); + allow_token(f, Token_Comma); + Token close = expect_token(f, Token_CloseBrace); + + return ast_inline_asm_expr(f, token, open, close, param_types, return_type, asm_string, constraints_string, has_side_effects, is_align_stack, dialect); + } + default: { #if 0 Ast *type = parse_type_or_ident(f); @@ -4142,6 +4246,7 @@ Ast *parse_stmt(AstFile *f) { case Token_String: case Token_OpenParen: case Token_Pointer: + case Token_asm: // Inline assembly // Unary Operators case Token_Add: case Token_Sub: diff --git a/src/parser.hpp b/src/parser.hpp index ff47d1887..8e210876f 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -209,6 +209,8 @@ enum ProcCallingConvention { ProcCC_None = 7, ProcCC_PureNone = 8, + ProcCC_InlineAsm = 9, + ProcCC_MAX, @@ -250,6 +252,20 @@ enum StmtAllowFlag { StmtAllowFlag_Label = 1<<1, }; +enum InlineAsmDialectKind : u8 { + InlineAsmDialect_Default, // ATT is default + InlineAsmDialect_ATT, + InlineAsmDialect_Intel, + + InlineAsmDialect_COUNT, +}; + +char const *inline_asm_dialect_strings[InlineAsmDialect_COUNT] = { + "", + "att", + "intel", +}; + #define AST_KINDS \ AST_KIND(Ident, "identifier", struct { \ Token token; \ @@ -323,6 +339,17 @@ AST_KIND(_ExprBegin, "", bool) \ AST_KIND(TypeAssertion, "type assertion", struct { Ast *expr; Token dot; Ast *type; Type *type_hint; }) \ AST_KIND(TypeCast, "type cast", struct { Token token; Ast *type, *expr; }) \ AST_KIND(AutoCast, "auto_cast", struct { Token token; Ast *expr; }) \ + AST_KIND(InlineAsmExpr, "inline asm expression", struct { \ + Token token; \ + Token open, close; \ + Array param_types; \ + Ast *return_type; \ + Ast *asm_string; \ + Ast *constraints_string; \ + bool has_side_effects; \ + bool is_align_stack; \ + InlineAsmDialectKind dialect; \ + }) \ AST_KIND(_ExprEnd, "", bool) \ AST_KIND(_StmtBegin, "", bool) \ AST_KIND(BadStmt, "bad statement", struct { Token begin, end; }) \ @@ -337,10 +364,6 @@ AST_KIND(_StmtBegin, "", bool) \ Token op; \ Array lhs, rhs; \ }) \ - AST_KIND(IncDecStmt, "increment decrement statement", struct { \ - Token op; \ - Ast *expr; \ - }) \ AST_KIND(_ComplexStmtBegin, "", bool) \ AST_KIND(BlockStmt, "block statement", struct { \ Array stmts; \ diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 947e3e7a0..d89ec43b5 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -117,6 +117,7 @@ TOKEN_KIND(Token__KeywordBegin, ""), \ TOKEN_KIND(Token_inline, "inline"), \ TOKEN_KIND(Token_no_inline, "no_inline"), \ TOKEN_KIND(Token_context, "context"), \ + TOKEN_KIND(Token_asm, "asm"), \ TOKEN_KIND(Token_macro, "macro"), \ TOKEN_KIND(Token_const, "const"), \ TOKEN_KIND(Token__KeywordEnd, ""), \ diff --git a/src/types.cpp b/src/types.cpp index 03e071045..fc4544385 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1210,6 +1210,10 @@ bool is_type_proc(Type *t) { t = base_type(t); return t->kind == Type_Proc; } +bool is_type_asm_proc(Type *t) { + t = base_type(t); + return t->kind == Type_Proc && t->Proc.calling_convention == ProcCC_InlineAsm; +} bool is_type_poly_proc(Type *t) { t = base_type(t); return t->kind == Type_Proc && t->Proc.is_polymorphic;