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 {...}`
This commit is contained in:
gingerBill
2020-10-24 16:32:37 +01:00
parent 0061e63db0
commit 4629754f7c
8 changed files with 271 additions and 14 deletions

View File

@@ -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));

View File

@@ -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<Type *>(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(&params->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;

View File

@@ -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;
}

View File

@@ -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]));

View File

@@ -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<Ast *> const &param_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<Ast *> param_types = {};
Ast *return_type = nullptr;
if (allow_token(f, Token_OpenParen)) {
param_types = array_make<Ast *>(heap_allocator());
while (f->curr_token.kind != Token_CloseParen && f->curr_token.kind != Token_EOF) {
Ast *t = parse_type(f);
array_add(&param_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:

View File

@@ -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<Ast *> 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<Ast *> lhs, rhs; \
}) \
AST_KIND(IncDecStmt, "increment decrement statement", struct { \
Token op; \
Ast *expr; \
}) \
AST_KIND(_ComplexStmtBegin, "", bool) \
AST_KIND(BlockStmt, "block statement", struct { \
Array<Ast *> stmts; \

View File

@@ -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, ""), \

View File

@@ -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;