Crash fixes and small improvements to inline asm. (#1756)

* codegen: LLVMConstInlineAsm is deprecated.

* codegen: replace commas in asm constraint strings by pipes as required by LLVM.

* ir: enforce usage of '=' constraint modifier for inline assembly outputs.

Others are not currently supported and this was just asserted alter in `ir_render_asm`.

* asm: forbid comptime_int/floats as inputs in favor of explicitely sized constants.

Fixes a crash due to comptime_int/floats having no type_ref.

* asm: handle inputs with integers of <8 or non power of 2 bitsize.

We widen them to the next highest power of two.
This commit is contained in:
Sahnvour 2018-11-19 22:22:21 +01:00 committed by Andrew Kelley
parent 89e82281be
commit 703c6684d1
6 changed files with 121 additions and 6 deletions

View File

@ -181,5 +181,15 @@ static inline Slice<uint8_t> buf_to_slice(Buf *buf) {
return Slice<uint8_t>{reinterpret_cast<uint8_t*>(buf_ptr(buf)), buf_len(buf)};
}
static inline void buf_replace(Buf* buf, char from, char to) {
const size_t count = buf_len(buf);
char* ptr = buf_ptr(buf);
for (size_t i = 0; i < count; ++i) {
char& l = ptr[i];
if (l == from)
l = to;
}
}
#endif

View File

@ -3660,6 +3660,13 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru
AsmOutput *asm_output = asm_expr->output_list.at(i);
bool is_return = (asm_output->return_type != nullptr);
assert(*buf_ptr(asm_output->constraint) == '=');
// LLVM uses commas internally to separate different constraints,
// alternative constraints are achieved with pipes.
// We still allow the user to use commas in a way that is similar
// to GCC's inline assembly.
// http://llvm.org/docs/LangRef.html#constraint-codes
buf_replace(asm_output->constraint, ',', '|');
if (is_return) {
buf_appendf(&constraint_buf, "=%s", buf_ptr(asm_output->constraint) + 1);
} else {
@ -3679,14 +3686,30 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru
}
for (size_t i = 0; i < asm_expr->input_list.length; i += 1, total_index += 1, param_index += 1) {
AsmInput *asm_input = asm_expr->input_list.at(i);
buf_replace(asm_input->constraint, ',', '|');
IrInstruction *ir_input = instruction->input_list[i];
buf_append_buf(&constraint_buf, asm_input->constraint);
if (total_index + 1 < total_constraint_count) {
buf_append_char(&constraint_buf, ',');
}
param_types[param_index] = ir_input->value.type->type_ref;
param_values[param_index] = ir_llvm_value(g, ir_input);
ZigType *const type = ir_input->value.type;
LLVMTypeRef type_ref = type->type_ref;
LLVMValueRef value_ref = ir_llvm_value(g, ir_input);
// Handle integers of non pot bitsize by widening them.
if (type->id == ZigTypeIdInt) {
const size_t bitsize = type->data.integral.bit_count;
if (bitsize < 8 || !is_power_of_2(bitsize)) {
const bool is_signed = type->data.integral.is_signed;
const size_t wider_bitsize = bitsize < 8 ? 8 : round_to_next_power_of_2(bitsize);
ZigType *const wider_type = get_int_type(g, is_signed, wider_bitsize);
type_ref = wider_type->type_ref;
value_ref = gen_widen_or_shorten(g, false, type, wider_type, value_ref);
}
}
param_types[param_index] = type_ref;
param_values[param_index] = value_ref;
}
for (size_t i = 0; i < asm_expr->clobber_list.length; i += 1, total_index += 1) {
Buf *clobber_buf = asm_expr->clobber_list.at(i);
@ -3705,8 +3728,8 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru
LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, (unsigned)input_and_output_count, false);
bool is_volatile = asm_expr->is_volatile || (asm_expr->output_list.length == 0);
LLVMValueRef asm_fn = LLVMConstInlineAsm(function_type, buf_ptr(&llvm_template),
buf_ptr(&constraint_buf), is_volatile, false);
LLVMValueRef asm_fn = LLVMGetInlineAsm(function_type, buf_ptr(&llvm_template), buf_len(&llvm_template),
buf_ptr(&constraint_buf), buf_len(&constraint_buf), is_volatile, false, LLVMInlineAsmDialectATT);
return LLVMBuildCall(g->builder, asm_fn, param_values, (unsigned)input_and_output_count, "");
}

View File

@ -5537,6 +5537,15 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod
return irb->codegen->invalid_instruction;
}
}
const char modifier = *buf_ptr(asm_output->constraint);
if (modifier != '=') {
add_node_error(irb->codegen, node,
buf_sprintf("invalid modifier starting output constraint for '%s': '%c', only '=' is supported."
" Compiler TODO: see https://github.com/ziglang/zig/issues/215",
buf_ptr(asm_output->asm_symbolic_name), modifier));
return irb->codegen->invalid_instruction;
}
}
for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1) {
AsmInput *asm_input = node->data.asm_expr.input_list.at(i);
@ -15386,9 +15395,19 @@ static IrInstruction *ir_analyze_instruction_asm(IrAnalyze *ira, IrInstructionAs
}
for (size_t i = 0; i < asm_expr->input_list.length; i += 1) {
input_list[i] = asm_instruction->input_list[i]->child;
if (type_is_invalid(input_list[i]->value.type))
IrInstruction *const input_value = asm_instruction->input_list[i]->child;
if (type_is_invalid(input_value->value.type))
return ira->codegen->invalid_instruction;
if (instr_is_comptime(input_value) &&
(input_value->value.type->id == ZigTypeIdComptimeInt ||
input_value->value.type->id == ZigTypeIdComptimeFloat)) {
ir_add_error_node(ira, input_value->source_node,
buf_sprintf("expected sized integer or sized float, found %s", buf_ptr(&input_value->value.type->name)));
return ira->codegen->invalid_instruction;
}
input_list[i] = input_value;
}
IrInstruction *result = ir_build_asm(&ira->new_irb,

View File

@ -158,6 +158,17 @@ static inline bool is_power_of_2(uint64_t x) {
return x != 0 && ((x & (~x + 1)) == x);
}
static inline uint64_t round_to_next_power_of_2(uint64_t x) {
--x;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}
uint32_t int_hash(int i);
bool int_eq(int a, int b);
uint32_t uint64_hash(uint64_t i);

View File

@ -17,6 +17,30 @@ test "module level assembly" {
}
}
test "output constraint modifiers" {
// This is only testing compilation.
var a: u32 = 3;
asm volatile ("" : [_]"=m,r"(a) : : "");
asm volatile ("" : [_]"=r,m"(a) : : "");
}
test "alternative constraints" {
// Make sure we allow commas as a separator for alternative constraints.
var a: u32 = 3;
asm volatile ("" : [_]"=r,m"(a) : [_]"r,m"(a) : "");
}
test "sized integer/float in asm input" {
asm volatile ("" : : [_]"m"(usize(3)) : "");
asm volatile ("" : : [_]"m"(i15(-3)) : "");
asm volatile ("" : : [_]"m"(u3(3)) : "");
asm volatile ("" : : [_]"m"(i3(3)) : "");
asm volatile ("" : : [_]"m"(u121(3)) : "");
asm volatile ("" : : [_]"m"(i121(3)) : "");
asm volatile ("" : : [_]"m"(f32(3.17)) : "");
asm volatile ("" : : [_]"m"(f64(3.17)) : "");
}
extern fn aoeu() i32;
export fn derp() i32 {

View File

@ -5232,4 +5232,32 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
,
".tmp_source.zig:3:36: error: @ArgType could not resolve the type of arg 0 because 'fn(var)var' is generic",
);
cases.add(
"unsupported modifier at start of asm output constraint",
\\export fn foo() void {
\\ var bar: u32 = 3;
\\ asm volatile ("" : [baz]"+r"(bar) : : "");
\\}
,
".tmp_source.zig:3:5: error: invalid modifier starting output constraint for 'baz': '+', only '=' is supported. Compiler TODO: see https://github.com/ziglang/zig/issues/215",
);
cases.add(
"comptime_int in asm input",
\\export fn foo() void {
\\ asm volatile ("" : : [bar]"r"(3) : "");
\\}
,
".tmp_source.zig:2:35: error: expected sized integer or sized float, found comptime_int",
);
cases.add(
"comptime_float in asm input",
\\export fn foo() void {
\\ asm volatile ("" : : [bar]"r"(3.17) : "");
\\}
,
".tmp_source.zig:2:35: error: expected sized integer or sized float, found comptime_float",
);
}