Browse Source

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 {...}`

gingerBill 4 years ago
parent
commit
4629754f7c
8 changed files with 271 additions and 14 deletions
  1. 5 1
      src/check_decl.cpp
  2. 85 7
      src/check_expr.cpp
  3. 7 2
      src/check_type.cpp
  4. 37 0
      src/llvm_backend.cpp
  5. 105 0
      src/parser.cpp
  6. 27 4
      src/parser.hpp
  7. 1 0
      src/tokenizer.cpp
  8. 4 0
      src/types.cpp

+ 5 - 1
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));

+ 85 - 7
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<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;

+ 7 - 2
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;
 	}
 

+ 37 - 0
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]));

+ 105 - 0
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<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:

+ 27 - 4
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<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; \

+ 1 - 0
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, ""), \

+ 4 - 0
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;