authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-05-18 02:45:21+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-05-20 17:30:21+02:00
log6e3770e970dbf460271a0e0cb60c2bf40a7c861e
treefa880846415c07b0d153688a1e1a7b8c35971671
parent7077e90b3f8991c844deb08a16ad3f4e0569398f
signaturelock-open Commit is signed but in an unrecognized format.

spirv: implement pointer comparison in for air cmp

It turns out that the Khronos LLVM SPIRV translator does not support OpPtrEqual. Therefore, this instruction is emitted using a series of conversions. This commit breaks intToEnum, because enum was removed from the arithmetic type info. The enum should be converted to an int before this function is called.

4 files changed, 127 insertions(+), 87 deletions(-)

src/codegen/spirv.zig+114-61
......@@ -369,17 +369,6 @@ pub const DeclGen = struct {
369369 .composite_integer,
370370 };
371371 },
372 .Enum => blk: {
373 var buffer: Type.Payload.Bits = undefined;
374 const int_ty = ty.intTagType(&buffer);
375 const int_info = int_ty.intInfo(target);
376 break :blk ArithmeticTypeInfo{
377 .bits = int_info.bits,
378 .is_vector = false,
379 .signedness = int_info.signedness,
380 .class = .integer,
381 };
382 },
383372 // As of yet, there is no vector support in the self-hosted compiler.
384373 .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}),
385374 // TODO: For which types is this the case?
......@@ -1742,12 +1731,12 @@ pub const DeclGen = struct {
17421731 .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
17431732 .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
17441733
1745 .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual),
1746 .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual),
1747 .cmp_gt => try self.airCmp(inst, .OpFOrdGreaterThan, .OpSGreaterThan, .OpUGreaterThan),
1748 .cmp_gte => try self.airCmp(inst, .OpFOrdGreaterThanEqual, .OpSGreaterThanEqual, .OpUGreaterThanEqual),
1749 .cmp_lt => try self.airCmp(inst, .OpFOrdLessThan, .OpSLessThan, .OpULessThan),
1750 .cmp_lte => try self.airCmp(inst, .OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual),
1734 .cmp_eq => try self.airCmp(inst, .eq),
1735 .cmp_neq => try self.airCmp(inst, .neq),
1736 .cmp_gt => try self.airCmp(inst, .gt),
1737 .cmp_gte => try self.airCmp(inst, .gte),
1738 .cmp_lt => try self.airCmp(inst, .lt),
1739 .cmp_lte => try self.airCmp(inst, .lte),
17511740
17521741 .arg => self.airArg(),
17531742 .alloc => try self.airAlloc(inst),
......@@ -2039,58 +2028,122 @@ pub const DeclGen = struct {
20392028 return result_id;
20402029 }
20412030
2042 fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !?IdRef {
2043 if (self.liveness.isUnused(inst)) return null;
2044 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2045 var lhs_id = try self.resolve(bin_op.lhs);
2046 var rhs_id = try self.resolve(bin_op.rhs);
2047 const result_id = self.spv.allocId();
2048 const result_type_id = try self.resolveTypeId(Type.bool);
2049 const op_ty = self.air.typeOf(bin_op.lhs);
2050 assert(op_ty.eql(self.air.typeOf(bin_op.rhs), self.module));
2031 fn cmp(
2032 self: *DeclGen,
2033 comptime op: std.math.CompareOperator,
2034 bool_ty_id: IdRef,
2035 ty: Type,
2036 lhs_id: IdRef,
2037 rhs_id: IdRef,
2038 ) !IdRef {
2039 var cmp_lhs_id = lhs_id;
2040 var cmp_rhs_id = rhs_id;
2041 const opcode: Opcode = opcode: {
2042 var int_buffer: Type.Payload.Bits = undefined;
2043 const op_ty = switch (ty.zigTypeTag()) {
2044 .Int, .Bool, .Float => ty,
2045 .Enum => ty.intTagType(&int_buffer),
2046 .ErrorSet => Type.u16,
2047 .Pointer => blk: {
2048 // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are
2049 // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using
2050 // OpConvertPtrToU...
2051 cmp_lhs_id = self.spv.allocId();
2052 cmp_rhs_id = self.spv.allocId();
2053
2054 const usize_ty_id = self.typeId(try self.sizeType());
2055
2056 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
2057 .id_result_type = usize_ty_id,
2058 .id_result = cmp_lhs_id,
2059 .pointer = lhs_id,
2060 });
20512061
2052 // Comparisons are generally applicable to both scalar and vector operations in SPIR-V,
2053 // but int and float versions of operations require different opcodes.
2054 const info = try self.arithmeticTypeInfo(op_ty);
2062 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
2063 .id_result_type = usize_ty_id,
2064 .id_result = cmp_rhs_id,
2065 .pointer = rhs_id,
2066 });
20552067
2056 const opcode_index: usize = switch (info.class) {
2057 .composite_integer => {
2058 return self.todo("binary operations for composite integers", .{});
2059 },
2060 .float => 0,
2061 .bool => 1,
2062 .strange_integer => blk: {
2063 const op_ty_ref = try self.resolveType(op_ty, .direct);
2064 lhs_id = try self.maskStrangeInt(op_ty_ref, lhs_id, info.bits);
2065 rhs_id = try self.maskStrangeInt(op_ty_ref, rhs_id, info.bits);
2066 break :blk switch (info.signedness) {
2067 .signed => @as(usize, 1),
2068 .unsigned => @as(usize, 2),
2069 };
2070 },
2071 .integer => switch (info.signedness) {
2072 .signed => @as(usize, 1),
2073 .unsigned => @as(usize, 2),
2074 },
2075 };
2068 break :blk Type.usize;
2069 },
2070 .Optional => unreachable, // TODO
2071 else => unreachable,
2072 };
20762073
2077 const operands = .{
2078 .id_result_type = result_type_id,
2079 .id_result = result_id,
2080 .operand_1 = lhs_id,
2081 .operand_2 = rhs_id,
2082 };
2074 const info = try self.arithmeticTypeInfo(op_ty);
2075 const signedness = switch (info.class) {
2076 .composite_integer => {
2077 return self.todo("binary operations for composite integers", .{});
2078 },
2079 .float => break :opcode switch (op) {
2080 .eq => .OpFOrdEqual,
2081 .neq => .OpFOrdNotEqual,
2082 .lt => .OpFOrdLessThan,
2083 .lte => .OpFOrdLessThanEqual,
2084 .gt => .OpFOrdGreaterThan,
2085 .gte => .OpFOrdGreaterThanEqual,
2086 },
2087 .bool => break :opcode switch (op) {
2088 .eq => .OpIEqual,
2089 .neq => .OpINotEqual,
2090 else => unreachable,
2091 },
2092 .strange_integer => sign: {
2093 const op_ty_ref = try self.resolveType(op_ty, .direct);
2094 // Mask operands before performing comparison.
2095 cmp_lhs_id = try self.maskStrangeInt(op_ty_ref, cmp_lhs_id, info.bits);
2096 cmp_rhs_id = try self.maskStrangeInt(op_ty_ref, cmp_rhs_id, info.bits);
2097 break :sign info.signedness;
2098 },
2099 .integer => info.signedness,
2100 };
20832101
2084 switch (opcode_index) {
2085 0 => try self.func.body.emit(self.spv.gpa, fop, operands),
2086 1 => try self.func.body.emit(self.spv.gpa, sop, operands),
2087 2 => try self.func.body.emit(self.spv.gpa, uop, operands),
2088 else => unreachable,
2089 }
2102 break :opcode switch (signedness) {
2103 .unsigned => switch (op) {
2104 .eq => .OpIEqual,
2105 .neq => .OpINotEqual,
2106 .lt => .OpULessThan,
2107 .lte => .OpULessThanEqual,
2108 .gt => .OpUGreaterThan,
2109 .gte => .OpUGreaterThanEqual,
2110 },
2111 .signed => switch (op) {
2112 .eq => .OpIEqual,
2113 .neq => .OpINotEqual,
2114 .lt => .OpSLessThan,
2115 .lte => .OpSLessThanEqual,
2116 .gt => .OpSGreaterThan,
2117 .gte => .OpSGreaterThanEqual,
2118 },
2119 };
2120 };
20902121
2122 const result_id = self.spv.allocId();
2123 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
2124 self.func.body.writeOperand(spec.IdResultType, bool_ty_id);
2125 self.func.body.writeOperand(spec.IdResult, result_id);
2126 self.func.body.writeOperand(spec.IdResultType, cmp_lhs_id);
2127 self.func.body.writeOperand(spec.IdResultType, cmp_rhs_id);
20912128 return result_id;
20922129 }
20932130
2131 fn airCmp(
2132 self: *DeclGen,
2133 inst: Air.Inst.Index,
2134 comptime op: std.math.CompareOperator,
2135 ) !?IdRef {
2136 if (self.liveness.isUnused(inst)) return null;
2137 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2138 const lhs_id = try self.resolve(bin_op.lhs);
2139 const rhs_id = try self.resolve(bin_op.rhs);
2140 const bool_ty_id = try self.resolveTypeId(Type.bool);
2141 const ty = self.air.typeOf(bin_op.lhs);
2142 assert(ty.eql(self.air.typeOf(bin_op.rhs), self.module));
2143
2144 return try self.cmp(op, bool_ty_id, ty, lhs_id, rhs_id);
2145 }
2146
20942147 fn bitcast(self: *DeclGen, target_type_id: IdResultType, value_id: IdRef) !IdRef {
20952148 const result_id = self.spv.allocId();
20962149 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
test/behavior/basic.zig-17
......@@ -134,21 +134,18 @@ fn first4KeysOfHomeRow() []const u8 {
134134
135135test "return string from function" {
136136 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
137 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
138137
139138 try expect(mem.eql(u8, first4KeysOfHomeRow(), "aoeu"));
140139}
141140
142141test "hex escape" {
143142 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
144 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
145143
146144 try expect(mem.eql(u8, "\x68\x65\x6c\x6c\x6f", "hello"));
147145}
148146
149147test "multiline string" {
150148 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
151 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
152149
153150 const s1 =
154151 \\one
......@@ -161,7 +158,6 @@ test "multiline string" {
161158
162159test "multiline string comments at start" {
163160 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
164 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
165161
166162 const s1 =
167163 //\\one
......@@ -174,7 +170,6 @@ test "multiline string comments at start" {
174170
175171test "multiline string comments at end" {
176172 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
177 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
178173
179174 const s1 =
180175 \\one
......@@ -187,7 +182,6 @@ test "multiline string comments at end" {
187182
188183test "multiline string comments in middle" {
189184 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
190 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
191185
192186 const s1 =
193187 \\one
......@@ -200,7 +194,6 @@ test "multiline string comments in middle" {
200194
201195test "multiline string comments at multiple places" {
202196 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
203 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
204197
205198 const s1 =
206199 \\one
......@@ -214,14 +207,11 @@ test "multiline string comments at multiple places" {
214207}
215208
216209test "string concatenation simple" {
217 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
218
219210 try expect(mem.eql(u8, "OK" ++ " IT " ++ "WORKED", "OK IT WORKED"));
220211}
221212
222213test "array mult operator" {
223214 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
224 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
225215
226216 try expect(mem.eql(u8, "ab" ** 5, "ababababab"));
227217}
......@@ -387,7 +377,6 @@ test "take address of parameter" {
387377 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
388378 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
389379 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
390 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
391380
392381 try testTakeAddressOfParameter(12.34);
393382}
......@@ -690,8 +679,6 @@ test "explicit cast optional pointers" {
690679}
691680
692681test "pointer comparison" {
693 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
694
695682 const a = @as([]const u8, "a");
696683 const b = &a;
697684 try expect(ptrEql(b, b));
......@@ -892,8 +879,6 @@ test "catch in block has correct result location" {
892879}
893880
894881test "labeled block with runtime branch forwards its result location type to break statements" {
895 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
896
897882 const E = enum { a, b };
898883 var a = false;
899884 const e: E = blk: {
......@@ -1062,8 +1047,6 @@ test "switch inside @as gets correct type" {
10621047}
10631048
10641049test "inline call of function with a switch inside the return statement" {
1065 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1066
10671050 const S = struct {
10681051 inline fn foo(x: anytype) @TypeOf(x) {
10691052 return switch (x) {
test/behavior/enum.zig+2
......@@ -20,6 +20,8 @@ test "enum to int" {
2020}
2121
2222fn testIntToEnumEval(x: i32) !void {
23 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
24
2325 try expect(@intToEnum(IntToEnumNumber, x) == IntToEnumNumber.Three);
2426}
2527const IntToEnumNumber = enum { Zero, One, Two, Three, Four };
test/behavior/memcpy.zig+11-9
......@@ -67,14 +67,16 @@ fn testMemcpyDestManyPtr() !void {
6767}
6868
6969comptime {
70 const S = struct {
71 buffer: [8]u8 = undefined,
72 fn set(self: *@This(), items: []const u8) void {
73 @memcpy(self.buffer[0..items.len], items);
74 }
75 };
70 if (builtin.zig_backend != .stage2_spirv64) {
71 const S = struct {
72 buffer: [8]u8 = undefined,
73 fn set(self: *@This(), items: []const u8) void {
74 @memcpy(self.buffer[0..items.len], items);
75 }
76 };
7677
77 var s = S{};
78 s.set("hello");
79 if (!std.mem.eql(u8, s.buffer[0..5], "hello")) @compileError("bad");
78 var s = S{};
79 s.set("hello");
80 if (!std.mem.eql(u8, s.buffer[0..5], "hello")) @compileError("bad");
81 }
8082}