| author | |
| committer | |
| log | 703c6684d103f14193411b589eaa0e0b1e1189f0 |
| tree | 3c19fddb4174bb5ed104e6dbac573533821956cd |
| parent | 89e82281be73701f5a634770366e9dca5a0e89a9 |
* 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.
6 files changed, 121 insertions(+), 6 deletions(-)
src/buffer.hpp+10| ... | @@ -181,5 +181,15 @@ static inline Slice<uint8_t> buf_to_slice(Buf *buf) { | ... | @@ -181,5 +181,15 @@ static inline Slice<uint8_t> buf_to_slice(Buf *buf) { |
| 181 | return Slice<uint8_t>{reinterpret_cast<uint8_t*>(buf_ptr(buf)), buf_len(buf)}; | 181 | return Slice<uint8_t>{reinterpret_cast<uint8_t*>(buf_ptr(buf)), buf_len(buf)}; |
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | static inline void buf_replace(Buf* buf, char from, char to) { | ||
| 185 | const size_t count = buf_len(buf); | ||
| 186 | char* ptr = buf_ptr(buf); | ||
| 187 | for (size_t i = 0; i < count; ++i) { | ||
| 188 | char& l = ptr[i]; | ||
| 189 | if (l == from) | ||
| 190 | l = to; | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 184 | 194 | ||
| 185 | #endif | 195 | #endif |
src/codegen.cpp+27-4| ... | @@ -3660,6 +3660,13 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru | ... | @@ -3660,6 +3660,13 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru |
| 3660 | AsmOutput *asm_output = asm_expr->output_list.at(i); | 3660 | AsmOutput *asm_output = asm_expr->output_list.at(i); |
| 3661 | bool is_return = (asm_output->return_type != nullptr); | 3661 | bool is_return = (asm_output->return_type != nullptr); |
| 3662 | assert(*buf_ptr(asm_output->constraint) == '='); | 3662 | assert(*buf_ptr(asm_output->constraint) == '='); |
| 3663 | // LLVM uses commas internally to separate different constraints, | ||
| 3664 | // alternative constraints are achieved with pipes. | ||
| 3665 | // We still allow the user to use commas in a way that is similar | ||
| 3666 | // to GCC's inline assembly. | ||
| 3667 | // http://llvm.org/docs/LangRef.html#constraint-codes | ||
| 3668 | buf_replace(asm_output->constraint, ',', '|'); | ||
| 3669 | |||
| 3663 | if (is_return) { | 3670 | if (is_return) { |
| 3664 | buf_appendf(&constraint_buf, "=%s", buf_ptr(asm_output->constraint) + 1); | 3671 | buf_appendf(&constraint_buf, "=%s", buf_ptr(asm_output->constraint) + 1); |
| 3665 | } else { | 3672 | } else { |
| ... | @@ -3679,14 +3686,30 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru | ... | @@ -3679,14 +3686,30 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru |
| 3679 | } | 3686 | } |
| 3680 | for (size_t i = 0; i < asm_expr->input_list.length; i += 1, total_index += 1, param_index += 1) { | 3687 | for (size_t i = 0; i < asm_expr->input_list.length; i += 1, total_index += 1, param_index += 1) { |
| 3681 | AsmInput *asm_input = asm_expr->input_list.at(i); | 3688 | AsmInput *asm_input = asm_expr->input_list.at(i); |
| 3689 | buf_replace(asm_input->constraint, ',', '|'); | ||
| 3682 | IrInstruction *ir_input = instruction->input_list[i]; | 3690 | IrInstruction *ir_input = instruction->input_list[i]; |
| 3683 | buf_append_buf(&constraint_buf, asm_input->constraint); | 3691 | buf_append_buf(&constraint_buf, asm_input->constraint); |
| 3684 | if (total_index + 1 < total_constraint_count) { | 3692 | if (total_index + 1 < total_constraint_count) { |
| 3685 | buf_append_char(&constraint_buf, ','); | 3693 | buf_append_char(&constraint_buf, ','); |
| 3686 | } | 3694 | } |
| 3687 | 3695 | ||
| 3688 | param_types[param_index] = ir_input->value.type->type_ref; | 3696 | ZigType *const type = ir_input->value.type; |
| 3689 | param_values[param_index] = ir_llvm_value(g, ir_input); | 3697 | LLVMTypeRef type_ref = type->type_ref; |
| 3698 | LLVMValueRef value_ref = ir_llvm_value(g, ir_input); | ||
| 3699 | // Handle integers of non pot bitsize by widening them. | ||
| 3700 | if (type->id == ZigTypeIdInt) { | ||
| 3701 | const size_t bitsize = type->data.integral.bit_count; | ||
| 3702 | if (bitsize < 8 || !is_power_of_2(bitsize)) { | ||
| 3703 | const bool is_signed = type->data.integral.is_signed; | ||
| 3704 | const size_t wider_bitsize = bitsize < 8 ? 8 : round_to_next_power_of_2(bitsize); | ||
| 3705 | ZigType *const wider_type = get_int_type(g, is_signed, wider_bitsize); | ||
| 3706 | type_ref = wider_type->type_ref; | ||
| 3707 | value_ref = gen_widen_or_shorten(g, false, type, wider_type, value_ref); | ||
| 3708 | } | ||
| 3709 | } | ||
| 3710 | |||
| 3711 | param_types[param_index] = type_ref; | ||
| 3712 | param_values[param_index] = value_ref; | ||
| 3690 | } | 3713 | } |
| 3691 | for (size_t i = 0; i < asm_expr->clobber_list.length; i += 1, total_index += 1) { | 3714 | for (size_t i = 0; i < asm_expr->clobber_list.length; i += 1, total_index += 1) { |
| 3692 | Buf *clobber_buf = asm_expr->clobber_list.at(i); | 3715 | Buf *clobber_buf = asm_expr->clobber_list.at(i); |
| ... | @@ -3705,8 +3728,8 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru | ... | @@ -3705,8 +3728,8 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru |
| 3705 | LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, (unsigned)input_and_output_count, false); | 3728 | LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, (unsigned)input_and_output_count, false); |
| 3706 | 3729 | ||
| 3707 | bool is_volatile = asm_expr->is_volatile || (asm_expr->output_list.length == 0); | 3730 | bool is_volatile = asm_expr->is_volatile || (asm_expr->output_list.length == 0); |
| 3708 | LLVMValueRef asm_fn = LLVMConstInlineAsm(function_type, buf_ptr(&llvm_template), | 3731 | LLVMValueRef asm_fn = LLVMGetInlineAsm(function_type, buf_ptr(&llvm_template), buf_len(&llvm_template), |
| 3709 | buf_ptr(&constraint_buf), is_volatile, false); | 3732 | buf_ptr(&constraint_buf), buf_len(&constraint_buf), is_volatile, false, LLVMInlineAsmDialectATT); |
| 3710 | 3733 | ||
| 3711 | return LLVMBuildCall(g->builder, asm_fn, param_values, (unsigned)input_and_output_count, ""); | 3734 | return LLVMBuildCall(g->builder, asm_fn, param_values, (unsigned)input_and_output_count, ""); |
| 3712 | } | 3735 | } |
src/ir.cpp+21-2| ... | @@ -5537,6 +5537,15 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod | ... | @@ -5537,6 +5537,15 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod |
| 5537 | return irb->codegen->invalid_instruction; | 5537 | return irb->codegen->invalid_instruction; |
| 5538 | } | 5538 | } |
| 5539 | } | 5539 | } |
| 5540 | |||
| 5541 | const char modifier = *buf_ptr(asm_output->constraint); | ||
| 5542 | if (modifier != '=') { | ||
| 5543 | add_node_error(irb->codegen, node, | ||
| 5544 | buf_sprintf("invalid modifier starting output constraint for '%s': '%c', only '=' is supported." | ||
| 5545 | " Compiler TODO: see https://github.com/ziglang/zig/issues/215", | ||
| 5546 | buf_ptr(asm_output->asm_symbolic_name), modifier)); | ||
| 5547 | return irb->codegen->invalid_instruction; | ||
| 5548 | } | ||
| 5540 | } | 5549 | } |
| 5541 | for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1) { | 5550 | for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1) { |
| 5542 | AsmInput *asm_input = node->data.asm_expr.input_list.at(i); | 5551 | AsmInput *asm_input = node->data.asm_expr.input_list.at(i); |
| ... | @@ -15386,9 +15395,19 @@ static IrInstruction *ir_analyze_instruction_asm(IrAnalyze *ira, IrInstructionAs | ... | @@ -15386,9 +15395,19 @@ static IrInstruction *ir_analyze_instruction_asm(IrAnalyze *ira, IrInstructionAs |
| 15386 | } | 15395 | } |
| 15387 | 15396 | ||
| 15388 | for (size_t i = 0; i < asm_expr->input_list.length; i += 1) { | 15397 | for (size_t i = 0; i < asm_expr->input_list.length; i += 1) { |
| 15389 | input_list[i] = asm_instruction->input_list[i]->child; | 15398 | IrInstruction *const input_value = asm_instruction->input_list[i]->child; |
| 15390 | if (type_is_invalid(input_list[i]->value.type)) | 15399 | if (type_is_invalid(input_value->value.type)) |
| 15391 | return ira->codegen->invalid_instruction; | 15400 | return ira->codegen->invalid_instruction; |
| 15401 | |||
| 15402 | if (instr_is_comptime(input_value) && | ||
| 15403 | (input_value->value.type->id == ZigTypeIdComptimeInt || | ||
| 15404 | input_value->value.type->id == ZigTypeIdComptimeFloat)) { | ||
| 15405 | ir_add_error_node(ira, input_value->source_node, | ||
| 15406 | buf_sprintf("expected sized integer or sized float, found %s", buf_ptr(&input_value->value.type->name))); | ||
| 15407 | return ira->codegen->invalid_instruction; | ||
| 15408 | } | ||
| 15409 | |||
| 15410 | input_list[i] = input_value; | ||
| 15392 | } | 15411 | } |
| 15393 | 15412 | ||
| 15394 | IrInstruction *result = ir_build_asm(&ira->new_irb, | 15413 | IrInstruction *result = ir_build_asm(&ira->new_irb, |
src/util.hpp+11| ... | @@ -158,6 +158,17 @@ static inline bool is_power_of_2(uint64_t x) { | ... | @@ -158,6 +158,17 @@ static inline bool is_power_of_2(uint64_t x) { |
| 158 | return x != 0 && ((x & (~x + 1)) == x); | 158 | return x != 0 && ((x & (~x + 1)) == x); |
| 159 | } | 159 | } |
| 160 | 160 | ||
| 161 | static inline uint64_t round_to_next_power_of_2(uint64_t x) { | ||
| 162 | --x; | ||
| 163 | x |= x >> 1; | ||
| 164 | x |= x >> 2; | ||
| 165 | x |= x >> 4; | ||
| 166 | x |= x >> 8; | ||
| 167 | x |= x >> 16; | ||
| 168 | x |= x >> 32; | ||
| 169 | return x + 1; | ||
| 170 | } | ||
| 171 | |||
| 161 | uint32_t int_hash(int i); | 172 | uint32_t int_hash(int i); |
| 162 | bool int_eq(int a, int b); | 173 | bool int_eq(int a, int b); |
| 163 | uint32_t uint64_hash(uint64_t i); | 174 | uint32_t uint64_hash(uint64_t i); |
test/cases/asm.zig+24| ... | @@ -17,6 +17,30 @@ test "module level assembly" { | ... | @@ -17,6 +17,30 @@ test "module level assembly" { |
| 17 | } | 17 | } |
| 18 | } | 18 | } |
| 19 | 19 | ||
| 20 | test "output constraint modifiers" { | ||
| 21 | // This is only testing compilation. | ||
| 22 | var a: u32 = 3; | ||
| 23 | asm volatile ("" : [_]"=m,r"(a) : : ""); | ||
| 24 | asm volatile ("" : [_]"=r,m"(a) : : ""); | ||
| 25 | } | ||
| 26 | |||
| 27 | test "alternative constraints" { | ||
| 28 | // Make sure we allow commas as a separator for alternative constraints. | ||
| 29 | var a: u32 = 3; | ||
| 30 | asm volatile ("" : [_]"=r,m"(a) : [_]"r,m"(a) : ""); | ||
| 31 | } | ||
| 32 | |||
| 33 | test "sized integer/float in asm input" { | ||
| 34 | asm volatile ("" : : [_]"m"(usize(3)) : ""); | ||
| 35 | asm volatile ("" : : [_]"m"(i15(-3)) : ""); | ||
| 36 | asm volatile ("" : : [_]"m"(u3(3)) : ""); | ||
| 37 | asm volatile ("" : : [_]"m"(i3(3)) : ""); | ||
| 38 | asm volatile ("" : : [_]"m"(u121(3)) : ""); | ||
| 39 | asm volatile ("" : : [_]"m"(i121(3)) : ""); | ||
| 40 | asm volatile ("" : : [_]"m"(f32(3.17)) : ""); | ||
| 41 | asm volatile ("" : : [_]"m"(f64(3.17)) : ""); | ||
| 42 | } | ||
| 43 | |||
| 20 | extern fn aoeu() i32; | 44 | extern fn aoeu() i32; |
| 21 | 45 | ||
| 22 | export fn derp() i32 { | 46 | export fn derp() i32 { |
test/compile_errors.zig+28| ... | @@ -5232,4 +5232,32 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { | ... | @@ -5232,4 +5232,32 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 5232 | , | 5232 | , |
| 5233 | ".tmp_source.zig:3:36: error: @ArgType could not resolve the type of arg 0 because 'fn(var)var' is generic", | 5233 | ".tmp_source.zig:3:36: error: @ArgType could not resolve the type of arg 0 because 'fn(var)var' is generic", |
| 5234 | ); | 5234 | ); |
| 5235 | |||
| 5236 | cases.add( | ||
| 5237 | "unsupported modifier at start of asm output constraint", | ||
| 5238 | \\export fn foo() void { | ||
| 5239 | \\ var bar: u32 = 3; | ||
| 5240 | \\ asm volatile ("" : [baz]"+r"(bar) : : ""); | ||
| 5241 | \\} | ||
| 5242 | , | ||
| 5243 | ".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", | ||
| 5244 | ); | ||
| 5245 | |||
| 5246 | cases.add( | ||
| 5247 | "comptime_int in asm input", | ||
| 5248 | \\export fn foo() void { | ||
| 5249 | \\ asm volatile ("" : : [bar]"r"(3) : ""); | ||
| 5250 | \\} | ||
| 5251 | , | ||
| 5252 | ".tmp_source.zig:2:35: error: expected sized integer or sized float, found comptime_int", | ||
| 5253 | ); | ||
| 5254 | |||
| 5255 | cases.add( | ||
| 5256 | "comptime_float in asm input", | ||
| 5257 | \\export fn foo() void { | ||
| 5258 | \\ asm volatile ("" : : [bar]"r"(3.17) : ""); | ||
| 5259 | \\} | ||
| 5260 | , | ||
| 5261 | ".tmp_source.zig:2:35: error: expected sized integer or sized float, found comptime_float", | ||
| 5262 | ); | ||
| 5235 | } | 5263 | } |