authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2023-07-29 04:26:45-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-07-29 09:49:04-07:00
logdc8a80a191611c81fe100f5df6669f6b0e622848
treeb6967b26fc1fc6da4c6f2861561f364311ee7286
parentb8dda2dbe1d6685e8d190cf0608eedf507819e6c

llvm: support read-write output constraints in assembly

Closes #15227

3 files changed, 80 insertions(+), 15 deletions(-)

src/Air.zig+8-1
...@@ -1783,7 +1783,14 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1783,7 +1783,14 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1783 .work_group_id,1783 .work_group_id,
1784 => false,1784 => false,
17851785
1786 .assembly => @as(u1, @truncate(air.extraData(Air.Asm, data.ty_pl.payload).data.flags >> 31)) != 0,1786 .assembly => {
1787 var extra = air.extraData(Air.Asm, data.ty_pl.payload);
1788 const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
1789 return is_volatile or if (extra.data.outputs_len == 1)
1790 @as(Air.Inst.Ref, @enumFromInt(air.extra[extra.end])) != .none
1791 else
1792 extra.data.outputs_len > 1;
1793 },
1787 .load => air.typeOf(data.ty_op.operand, ip).isVolatilePtrIp(ip),1794 .load => air.typeOf(data.ty_op.operand, ip).isVolatilePtrIp(ip),
1788 .slice_elem_val, .ptr_elem_val => air.typeOf(data.bin_op.lhs, ip).isVolatilePtrIp(ip),1795 .slice_elem_val, .ptr_elem_val => air.typeOf(data.bin_op.lhs, ip).isVolatilePtrIp(ip),
1789 .atomic_load => air.typeOf(data.atomic_load.ptr, ip).isVolatilePtrIp(ip),1796 .atomic_load => air.typeOf(data.atomic_load.ptr, ip).isVolatilePtrIp(ip),
src/codegen/llvm.zig+57-13
...@@ -6776,8 +6776,9 @@ pub const FuncGen = struct {...@@ -6776,8 +6776,9 @@ pub const FuncGen = struct {
6776 const max_return_count = outputs.len;6776 const max_return_count = outputs.len;
6777 const llvm_ret_types = try arena.alloc(Builder.Type, max_return_count);6777 const llvm_ret_types = try arena.alloc(Builder.Type, max_return_count);
6778 const llvm_ret_indirect = try arena.alloc(bool, max_return_count);6778 const llvm_ret_indirect = try arena.alloc(bool, max_return_count);
6779 const llvm_rw_vals = try arena.alloc(Builder.Value, max_return_count);
67796780
6780 const max_param_count = inputs.len + outputs.len;6781 const max_param_count = max_return_count + inputs.len + outputs.len;
6781 const llvm_param_types = try arena.alloc(Builder.Type, max_param_count);6782 const llvm_param_types = try arena.alloc(Builder.Type, max_param_count);
6782 const llvm_param_values = try arena.alloc(Builder.Value, max_param_count);6783 const llvm_param_values = try arena.alloc(Builder.Value, max_param_count);
6783 // This stores whether we need to add an elementtype attribute and6784 // This stores whether we need to add an elementtype attribute and
...@@ -6793,7 +6794,8 @@ pub const FuncGen = struct {...@@ -6793,7 +6794,8 @@ pub const FuncGen = struct {
6793 var name_map: std.StringArrayHashMapUnmanaged(u16) = .{};6794 var name_map: std.StringArrayHashMapUnmanaged(u16) = .{};
6794 try name_map.ensureUnusedCapacity(arena, max_param_count);6795 try name_map.ensureUnusedCapacity(arena, max_param_count);
67956796
6796 for (outputs, 0..) |output, i| {6797 var rw_extra_i = extra_i;
6798 for (outputs, llvm_ret_indirect, llvm_rw_vals) |output, *is_indirect, *llvm_rw_val| {
6797 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);6799 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
6798 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);6800 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
6799 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);6801 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
...@@ -6808,14 +6810,22 @@ pub const FuncGen = struct {...@@ -6808,14 +6810,22 @@ pub const FuncGen = struct {
6808 llvm_constraints.appendAssumeCapacity('=');6810 llvm_constraints.appendAssumeCapacity('=');
68096811
6810 // Pass any non-return outputs indirectly, if the constraint accepts a memory location6812 // Pass any non-return outputs indirectly, if the constraint accepts a memory location
6811 llvm_ret_indirect[i] = (output != .none) and constraintAllowsMemory(constraint);6813 is_indirect.* = (output != .none) and constraintAllowsMemory(constraint);
6812 if (output != .none) {6814 if (output != .none) {
6813 const output_inst = try self.resolveInst(output);6815 const output_inst = try self.resolveInst(output);
6814 const output_ty = self.typeOf(output);6816 const output_ty = self.typeOf(output);
6815 assert(output_ty.zigTypeTag(mod) == .Pointer);6817 assert(output_ty.zigTypeTag(mod) == .Pointer);
6816 const elem_llvm_ty = try o.lowerPtrElemTy(output_ty.childType(mod));6818 const elem_llvm_ty = try o.lowerPtrElemTy(output_ty.childType(mod));
68176819
6818 if (llvm_ret_indirect[i]) {6820 switch (constraint[0]) {
6821 '=' => {},
6822 '+' => llvm_rw_val.* = output_inst,
6823 else => return self.todo("unsupported output constraint on output type '{c}'", .{
6824 constraint[0],
6825 }),
6826 }
6827
6828 if (is_indirect.*) {
6819 // Pass the result by reference as an indirect output (e.g. "=*m")6829 // Pass the result by reference as an indirect output (e.g. "=*m")
6820 llvm_constraints.appendAssumeCapacity('*');6830 llvm_constraints.appendAssumeCapacity('*');
68216831
...@@ -6829,6 +6839,13 @@ pub const FuncGen = struct {...@@ -6829,6 +6839,13 @@ pub const FuncGen = struct {
6829 llvm_ret_i += 1;6839 llvm_ret_i += 1;
6830 }6840 }
6831 } else {6841 } else {
6842 switch (constraint[0]) {
6843 '=' => {},
6844 else => return self.todo("unsupported output constraint on result type '{c}'", .{
6845 constraint[0],
6846 }),
6847 }
6848
6832 const ret_ty = self.typeOfIndex(inst);6849 const ret_ty = self.typeOfIndex(inst);
6833 llvm_ret_types[llvm_ret_i] = try o.lowerType(ret_ty);6850 llvm_ret_types[llvm_ret_i] = try o.lowerType(ret_ty);
6834 llvm_ret_i += 1;6851 llvm_ret_i += 1;
...@@ -6865,9 +6882,8 @@ pub const FuncGen = struct {...@@ -6865,9 +6882,8 @@ pub const FuncGen = struct {
68656882
6866 const arg_llvm_value = try self.resolveInst(input);6883 const arg_llvm_value = try self.resolveInst(input);
6867 const arg_ty = self.typeOf(input);6884 const arg_ty = self.typeOf(input);
6868 var llvm_elem_ty: Builder.Type = .none;6885 const is_by_ref = isByRef(arg_ty, mod);
6869 if (isByRef(arg_ty, mod)) {6886 if (is_by_ref) {
6870 llvm_elem_ty = try o.lowerPtrElemTy(arg_ty);
6871 if (constraintAllowsMemory(constraint)) {6887 if (constraintAllowsMemory(constraint)) {
6872 llvm_param_values[llvm_param_i] = arg_llvm_value;6888 llvm_param_values[llvm_param_i] = arg_llvm_value;
6873 llvm_param_types[llvm_param_i] = arg_llvm_value.typeOfWip(&self.wip);6889 llvm_param_types[llvm_param_i] = arg_llvm_value.typeOfWip(&self.wip);
...@@ -6911,15 +6927,43 @@ pub const FuncGen = struct {...@@ -6911,15 +6927,43 @@ pub const FuncGen = struct {
69116927
6912 // In the case of indirect inputs, LLVM requires the callsite to have6928 // In the case of indirect inputs, LLVM requires the callsite to have
6913 // an elementtype(<ty>) attribute.6929 // an elementtype(<ty>) attribute.
6914 if (constraint[0] == '*') {6930 llvm_param_attrs[llvm_param_i] = if (constraint[0] == '*')
6915 llvm_param_attrs[llvm_param_i] = if (llvm_elem_ty != .none)6931 try o.lowerPtrElemTy(if (is_by_ref) arg_ty else arg_ty.childType(mod))
6916 llvm_elem_ty6932 else
6917 else6933 .none;
6918 try o.lowerPtrElemTy(arg_ty.childType(mod));6934
6935 llvm_param_i += 1;
6936 total_i += 1;
6937 }
6938
6939 for (outputs, llvm_ret_indirect, llvm_rw_vals, 0..) |output, is_indirect, llvm_rw_val, output_index| {
6940 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[rw_extra_i..]);
6941 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[rw_extra_i..]), 0);
6942 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
6943 // This equation accounts for the fact that even if we have exactly 4 bytes
6944 // for the string, we still use the next u32 for the null terminator.
6945 rw_extra_i += (constraint.len + name.len + (2 + 3)) / 4;
6946
6947 if (constraint[0] != '+') continue;
6948
6949 const rw_ty = self.typeOf(output);
6950 const llvm_elem_ty = try o.lowerPtrElemTy(rw_ty.childType(mod));
6951 if (is_indirect) {
6952 llvm_param_values[llvm_param_i] = llvm_rw_val;
6953 llvm_param_types[llvm_param_i] = llvm_rw_val.typeOfWip(&self.wip);
6919 } else {6954 } else {
6920 llvm_param_attrs[llvm_param_i] = .none;6955 const alignment = Builder.Alignment.fromByteUnits(rw_ty.abiAlignment(mod));
6956 const loaded = try self.wip.load(.normal, llvm_elem_ty, llvm_rw_val, alignment, "");
6957 llvm_param_values[llvm_param_i] = loaded;
6958 llvm_param_types[llvm_param_i] = llvm_elem_ty;
6921 }6959 }
69226960
6961 try llvm_constraints.writer(self.gpa).print(",{d}", .{output_index});
6962
6963 // In the case of indirect inputs, LLVM requires the callsite to have
6964 // an elementtype(<ty>) attribute.
6965 llvm_param_attrs[llvm_param_i] = if (is_indirect) llvm_elem_ty else .none;
6966
6923 llvm_param_i += 1;6967 llvm_param_i += 1;
6924 total_i += 1;6968 total_i += 1;
6925 }6969 }
test/behavior/asm.zig+15-1
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const expect = std.testing.expect;3const expect = std.testing.expect;
4const expectEqual = std.testing.expectEqual;
45
5const is_x86_64_linux = builtin.cpu.arch == .x86_64 and builtin.os.tag == .linux;6const is_x86_64_linux = builtin.cpu.arch == .x86_64 and builtin.os.tag == .linux;
67
...@@ -163,6 +164,19 @@ export fn derp() i32 {...@@ -163,6 +164,19 @@ export fn derp() i32 {
163 return 1234;164 return 1234;
164}165}
165166
167test "rw constraint (x86_64)" {
168 if (builtin.target.cpu.arch != .x86_64 or builtin.zig_backend != .stage2_llvm)
169 return error.SkipZigTest;
170
171 var res: i32 = 5;
172 asm ("addl %[b], %[a]"
173 : [a] "+r" (res),
174 : [b] "r" (@as(i32, 13)),
175 : "flags"
176 );
177 try expectEqual(@as(i32, 18), res);
178}
179
166test "asm modifiers (AArch64)" {180test "asm modifiers (AArch64)" {
167 if (builtin.target.cpu.arch != .aarch64) return error.SkipZigTest;181 if (builtin.target.cpu.arch != .aarch64) return error.SkipZigTest;
168 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO182 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
...@@ -174,5 +188,5 @@ test "asm modifiers (AArch64)" {...@@ -174,5 +188,5 @@ test "asm modifiers (AArch64)" {
174 : [ret] "=r" (-> u32),188 : [ret] "=r" (-> u32),
175 : [in] "r" (x),189 : [in] "r" (x),
176 );190 );
177 try expect(double == 2 * x);191 try expectEqual(2 * x, double);
178}192}