From 6e3770e970dbf460271a0e0cb60c2bf40a7c861e Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 18 May 2023 02:45:21 +0200 Subject: [PATCH 01/13] 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. --- src/codegen/spirv.zig | 183 +++++++++++++++++++++++++-------------- test/behavior/basic.zig | 17 ---- test/behavior/enum.zig | 2 + test/behavior/memcpy.zig | 20 +++-- 4 files changed, 131 insertions(+), 91 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index d1def8a02ef51974f11665180fb8c5ed12783a94..9cfac608a139da86713ab19063ad4588b51d1fe1 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -369,17 +369,6 @@ pub const DeclGen = struct { .composite_integer, }; }, - .Enum => blk: { - var buffer: Type.Payload.Bits = undefined; - const int_ty = ty.intTagType(&buffer); - const int_info = int_ty.intInfo(target); - break :blk ArithmeticTypeInfo{ - .bits = int_info.bits, - .is_vector = false, - .signedness = int_info.signedness, - .class = .integer, - }; - }, // As of yet, there is no vector support in the self-hosted compiler. .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}), // TODO: For which types is this the case? @@ -1742,12 +1731,12 @@ pub const DeclGen = struct { .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2), .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3), - .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual), - .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual), - .cmp_gt => try self.airCmp(inst, .OpFOrdGreaterThan, .OpSGreaterThan, .OpUGreaterThan), - .cmp_gte => try self.airCmp(inst, .OpFOrdGreaterThanEqual, .OpSGreaterThanEqual, .OpUGreaterThanEqual), - .cmp_lt => try self.airCmp(inst, .OpFOrdLessThan, .OpSLessThan, .OpULessThan), - .cmp_lte => try self.airCmp(inst, .OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual), + .cmp_eq => try self.airCmp(inst, .eq), + .cmp_neq => try self.airCmp(inst, .neq), + .cmp_gt => try self.airCmp(inst, .gt), + .cmp_gte => try self.airCmp(inst, .gte), + .cmp_lt => try self.airCmp(inst, .lt), + .cmp_lte => try self.airCmp(inst, .lte), .arg => self.airArg(), .alloc => try self.airAlloc(inst), @@ -2039,58 +2028,122 @@ pub const DeclGen = struct { return result_id; } - fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !?IdRef { - if (self.liveness.isUnused(inst)) return null; - const bin_op = self.air.instructions.items(.data)[inst].bin_op; - var lhs_id = try self.resolve(bin_op.lhs); - var rhs_id = try self.resolve(bin_op.rhs); + fn cmp( + self: *DeclGen, + comptime op: std.math.CompareOperator, + bool_ty_id: IdRef, + ty: Type, + lhs_id: IdRef, + rhs_id: IdRef, + ) !IdRef { + var cmp_lhs_id = lhs_id; + var cmp_rhs_id = rhs_id; + const opcode: Opcode = opcode: { + var int_buffer: Type.Payload.Bits = undefined; + const op_ty = switch (ty.zigTypeTag()) { + .Int, .Bool, .Float => ty, + .Enum => ty.intTagType(&int_buffer), + .ErrorSet => Type.u16, + .Pointer => blk: { + // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are + // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using + // OpConvertPtrToU... + cmp_lhs_id = self.spv.allocId(); + cmp_rhs_id = self.spv.allocId(); + + const usize_ty_id = self.typeId(try self.sizeType()); + + try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{ + .id_result_type = usize_ty_id, + .id_result = cmp_lhs_id, + .pointer = lhs_id, + }); + + try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{ + .id_result_type = usize_ty_id, + .id_result = cmp_rhs_id, + .pointer = rhs_id, + }); + + break :blk Type.usize; + }, + .Optional => unreachable, // TODO + else => unreachable, + }; + + const info = try self.arithmeticTypeInfo(op_ty); + const signedness = switch (info.class) { + .composite_integer => { + return self.todo("binary operations for composite integers", .{}); + }, + .float => break :opcode switch (op) { + .eq => .OpFOrdEqual, + .neq => .OpFOrdNotEqual, + .lt => .OpFOrdLessThan, + .lte => .OpFOrdLessThanEqual, + .gt => .OpFOrdGreaterThan, + .gte => .OpFOrdGreaterThanEqual, + }, + .bool => break :opcode switch (op) { + .eq => .OpIEqual, + .neq => .OpINotEqual, + else => unreachable, + }, + .strange_integer => sign: { + const op_ty_ref = try self.resolveType(op_ty, .direct); + // Mask operands before performing comparison. + cmp_lhs_id = try self.maskStrangeInt(op_ty_ref, cmp_lhs_id, info.bits); + cmp_rhs_id = try self.maskStrangeInt(op_ty_ref, cmp_rhs_id, info.bits); + break :sign info.signedness; + }, + .integer => info.signedness, + }; + + break :opcode switch (signedness) { + .unsigned => switch (op) { + .eq => .OpIEqual, + .neq => .OpINotEqual, + .lt => .OpULessThan, + .lte => .OpULessThanEqual, + .gt => .OpUGreaterThan, + .gte => .OpUGreaterThanEqual, + }, + .signed => switch (op) { + .eq => .OpIEqual, + .neq => .OpINotEqual, + .lt => .OpSLessThan, + .lte => .OpSLessThanEqual, + .gt => .OpSGreaterThan, + .gte => .OpSGreaterThanEqual, + }, + }; + }; + const result_id = self.spv.allocId(); - const result_type_id = try self.resolveTypeId(Type.bool); - const op_ty = self.air.typeOf(bin_op.lhs); - assert(op_ty.eql(self.air.typeOf(bin_op.rhs), self.module)); - - // Comparisons are generally applicable to both scalar and vector operations in SPIR-V, - // but int and float versions of operations require different opcodes. - const info = try self.arithmeticTypeInfo(op_ty); - - const opcode_index: usize = switch (info.class) { - .composite_integer => { - return self.todo("binary operations for composite integers", .{}); - }, - .float => 0, - .bool => 1, - .strange_integer => blk: { - const op_ty_ref = try self.resolveType(op_ty, .direct); - lhs_id = try self.maskStrangeInt(op_ty_ref, lhs_id, info.bits); - rhs_id = try self.maskStrangeInt(op_ty_ref, rhs_id, info.bits); - break :blk switch (info.signedness) { - .signed => @as(usize, 1), - .unsigned => @as(usize, 2), - }; - }, - .integer => switch (info.signedness) { - .signed => @as(usize, 1), - .unsigned => @as(usize, 2), - }, - }; - - const operands = .{ - .id_result_type = result_type_id, - .id_result = result_id, - .operand_1 = lhs_id, - .operand_2 = rhs_id, - }; - - switch (opcode_index) { - 0 => try self.func.body.emit(self.spv.gpa, fop, operands), - 1 => try self.func.body.emit(self.spv.gpa, sop, operands), - 2 => try self.func.body.emit(self.spv.gpa, uop, operands), - else => unreachable, - } - + try self.func.body.emitRaw(self.spv.gpa, opcode, 4); + self.func.body.writeOperand(spec.IdResultType, bool_ty_id); + self.func.body.writeOperand(spec.IdResult, result_id); + self.func.body.writeOperand(spec.IdResultType, cmp_lhs_id); + self.func.body.writeOperand(spec.IdResultType, cmp_rhs_id); return result_id; } + fn airCmp( + self: *DeclGen, + inst: Air.Inst.Index, + comptime op: std.math.CompareOperator, + ) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs_id = try self.resolve(bin_op.lhs); + const rhs_id = try self.resolve(bin_op.rhs); + const bool_ty_id = try self.resolveTypeId(Type.bool); + const ty = self.air.typeOf(bin_op.lhs); + assert(ty.eql(self.air.typeOf(bin_op.rhs), self.module)); + + return try self.cmp(op, bool_ty_id, ty, lhs_id, rhs_id); + } + fn bitcast(self: *DeclGen, target_type_id: IdResultType, value_id: IdRef) !IdRef { const result_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 0de278fc5767e3dda1c283ddf02e57cbc2799ad7..756d62f3ab32ca2efc3ed2ee167cd64dfbaf2090 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -134,21 +134,18 @@ fn first4KeysOfHomeRow() []const u8 { test "return string from function" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(mem.eql(u8, first4KeysOfHomeRow(), "aoeu")); } test "hex escape" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(mem.eql(u8, "\x68\x65\x6c\x6c\x6f", "hello")); } test "multiline string" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const s1 = \\one @@ -161,7 +158,6 @@ test "multiline string" { test "multiline string comments at start" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const s1 = //\\one @@ -174,7 +170,6 @@ test "multiline string comments at start" { test "multiline string comments at end" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const s1 = \\one @@ -187,7 +182,6 @@ test "multiline string comments at end" { test "multiline string comments in middle" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const s1 = \\one @@ -200,7 +194,6 @@ test "multiline string comments in middle" { test "multiline string comments at multiple places" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const s1 = \\one @@ -214,14 +207,11 @@ test "multiline string comments at multiple places" { } test "string concatenation simple" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try expect(mem.eql(u8, "OK" ++ " IT " ++ "WORKED", "OK IT WORKED")); } test "array mult operator" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(mem.eql(u8, "ab" ** 5, "ababababab")); } @@ -387,7 +377,6 @@ test "take address of parameter" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try testTakeAddressOfParameter(12.34); } @@ -690,8 +679,6 @@ test "explicit cast optional pointers" { } test "pointer comparison" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const a = @as([]const u8, "a"); const b = &a; try expect(ptrEql(b, b)); @@ -892,8 +879,6 @@ test "catch in block has correct result location" { } test "labeled block with runtime branch forwards its result location type to break statements" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const E = enum { a, b }; var a = false; const e: E = blk: { @@ -1062,8 +1047,6 @@ test "switch inside @as gets correct type" { } test "inline call of function with a switch inside the return statement" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const S = struct { inline fn foo(x: anytype) @TypeOf(x) { return switch (x) { diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig index 097caaad19e820787ad239b7c6cdee513520fa15..44a6026f2b6d4e7ca1b3f8537dfee39459db8c2f 100644 --- a/test/behavior/enum.zig +++ b/test/behavior/enum.zig @@ -20,6 +20,8 @@ test "enum to int" { } fn testIntToEnumEval(x: i32) !void { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + try expect(@intToEnum(IntToEnumNumber, x) == IntToEnumNumber.Three); } const IntToEnumNumber = enum { Zero, One, Two, Three, Four }; diff --git a/test/behavior/memcpy.zig b/test/behavior/memcpy.zig index af86c0911306d9893f932d465ab5d2bf7b2676fd..b7c5eb29d97585dd00d5fa6f9bb65d7fc81b1431 100644 --- a/test/behavior/memcpy.zig +++ b/test/behavior/memcpy.zig @@ -67,14 +67,16 @@ fn testMemcpyDestManyPtr() !void { } comptime { - const S = struct { - buffer: [8]u8 = undefined, - fn set(self: *@This(), items: []const u8) void { - @memcpy(self.buffer[0..items.len], items); - } - }; + if (builtin.zig_backend != .stage2_spirv64) { + const S = struct { + buffer: [8]u8 = undefined, + fn set(self: *@This(), items: []const u8) void { + @memcpy(self.buffer[0..items.len], items); + } + }; - var s = S{}; - s.set("hello"); - if (!std.mem.eql(u8, s.buffer[0..5], "hello")) @compileError("bad"); + var s = S{}; + s.set("hello"); + if (!std.mem.eql(u8, s.buffer[0..5], "hello")) @compileError("bad"); + } } -- 2.54.0 From 7d519b3383431df48a41d6b32520e5c5e3d77612 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 18 May 2023 12:27:16 +0200 Subject: [PATCH 02/13] spirv: use intInfo instead of arithmeticTypeInfo in airIntCast This ensures that we can also cast enums and error sets here. In the future this function will need to be changed to support composite and strange integers, but that is fine. --- src/codegen/spirv.zig | 13 +++++++++---- test/behavior/enum.zig | 2 -- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 9cfac608a139da86713ab19063ad4588b51d1fe1..5edafc503768f01a6af214eab1f3ed9e10f8209f 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -372,7 +372,8 @@ pub const DeclGen = struct { // As of yet, there is no vector support in the self-hosted compiler. .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}), // TODO: For which types is this the case? - else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmt(self.module)}), + // else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmt(self.module)}), + else => unreachable, }; } @@ -1712,7 +1713,7 @@ pub const DeclGen = struct { .shl => try self.airShift(inst, .OpShiftLeftLogical), .bitcast => try self.airBitcast(inst), - .intcast, .trunc => try self.airIntcast(inst), + .intcast, .trunc => try self.airIntCast(inst), .ptrtoint => try self.airPtrToInt(inst), .int_to_float => try self.airIntToFloat(inst), .float_to_int => try self.airFloatToInt(inst), @@ -2162,15 +2163,19 @@ pub const DeclGen = struct { return try self.bitcast(result_type_id, operand_id); } - fn airIntcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + fn airIntCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand_id = try self.resolve(ty_op.operand); const dest_ty = self.air.typeOfIndex(inst); - const dest_info = try self.arithmeticTypeInfo(dest_ty); const dest_ty_id = try self.resolveTypeId(dest_ty); + const target = self.getTarget(); + const dest_info = dest_ty.intInfo(target); + + // TODO: Masking? + const result_id = self.spv.allocId(); switch (dest_info.signedness) { .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{ diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig index 44a6026f2b6d4e7ca1b3f8537dfee39459db8c2f..097caaad19e820787ad239b7c6cdee513520fa15 100644 --- a/test/behavior/enum.zig +++ b/test/behavior/enum.zig @@ -20,8 +20,6 @@ test "enum to int" { } fn testIntToEnumEval(x: i32) !void { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try expect(@intToEnum(IntToEnumNumber, x) == IntToEnumNumber.Three); } const IntToEnumNumber = enum { Zero, One, Two, Three, Four }; -- 2.54.0 From 0ba0d8fecb255cf19a225e4cb160921d73f83652 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 18 May 2023 17:39:43 +0200 Subject: [PATCH 03/13] spirv: dont use OpIAddCarry This instruction is not really working well in the LLVM SPIRV translator, as it is not implemented. This commit also intruces the constructStruct helper function to initialize structs at runtime. This is ALSO buggy in the translator, and we must work around OpCompositeConstruct not working when some of the constituents are runtime-known only. Some other improvements are made: - improved variable() so that it is more useful and no longer requires the address space. It always puts values in the Function address space, and returns a pointer to the Generic address space - adds a boolToInt utility function --- src/codegen/spirv.zig | 289 +++++++++++++++++++++------------------- test/behavior/basic.zig | 7 - test/behavior/math.zig | 2 - 3 files changed, 153 insertions(+), 145 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 5edafc503768f01a6af214eab1f3ed9e10f8209f..b86b4193ab6b60bb666b1829335157b57841fca2 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -442,6 +442,43 @@ pub const DeclGen = struct { } } + /// Construct a struct at runtime. + /// result_ty_ref must be a struct type. + fn constructStruct(self: *DeclGen, result_ty_ref: SpvType.Ref, constituents: []const IdRef) !IdRef { + // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which' + // operands are not constant. + // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349 + // For now, just initialize the struct by setting the fields manually... + // TODO: Make this OpCompositeConstruct when we can + const ptr_composite_id = try self.alloc(result_ty_ref, null); + // Note: using 32-bit ints here because usize crashes the translator as well + const index_ty_ref = try self.intType(.unsigned, 32); + const spv_composite_ty = self.spv.typeRefType(result_ty_ref); + const members = spv_composite_ty.payload(.@"struct").members; + for (constituents, members, 0..) |constitent_id, member, index| { + const index_id = try self.constInt(index_ty_ref, index); + const ptr_id = self.spv.allocId(); + const ptr_member_ty_ref = try self.spv.ptrType(member.ty, .Generic, 0); + try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ + .id_result_type = self.typeId(ptr_member_ty_ref), + .id_result = ptr_id, + .base = ptr_composite_id, + .indexes = &.{index_id}, + }); + try self.func.body.emit(self.spv.gpa, .OpStore, .{ + .pointer = ptr_id, + .object = constitent_id, + }); + } + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpLoad, .{ + .id_result_type = self.typeId(result_ty_ref), + .id_result = result_id, + .pointer = ptr_composite_id, + }); + return result_id; + } + const IndirectConstantLowering = struct { const undef = 0xAA; @@ -1582,6 +1619,20 @@ pub const DeclGen = struct { } } + fn boolToInt(self: *DeclGen, result_ty_ref: SpvType.Ref, condition_id: IdRef) !IdRef { + const zero_id = try self.constInt(result_ty_ref, 0); + const one_id = try self.constInt(result_ty_ref, 1); + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpSelect, .{ + .id_result_type = self.typeId(result_ty_ref), + .id_result = result_id, + .condition = condition_id, + .object_1 = one_id, + .object_2 = zero_id, + }); + return result_id; + } + /// Convert representation from indirect (in memory) to direct (in 'register') /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct). fn convertToDirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef { @@ -1610,17 +1661,7 @@ pub const DeclGen = struct { return switch (ty.zigTypeTag()) { .Bool => blk: { const indirect_bool_ty_ref = try self.resolveType(ty, .indirect); - const zero_id = try self.constInt(indirect_bool_ty_ref, 0); - const one_id = try self.constInt(indirect_bool_ty_ref, 1); - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpSelect, .{ - .id_result_type = self.typeId(indirect_bool_ty_ref), - .id_result = result_id, - .condition = operand_id, - .object_1 = one_id, - .object_2 = zero_id, - }); - break :blk result_id; + break :blk self.boolToInt(indirect_bool_ty_ref, operand_id); }, else => operand_id, }; @@ -1933,64 +1974,83 @@ pub const DeclGen = struct { .float, .bool => unreachable, } - // The operand type must be the same as the result type in SPIR-V. + // The operand type must be the same as the result type in SPIR-V, which + // is the same as in Zig. const operand_ty_ref = try self.resolveType(operand_ty, .direct); const operand_ty_id = self.typeId(operand_ty_ref); - const op_result_id = blk: { - // Construct the SPIR-V result type. - // It is almost the same as the zig one, except that the fields must be the same type - // and they must be unsigned. - const overflow_result_ty_ref = try self.spv.simpleStructType(&.{ - .{ .ty = operand_ty_ref, .name = "res" }, - .{ .ty = operand_ty_ref, .name = "ov" }, - }); - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{ - .id_result_type = self.typeId(overflow_result_ty_ref), - .id_result = result_id, - .operand_1 = lhs, - .operand_2 = rhs, - }); - break :blk result_id; - }; + const bool_ty_ref = try self.resolveType(Type.bool, .direct); - // Now convert the SPIR-V flavor result into a Zig-flavor result. - // First, extract the two fields. - const unsigned_result = try self.extractField(operand_ty, op_result_id, 0); - const overflow = try self.extractField(operand_ty, op_result_id, 1); + const ov_ty = result_ty.tupleFields().types[1]; + // Note: result is stored in a struct, so indirect representation. + const ov_ty_ref = try self.resolveType(ov_ty, .indirect); - // We need to convert the results to the types that Zig expects here. - // The `result` is the same type except unsigned, so we can just bitcast that. - // TODO: This can be removed in Kernels as there are only unsigned ints. Maybe for - // shaders as well? - const result = try self.bitcast(operand_ty_id, unsigned_result); - - // The overflow needs to be converted into whatever is used to represent it in Zig. - const casted_overflow = blk: { - const ov_ty = result_ty.tupleFields().types[1]; - const ov_ty_id = try self.resolveTypeId(ov_ty); - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ - .id_result_type = ov_ty_id, - .id_result = result_id, - .unsigned_value = overflow, - }); - break :blk result_id; - }; - - // TODO: If copying this function for borrow, make sure to convert -1 to 1 as appropriate. - - // Finally, construct the Zig type. - // Layout is result, overflow. - const result_id = self.spv.allocId(); - const constituents = [_]IdRef{ result, casted_overflow }; - try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{ + // TODO: Operations other than addition. + const value_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpIAdd, .{ .id_result_type = operand_ty_id, - .id_result = result_id, - .constituents = &constituents, + .id_result = value_id, + .operand_1 = lhs, + .operand_2 = rhs, + }); + + const overflowed_id = switch (info.signedness) { + .unsigned => blk: { + // Overflow happened if the result is smaller than either of the operands. It doesn't matter which. + const overflowed_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpULessThan, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = overflowed_id, + .operand_1 = value_id, + .operand_2 = lhs, + }); + break :blk overflowed_id; + }, + .signed => blk: { + // Overflow happened if: + // - rhs is negative and value > lhs + // - rhs is positive and value < lhs + // This can be shortened to: + // (rhs < 0 && value > lhs) || (rhs >= 0 && value <= lhs) + // = (rhs < 0) == (value > lhs) + // Note that signed overflow is also wrapping in spir-v. + + const rhs_lt_zero_id = self.spv.allocId(); + const zero_id = try self.constInt(operand_ty_ref, 0); + try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = rhs_lt_zero_id, + .operand_1 = rhs, + .operand_2 = zero_id, + }); + + const value_gt_lhs_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpSGreaterThan, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = value_gt_lhs_id, + .operand_1 = value_id, + .operand_2 = lhs, + }); + + const overflowed_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = overflowed_id, + .operand_1 = rhs_lt_zero_id, + .operand_2 = value_gt_lhs_id, + }); + break :blk overflowed_id; + }, + }; + + // Construct the struct that Zig wants as result. + // The value should already be the correct type. + const ov_id = try self.boolToInt(ov_ty_ref, overflowed_id); + const result_ty_ref = try self.resolveType(result_ty, .direct); + return try self.constructStruct(result_ty_ref, &.{ + value_id, + ov_id, }); - return result_id; } fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -2463,76 +2523,45 @@ pub const DeclGen = struct { return result_id; } - fn variable( + // Allocate a function-local variable, with possible initializer. + // This function returns a pointer to a variable of type `ty_ref`, + // which is in the Generic address space. The variable is actually + // placed in the Function address space. + fn alloc( self: *DeclGen, - comptime context: enum { function, global }, - result_id: IdRef, - ptr_ty_ref: SpvType.Ref, + ty_ref: SpvType.Ref, initializer: ?IdRef, - ) !void { - const storage_class = self.spv.typeRefType(ptr_ty_ref).payload(.pointer).storage_class; - const actual_storage_class = switch (storage_class) { - .Generic => switch (context) { - .function => .Function, - .global => .CrossWorkgroup, - }, - else => storage_class, - }; - const actual_ptr_ty_ref = switch (storage_class) { - .Generic => try self.spv.changePtrStorageClass(ptr_ty_ref, actual_storage_class), - else => ptr_ty_ref, - }; - const alloc_result_id = switch (storage_class) { - .Generic => self.spv.allocId(), - else => result_id, - }; + ) !IdRef { + const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function, 0); + const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0); - const section = switch (actual_storage_class) { - .Generic => unreachable, - // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to - // directly generate them into func.prologue instead of the body. - .Function => &self.func.prologue, - else => &self.spv.sections.types_globals_constants, - }; - try section.emit(self.spv.gpa, .OpVariable, .{ - .id_result_type = self.typeId(actual_ptr_ty_ref), - .id_result = alloc_result_id, - .storage_class = actual_storage_class, + // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to + // directly generate them into func.prologue instead of the body. + const var_id = self.spv.allocId(); + try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{ + .id_result_type = self.typeId(fn_ptr_ty_ref), + .id_result = var_id, + .storage_class = .Function, .initializer = initializer, }); - if (storage_class != .Generic) { - return; - } - - // Now we need to convert the pointer. - // If this is a function local, we need to perform the conversion at runtime. Otherwise, we can do - // it ahead of time using OpSpecConstantOp. - switch (actual_storage_class) { - .Function => try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, - .pointer = alloc_result_id, - }), - // TODO: Can we do without this cast or move it to runtime? - else => { - const const_ptr_id = try self.makePointerConstant(section, actual_ptr_ty_ref, alloc_result_id); - try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, - .pointer = const_ptr_id, - }); - }, - } + // Convert to a generic pointer + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{ + .id_result_type = self.typeId(general_ptr_ty_ref), + .id_result = result_id, + .pointer = var_id, + }); + return result_id; } fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; - const ty = self.air.typeOfIndex(inst); - const result_ty_ref = try self.resolveType(ty, .direct); - const result_id = self.spv.allocId(); - try self.variable(.function, result_id, result_ty_ref, null); - return result_id; + const ptr_ty = self.air.typeOfIndex(inst); + assert(ptr_ty.ptrAddressSpace() == .generic); + const child_ty = ptr_ty.childType(); + const child_ty_ref = try self.resolveType(child_ty, .indirect); + return try self.alloc(child_ty_ref, null); } fn airArg(self: *DeclGen) IdRef { @@ -2819,13 +2848,7 @@ pub const DeclGen = struct { } const err_union_ty_ref = try self.resolveType(err_union_ty, .direct); - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{ - .id_result_type = self.typeId(err_union_ty_ref), - .id_result = result_id, - .constituents = members.slice(), - }); - return result_id; + return try self.constructStruct(err_union_ty_ref, members.slice()); } fn airIsNull(self: *DeclGen, inst: Air.Inst.Index, pred: enum { is_null, is_non_null }) !?IdRef { @@ -2925,14 +2948,8 @@ pub const DeclGen = struct { } const optional_ty_ref = try self.resolveType(optional_ty, .direct); - const result_id = self.spv.allocId(); const members = [_]IdRef{ operand_id, try self.constBool(true, .indirect) }; - try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{ - .id_result_type = self.typeId(optional_ty_ref), - .id_result = result_id, - .constituents = &members, - }); - return result_id; + return try self.constructStruct(optional_ty_ref, &members); } fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void { diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 756d62f3ab32ca2efc3ed2ee167cd64dfbaf2090..9fced18915192b316d0635ff00d4b3570d7fc5a4 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -82,8 +82,6 @@ test "type equality" { } test "pointer dereferencing" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - var x = @as(i32, 3); const y = &x; @@ -293,8 +291,6 @@ test "function closes over local const" { } test "volatile load and store" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - var number: i32 = 1234; const ptr = @as(*volatile i32, &number); ptr.* += 1; @@ -466,7 +462,6 @@ fn nine() u8 { test "struct inside function" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try testStructInFn(); comptime try testStructInFn(); @@ -691,7 +686,6 @@ test "string concatenation" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const a = "OK" ++ " IT " ++ "WORKED"; const b = "OK IT WORKED"; @@ -1130,7 +1124,6 @@ test "returning an opaque type from a function" { test "orelse coercion as function argument" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const Loc = struct { start: i32 = -1 }; const Container = struct { diff --git a/test/behavior/math.zig b/test/behavior/math.zig index cc85594c5026a9417e86fefc001005ac6fc3c9f5..01aec232233070ea32038f850afdecaaf3ad1fc7 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -208,7 +208,6 @@ test "float equality" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const x: f64 = 0.012; const y: f64 = x + 1.0; @@ -685,7 +684,6 @@ test "@addWithOverflow" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; { var a: u8 = 250; -- 2.54.0 From 6c055570720197af242b4c5538dd784d2e3a58f7 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 18 May 2023 18:55:15 +0200 Subject: [PATCH 04/13] spirv: fix some (Ptr)AccessChain uses The first dereference of PtrAccessChain returns a pointer of the same type as the base pointer, in contrast to AccessChain, where the first dereference returns a pointer of the dereferenced type of the base pointer. --- src/codegen/spirv.zig | 106 ++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index b86b4193ab6b60bb666b1829335157b57841fca2..d8446ea045c41a92e70b6611ea99b77626d31203 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -457,14 +457,8 @@ pub const DeclGen = struct { const members = spv_composite_ty.payload(.@"struct").members; for (constituents, members, 0..) |constitent_id, member, index| { const index_id = try self.constInt(index_ty_ref, index); - const ptr_id = self.spv.allocId(); const ptr_member_ty_ref = try self.spv.ptrType(member.ty, .Generic, 0); - try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ - .id_result_type = self.typeId(ptr_member_ty_ref), - .id_result = ptr_id, - .base = ptr_composite_id, - .indexes = &.{index_id}, - }); + const ptr_id = try self.accessChain(ptr_member_ty_ref, ptr_composite_id, &.{index_id}); try self.func.body.emit(self.spv.gpa, .OpStore, .{ .pointer = ptr_id, .object = constitent_id, @@ -2089,6 +2083,44 @@ pub const DeclGen = struct { return result_id; } + /// AccessChain is essentially PtrAccessChain with 0 as initial argument. The effective + /// difference lies in whether the resulting type of the first dereference will be the + /// same as that of the base pointer, or that of a dereferenced base pointer. AccessChain + /// is the latter and PtrAccessChain is the former. + fn accessChain( + self: *DeclGen, + result_ty_ref: SpvType.Ref, + base: IdRef, + indexes: []const IdRef, + ) !IdRef { + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ + .id_result_type = self.typeId(result_ty_ref), + .id_result = result_id, + .base = base, + .indexes = indexes, + }); + return result_id; + } + + fn ptrAccessChain( + self: *DeclGen, + result_ty_ref: SpvType.Ref, + base: IdRef, + element: IdRef, + indexes: []const IdRef, + ) !IdRef { + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{ + .id_result_type = self.typeId(result_ty_ref), + .id_result = result_id, + .base = base, + .element = element, + .indexes = indexes, + }); + return result_id; + } + fn cmp( self: *DeclGen, comptime op: std.math.CompareOperator, @@ -2353,12 +2385,12 @@ pub const DeclGen = struct { const slice = try self.resolve(bin_op.lhs); const index = try self.resolve(bin_op.rhs); - const spv_ptr_ty = try self.resolveTypeId(self.air.typeOfIndex(inst)); + const ptr_ty_ref = try self.resolveType(self.air.typeOfIndex(inst), .direct); const slice_ptr = blk: { const result_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ - .id_result_type = spv_ptr_ty, + .id_result_type = self.typeId(ptr_ty_ref), .id_result = result_id, .composite = slice, .indexes = &.{0}, @@ -2366,14 +2398,7 @@ pub const DeclGen = struct { break :blk result_id; }; - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{ - .id_result_type = spv_ptr_ty, - .id_result = result_id, - .base = slice_ptr, - .element = index, - }); - return result_id; + return try self.ptrAccessChain(ptr_ty_ref, slice_ptr, index, &.{}); } fn airSliceElemVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -2385,12 +2410,12 @@ pub const DeclGen = struct { const index = try self.resolve(bin_op.rhs); var slice_buf: Type.SlicePtrFieldTypeBuffer = undefined; - const ptr_ty_id = try self.resolveTypeId(slice_ty.slicePtrFieldType(&slice_buf)); + const ptr_ty_ref = try self.resolveType(slice_ty.slicePtrFieldType(&slice_buf), .direct); const slice_ptr = blk: { const result_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ - .id_result_type = ptr_ty_id, + .id_result_type = self.typeId(ptr_ty_ref), .id_result = result_id, .composite = slice, .indexes = &.{0}, @@ -2398,17 +2423,7 @@ pub const DeclGen = struct { break :blk result_id; }; - const elem_ptr = blk: { - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{ - .id_result_type = ptr_ty_id, - .id_result = result_id, - .base = slice_ptr, - .element = index, - }); - break :blk result_id; - }; - + const elem_ptr = try self.ptrAccessChain(ptr_ty_ref, slice_ptr, index, &.{}); return try self.load(slice_ty, elem_ptr); } @@ -2423,19 +2438,18 @@ pub const DeclGen = struct { // TODO: Make this return a null ptr or something if (!elem_ty.hasRuntimeBitsIgnoreComptime()) return null; - const result_type_id = try self.resolveTypeId(result_ty); + const result_ty_ref = try self.resolveType(result_ty, .direct); const base_ptr = try self.resolve(bin_op.lhs); const rhs = try self.resolve(bin_op.rhs); - const result_id = self.spv.allocId(); - const indexes = [_]IdRef{rhs}; - try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ - .id_result_type = result_type_id, - .id_result = result_id, - .base = base_ptr, - .indexes = &indexes, - }); - return result_id; + if (ptr_ty.isSinglePointer()) { + // Pointer-to-array. In this case, the resulting pointer is not of the same type + // as the ptr_ty, and hence we need to use accessChain. + return try self.accessChain(result_ty_ref, base_ptr, &.{rhs}); + } else { + // Resulting pointer type is the same as the ptr_ty, so use ptrAccessChain + return try self.ptrAccessChain(result_ty_ref, base_ptr, rhs, &.{}); + } } fn airStructFieldVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -2480,16 +2494,8 @@ pub const DeclGen = struct { const u32_ty_id = self.typeId(try self.intType(.unsigned, 32)); const field_index_id = self.spv.allocId(); try self.spv.emitConstant(u32_ty_id, field_index_id, .{ .uint32 = field_index }); - const result_id = self.spv.allocId(); - const result_type_id = try self.resolveTypeId(result_ptr_ty); - const indexes = [_]IdRef{field_index_id}; - try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ - .id_result_type = result_type_id, - .id_result = result_id, - .base = object_ptr, - .indexes = &indexes, - }); - return result_id; + const result_ty_ref = try self.resolveType(result_ptr_ty, .direct); + return try self.accessChain(result_ty_ref, object_ptr, &.{field_index_id}); }, }, else => unreachable, // TODO -- 2.54.0 From 3c14438a937eb5b470f7f6191d850030aa7a4a06 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 18 May 2023 19:06:48 +0200 Subject: [PATCH 05/13] spirv: use extractField more reduce some code duplication --- src/codegen/spirv.zig | 62 +++++++++++-------------------------------- 1 file changed, 15 insertions(+), 47 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index d8446ea045c41a92e70b6611ea99b77626d31203..51cfba1a28b753ba2bbca68e528e151d3b00909b 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1630,7 +1630,6 @@ pub const DeclGen = struct { /// Convert representation from indirect (in memory) to direct (in 'register') /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct). fn convertToDirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef { - // const direct_ty_ref = try self.resolveType(ty, .direct); return switch (ty.zigTypeTag()) { .Bool => blk: { const direct_bool_ty_ref = try self.resolveType(ty, .direct); @@ -2370,11 +2369,7 @@ pub const DeclGen = struct { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const field_ty = self.air.typeOfIndex(inst); const operand_id = try self.resolve(ty_op.operand); - return try self.extractField( - field_ty, - operand_id, - field, - ); + return try self.extractField(field_ty, operand_id, field); } fn airSliceElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -2382,23 +2377,14 @@ pub const DeclGen = struct { const slice_ty = self.air.typeOf(bin_op.lhs); if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; - const slice = try self.resolve(bin_op.lhs); - const index = try self.resolve(bin_op.rhs); + const slice_id = try self.resolve(bin_op.lhs); + const index_id = try self.resolve(bin_op.rhs); - const ptr_ty_ref = try self.resolveType(self.air.typeOfIndex(inst), .direct); + const ptr_ty = self.air.typeOfIndex(inst); + const ptr_ty_ref = try self.resolveType(ptr_ty, .direct); - const slice_ptr = blk: { - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, - .composite = slice, - .indexes = &.{0}, - }); - break :blk result_id; - }; - - return try self.ptrAccessChain(ptr_ty_ref, slice_ptr, index, &.{}); + const slice_ptr = try self.extractField(ptr_ty, slice_id, 0); + return try self.ptrAccessChain(ptr_ty_ref, slice_ptr, index_id, &.{}); } fn airSliceElemVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -2406,24 +2392,15 @@ pub const DeclGen = struct { const slice_ty = self.air.typeOf(bin_op.lhs); if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; - const slice = try self.resolve(bin_op.lhs); - const index = try self.resolve(bin_op.rhs); + const slice_id = try self.resolve(bin_op.lhs); + const index_id = try self.resolve(bin_op.rhs); var slice_buf: Type.SlicePtrFieldTypeBuffer = undefined; - const ptr_ty_ref = try self.resolveType(slice_ty.slicePtrFieldType(&slice_buf), .direct); + const ptr_ty = slice_ty.slicePtrFieldType(&slice_buf); + const ptr_ty_ref = try self.resolveType(ptr_ty, .direct); - const slice_ptr = blk: { - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, - .composite = slice, - .indexes = &.{0}, - }); - break :blk result_id; - }; - - const elem_ptr = try self.ptrAccessChain(ptr_ty_ref, slice_ptr, index, &.{}); + const slice_ptr = try self.extractField(ptr_ty, slice_id, 0); + const elem_ptr = try self.ptrAccessChain(ptr_ty_ref, slice_ptr, index_id, &.{}); return try self.load(slice_ty, elem_ptr); } @@ -2459,24 +2436,15 @@ pub const DeclGen = struct { const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; const struct_ty = self.air.typeOf(struct_field.struct_operand); - const object = try self.resolve(struct_field.struct_operand); + const object_id = try self.resolve(struct_field.struct_operand); const field_index = struct_field.field_index; const field_ty = struct_ty.structFieldType(field_index); - const field_ty_id = try self.resolveTypeId(field_ty); if (!field_ty.hasRuntimeBitsIgnoreComptime()) return null; assert(struct_ty.zigTypeTag() == .Struct); // Cannot do unions yet. - const result_id = self.spv.allocId(); - const indexes = [_]u32{field_index}; - try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ - .id_result_type = field_ty_id, - .id_result = result_id, - .composite = object, - .indexes = &indexes, - }); - return result_id; + return try self.extractField(field_ty, object_id, field_index); } fn structFieldPtr( -- 2.54.0 From 64f99f36a6d960a97d241c3235653c464f7ac6b2 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 18 May 2023 19:25:52 +0200 Subject: [PATCH 06/13] spirv: ptr_add Implements the ptr_add air tag for spirv. The implementation for slices is probably wrong, but there seems to be no test for this... --- src/codegen/spirv.zig | 29 +++++++++++++++++++++++++++++ test/behavior/basic.zig | 1 - 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 51cfba1a28b753ba2bbca68e528e151d3b00909b..bf75fee3fdeac3293645e5ef5ad91d953438eaa4 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1738,6 +1738,8 @@ pub const DeclGen = struct { .shuffle => try self.airShuffle(inst), + .ptr_add => try self.airPtrAdd(inst), + .bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd), .bit_or => try self.airBinOpSimple(inst, .OpBitwiseOr), .xor => try self.airBinOpSimple(inst, .OpBitwiseXor), @@ -2120,6 +2122,33 @@ pub const DeclGen = struct { return result_id; } + fn airPtrAdd(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; + const ptr_id = try self.resolve(bin_op.lhs); + const offset_id = try self.resolve(bin_op.rhs); + const ptr_ty = self.air.typeOf(bin_op.lhs); + const result_ty = self.air.typeOfIndex(inst); + const result_ty_ref = try self.resolveType(result_ty, .direct); + + switch (ptr_ty.ptrSize()) { + .One => { + // Pointer to array + // TODO: Is this correct? + return try self.accessChain(result_ty_ref, ptr_id, &.{offset_id}); + }, + .C, .Many => { + return try self.ptrAccessChain(result_ty_ref, ptr_id, offset_id, &.{}); + }, + .Slice => { + // TODO: This is probably incorrect. A slice should be returned here, though this is what llvm does. + const slice_ptr_id = try self.extractField(result_ty, ptr_id, 0); + return try self.ptrAccessChain(result_ty_ref, slice_ptr_id, offset_id, &.{}); + }, + } + } + fn cmp( self: *DeclGen, comptime op: std.math.CompareOperator, diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 9fced18915192b316d0635ff00d4b3570d7fc5a4..7f9e2ae78a24ee4f65959aceab04a7e2ecec8c67 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -751,7 +751,6 @@ fn maybe(x: bool) anyerror!?u32 { test "auto created variables have correct alignment" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn foo(str: [*]const u8) u32 { -- 2.54.0 From 4203d099be2a5fd4d7fd2a24d6e682c817cba7fa Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 18 May 2023 22:36:30 +0200 Subject: [PATCH 07/13] spirv: lower integer pointer constants --- src/codegen/spirv.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index bf75fee3fdeac3293645e5ef5ad91d953438eaa4..9ed1dd0b1ab62131df3b78fe4c94b415d8974755 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -726,6 +726,10 @@ pub const DeclGen = struct { try self.lower(ptr_ty, slice.ptr); try self.addInt(Type.usize, slice.len); }, + .null_value, .zero => try self.addNullPtr(try dg.resolveType(ty, .indirect)), + .int_u64, .one, .int_big_positive, .lazy_align, .lazy_size => { + try self.addInt(Type.usize, val); + }, else => |tag| return dg.todo("pointer value of type {s}", .{@tagName(tag)}), }, .Struct => { -- 2.54.0 From 2f28713bd7c8eefd90d59d69a0786ee3f12400e1 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 18 May 2023 22:39:28 +0200 Subject: [PATCH 08/13] spirv: pointer bitcasting --- src/codegen/spirv.zig | 39 ++++++++++++++++++++++++++++---------- test/behavior/pointers.zig | 3 --- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 9ed1dd0b1ab62131df3b78fe4c94b415d8974755..d25a346640f1fc531b9dffa0d8b5f8b959cbb659 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1752,7 +1752,7 @@ pub const DeclGen = struct { .shl => try self.airShift(inst, .OpShiftLeftLogical), - .bitcast => try self.airBitcast(inst), + .bitcast => try self.airBitCast(inst), .intcast, .trunc => try self.airIntCast(inst), .ptrtoint => try self.airPtrToInt(inst), .int_to_float => try self.airIntToFloat(inst), @@ -2269,22 +2269,41 @@ pub const DeclGen = struct { return try self.cmp(op, bool_ty_id, ty, lhs_id, rhs_id); } - fn bitcast(self: *DeclGen, target_type_id: IdResultType, value_id: IdRef) !IdRef { + fn bitCast( + self: *DeclGen, + dst_ty: Type, + src_ty: Type, + src_id: IdRef, + ) !IdRef { + const dst_ty_ref = try self.resolveType(dst_ty, .direct); const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ - .id_result_type = target_type_id, - .id_result = result_id, - .operand = value_id, - }); + + // TODO: Some more cases are missing here + // See fn bitCast in llvm.zig + + if (src_ty.zigTypeTag() == .Int and dst_ty.isPtrAtRuntime()) { + try self.func.body.emit(self.spv.gpa, .OpConvertUToPtr, .{ + .id_result_type = self.typeId(dst_ty_ref), + .id_result = result_id, + .integer_value = src_id, + }); + } else { + try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ + .id_result_type = self.typeId(dst_ty_ref), + .id_result = result_id, + .operand = src_id, + }); + } return result_id; } - fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + fn airBitCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand_id = try self.resolve(ty_op.operand); - const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst)); - return try self.bitcast(result_type_id, operand_id); + const operand_ty = self.air.typeOf(ty_op.operand); + const result_ty = self.air.typeOfIndex(inst); + return try self.bitCast(result_ty, operand_ty, operand_id); } fn airIntCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig index 26d841a1fd5f46c45a31de37769bf954eba2d199..bbfbd1feb75b83287fba6eaa70e5ac5f66f2ceb6 100644 --- a/test/behavior/pointers.zig +++ b/test/behavior/pointers.zig @@ -5,7 +5,6 @@ const expect = testing.expect; const expectError = testing.expectError; test "dereference pointer" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; comptime try testDerefPtr(); try testDerefPtr(); } @@ -53,7 +52,6 @@ fn PtrOf(comptime T: type) type { test "implicit cast single item pointer to C pointer and back" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; var y: u8 = 11; var x: [*c]u8 = &y; @@ -70,7 +68,6 @@ test "initialize const optional C pointer to null" { test "assigning integer to C pointer" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; var x: i32 = 0; var y: i32 = 1; -- 2.54.0 From 37aa343079d32db96be742c9740f343ff8d2839e Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 19 May 2023 11:23:22 +0200 Subject: [PATCH 09/13] spirv: more passing tests --- test/behavior/align.zig | 4 ---- test/behavior/array.zig | 2 -- test/behavior/atomics.zig | 2 -- test/behavior/basic.zig | 2 +- test/behavior/bitcast.zig | 1 - test/behavior/bitreverse.zig | 1 - test/behavior/call.zig | 3 --- test/behavior/cast.zig | 2 -- test/behavior/decltest.zig | 2 -- test/behavior/defer.zig | 3 --- test/behavior/duplicated_test_names.zig | 2 -- test/behavior/enum.zig | 1 - test/behavior/error.zig | 1 - test/behavior/eval.zig | 1 - test/behavior/floatop.zig | 1 - test/behavior/fn.zig | 2 -- test/behavior/fn_delegation.zig | 1 - test/behavior/fn_in_struct_in_comptime.zig | 2 -- test/behavior/for.zig | 2 -- test/behavior/generics.zig | 2 -- test/behavior/if.zig | 2 -- test/behavior/inttoptr.zig | 1 - test/behavior/maximum_minimum.zig | 2 -- test/behavior/optional.zig | 1 - test/behavior/packed-struct.zig | 1 - test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig | 1 - test/behavior/reflection.zig | 1 - test/behavior/sizeof_and_typeof.zig | 2 -- test/behavior/slice.zig | 2 -- test/behavior/struct.zig | 1 - test/behavior/switch.zig | 1 - test/behavior/this.zig | 2 -- test/behavior/threadlocal.zig | 1 - test/behavior/type.zig | 1 - test/behavior/type_info.zig | 1 - test/behavior/undefined.zig | 1 - test/behavior/union.zig | 2 -- test/behavior/var_args.zig | 2 -- test/behavior/while.zig | 1 - 39 files changed, 1 insertion(+), 62 deletions(-) diff --git a/test/behavior/align.zig b/test/behavior/align.zig index 4bd66666968faeb7cb433c7f9fecabd01982c490..bc1091ace772ed82438a23b6f78a667d864671fb 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -33,8 +33,6 @@ test "default alignment allows unspecified in type syntax" { } test "implicitly decreasing pointer alignment" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const a: u32 align(4) = 3; const b: u32 align(8) = 4; try expect(addUnaligned(&a, &b) == 7); @@ -45,8 +43,6 @@ fn addUnaligned(a: *align(1) const u32, b: *align(1) const u32) u32 { } test "@alignCast pointers" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - var x: u32 align(4) = 1; expectsOnly1(&x); try expect(x == 2); diff --git a/test/behavior/array.zig b/test/behavior/array.zig index b4754a59a478e6a3bbb32b0800a724bacdf28cc1..ed8b0216130f61f89da7918a55c5212737f82c42 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -347,7 +347,6 @@ test "read/write through global variable array of struct fields initialized via test "implicit cast single-item pointer" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try testImplicitCastSingleItemPtr(); comptime try testImplicitCastSingleItemPtr(); @@ -542,7 +541,6 @@ test "sentinel element count towards the ABI size calculation" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index d1b51207a4cfaea3a0a7525d9d48d794d34ab68b..99f08a6caeed7442c2ec1aeef0419ca042da8937 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -120,7 +120,6 @@ test "128-bit cmpxchg" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try test_u128_cmpxchg(); comptime try test_u128_cmpxchg(); @@ -313,7 +312,6 @@ test "atomicrmw with 128-bit ints" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO "ld.lld: undefined symbol: __sync_lock_test_and_set_16" on -mcpu x86_64 if (builtin.cpu.arch == .x86_64 and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 7f9e2ae78a24ee4f65959aceab04a7e2ecec8c67..3024ad58c089ff39694d02a30d3f7b661bd9942d 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -583,7 +583,7 @@ test "comptime cast fn to ptr" { } test "equality compare fn ptrs" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // Test passes but should not var a = &emptyFn; try expect(a == a); diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index 4b8c363ac2d7790cf184bdcbf1bd22a417c2955a..28eb00c6c1200ed47420da52855208d31e6558a3 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -9,7 +9,6 @@ const native_endian = builtin.target.cpu.arch.endian(); test "@bitCast iX -> uX (32, 64)" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const bit_values = [_]usize{ 32, 64 }; diff --git a/test/behavior/bitreverse.zig b/test/behavior/bitreverse.zig index 87cea942d05f45aa5ee2ea642030ca960c8f0397..8d98f65b55d1c0d95586f2bb405e1b23935d5df8 100644 --- a/test/behavior/bitreverse.zig +++ b/test/behavior/bitreverse.zig @@ -5,7 +5,6 @@ const minInt = std.math.minInt; test "@bitReverse large exotic integer" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(@bitReverse(@as(u95, 0x123456789abcdef111213141)) == 0x4146424447bd9eac8f351624); } diff --git a/test/behavior/call.zig b/test/behavior/call.zig index e7d59d7e756dfa36a1097cd7a2f0d5cf82039c03..76225d815dd33bc0c82abe7081f4136a08c27219 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -109,7 +109,6 @@ test "result location of function call argument through runtime condition and st test "function call with 40 arguments" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest(thirty_nine: i32) !void { @@ -374,8 +373,6 @@ test "Enum constructed by @Type passed as generic argument" { } test "generic function with generic function parameter" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const S = struct { fn f(comptime a: fn (anytype) anyerror!void, b: anytype) anyerror!void { try a(b); diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 847abc17981f397d821f1069f9530edd051648f2..f3a64b4b7ca9509e28e8538ee1efd1fa1ba90441 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -366,7 +366,6 @@ test "return u8 coercing into ?u32 return type" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -428,7 +427,6 @@ test "peer resolve array and const slice" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try testPeerResolveArrayConstSlice(true); comptime try testPeerResolveArrayConstSlice(true); diff --git a/test/behavior/decltest.zig b/test/behavior/decltest.zig index 7aa8fb631880731edd452d3578322509e2576d7f..b01a431e2859d14d5469650171b035b21d627e4b 100644 --- a/test/behavior/decltest.zig +++ b/test/behavior/decltest.zig @@ -5,7 +5,5 @@ pub fn the_add_function(a: u32, b: u32) u32 { } test the_add_function { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (the_add_function(1, 2) != 3) unreachable; } diff --git a/test/behavior/defer.zig b/test/behavior/defer.zig index 5aa8c2f02bb827d00a73fad9464d5837831760be..c97350cd759978cced9d6c40a20cd16b98b70669 100644 --- a/test/behavior/defer.zig +++ b/test/behavior/defer.zig @@ -23,8 +23,6 @@ fn testBreakContInDefer(x: usize) void { } test "defer and labeled break" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - var i = @as(usize, 0); blk: { @@ -58,7 +56,6 @@ test "return variable while defer expression in scope to modify it" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { diff --git a/test/behavior/duplicated_test_names.zig b/test/behavior/duplicated_test_names.zig index 81b9ebdf50ceaa21b7f8c9953bb9257bd8fa079c..52930ea31819c042a6e3077fc7fd0bacaed5bf92 100644 --- a/test/behavior/duplicated_test_names.zig +++ b/test/behavior/duplicated_test_names.zig @@ -15,7 +15,5 @@ comptime { test "thingy" {} test thingy { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (thingy(1, 2) != 3) unreachable; } diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig index 097caaad19e820787ad239b7c6cdee513520fa15..255efc50fc8bb3822e29edc7cf8790d231514d33 100644 --- a/test/behavior/enum.zig +++ b/test/behavior/enum.zig @@ -1046,7 +1046,6 @@ test "tag name with assigned enum values" { test "@tagName on enum literals" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(mem.eql(u8, @tagName(.FooBar), "FooBar")); comptime try expect(mem.eql(u8, @tagName(.FooBar), "FooBar")); diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 54d53eec4a0686ff19c929c582d89c8d923c7dcd..89ce09401083bca1f3ab267c84711368f74046b5 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -16,7 +16,6 @@ fn expectError(expected_err: anyerror, observed_err_union: anytype) !void { } test "error values" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const a = @errorToInt(error.err1); const b = @errorToInt(error.err2); try expect(a != b); diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index d22eba4fa0ebc4b374c7a06c0d887380ee28ffd8..cd913dcc9ac759e799411980199ded8fb4a4d395 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -23,7 +23,6 @@ test "static add one" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(should_be_1235 == 1235); } diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index 21fc87ff2266d033575ac6b922ad5cdbe0b41f9c..97054b70834b0f7fd53a123398327165e205fbc2 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -506,7 +506,6 @@ test "@fabs" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; comptime try testFabs(); try testFabs(); diff --git a/test/behavior/fn.zig b/test/behavior/fn.zig index c84eb48d2e6695d34cb1300ddaa8158e83b057d6..608b992eb3e742baa572df76f76b05f4fb3838d2 100644 --- a/test/behavior/fn.zig +++ b/test/behavior/fn.zig @@ -5,8 +5,6 @@ const expect = testing.expect; const expectEqual = testing.expectEqual; test "params" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try expect(testParamsAdd(22, 11) == 33); } fn testParamsAdd(a: i32, b: i32) i32 { diff --git a/test/behavior/fn_delegation.zig b/test/behavior/fn_delegation.zig index d7a2ba2125b33ed9b55d211657b662309b9f6c31..95dbfeb4b2b75489a00e6be9cc9abd868d3e174a 100644 --- a/test/behavior/fn_delegation.zig +++ b/test/behavior/fn_delegation.zig @@ -34,7 +34,6 @@ fn custom(comptime T: type, comptime num: u64) fn (T) u64 { test "fn delegation" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const foo = Foo{}; try expect(foo.one() == 11); diff --git a/test/behavior/fn_in_struct_in_comptime.zig b/test/behavior/fn_in_struct_in_comptime.zig index 427b08e6a46cfecd8fbca572a334956d59c0742a..a7d8e779cc213d705b7dd9f40684cb66431d6c94 100644 --- a/test/behavior/fn_in_struct_in_comptime.zig +++ b/test/behavior/fn_in_struct_in_comptime.zig @@ -13,8 +13,6 @@ fn get_foo() fn (*u8) usize { } test "define a function in an anonymous struct in comptime" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const foo = get_foo(); try expect(foo(@intToPtr(*u8, 12345)) == 12345); } diff --git a/test/behavior/for.zig b/test/behavior/for.zig index 8c9eeb083e161639803cc3321fd23a3ade0e211a..c99ee2edeb78146da08041aef5ecc600c0eb5ba6 100644 --- a/test/behavior/for.zig +++ b/test/behavior/for.zig @@ -22,8 +22,6 @@ test "continue in for loop" { } test "break from outer for loop" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try testBreakOuter(); comptime try testBreakOuter(); } diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index e8d064154b56c4ed82851f44ab359eee084c1977..eddca9e7b1a95d04d6fc5e7ced69de6c93cb205b 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -5,8 +5,6 @@ const expect = testing.expect; const expectEqual = testing.expectEqual; test "one param, explicit comptime" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - var x: usize = 0; x += checkSize(i32); x += checkSize(bool); diff --git a/test/behavior/if.zig b/test/behavior/if.zig index 7369435cd324e368388a9c09faa863a60df18b0a..c17f2c989163f6d6b538eef46402b5ababee09de 100644 --- a/test/behavior/if.zig +++ b/test/behavior/if.zig @@ -71,8 +71,6 @@ test "labeled break inside comptime if inside runtime if" { } test "const result loc, runtime if cond, else unreachable" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const Num = enum { One, Two }; var t = true; diff --git a/test/behavior/inttoptr.zig b/test/behavior/inttoptr.zig index a05ed83b9b522aa3643e046007762e278ab696e5..29a263b2ce87c88bef599b466aa85da30f8d7cb2 100644 --- a/test/behavior/inttoptr.zig +++ b/test/behavior/inttoptr.zig @@ -14,7 +14,6 @@ test "mutate through ptr initialized with constant intToPtr value" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; forceCompilerAnalyzeBranchHardCodedPtrDereference(false); } diff --git a/test/behavior/maximum_minimum.zig b/test/behavior/maximum_minimum.zig index db6cad221fd27971fc7f2f3fd6a7cbddde41e8e4..648d4d94936cb90d3a4f3d97087cd64a5a5f141b 100644 --- a/test/behavior/maximum_minimum.zig +++ b/test/behavior/maximum_minimum.zig @@ -129,8 +129,6 @@ test "@min/max for floats" { } test "@min/@max on lazy values" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const A = extern struct { u8_4: [4]u8 }; const B = extern struct { u8_16: [16]u8 }; const size = @max(@sizeOf(A), @sizeOf(B)); diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index e62065cf2551d9c3725bd37961ddd080e1ac2261..a813580a209a9d2cac13e62b81e3fc966771465b 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -8,7 +8,6 @@ const expectEqualStrings = std.testing.expectEqualStrings; test "passing an optional integer as a parameter" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn entry() bool { diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig index 36e37786f1166ae463624ea27248e7edacf21434..7406c22f468dab92adb756d8b193a8be6216119c 100644 --- a/test/behavior/packed-struct.zig +++ b/test/behavior/packed-struct.zig @@ -7,7 +7,6 @@ const native_endian = builtin.cpu.arch.endian(); test "flags in packed structs" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const Flags1 = packed struct { // first 8 bits diff --git a/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig b/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig index 41d7f566622a3358eb3c250ce519190203b093c4..bb6d5b1359a4c46afeb3a48ca3ba6ad88a3df117 100644 --- a/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig +++ b/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig @@ -8,7 +8,6 @@ test "reference a variable in an if after an if in the 2nd switch prong" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try foo(true, Num.Two, false, "aoeu"); try expect(!ok); diff --git a/test/behavior/reflection.zig b/test/behavior/reflection.zig index b62838f243875e371403c6e414f3fbb59550aabd..aea84bc45ad43fc7462ae81797627f39572ef016 100644 --- a/test/behavior/reflection.zig +++ b/test/behavior/reflection.zig @@ -28,7 +28,6 @@ fn dummy(a: bool, b: i32, c: f32) i32 { test "reflection: @field" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; var f = Foo{ .one = 42, diff --git a/test/behavior/sizeof_and_typeof.zig b/test/behavior/sizeof_and_typeof.zig index e01e20e5441b86c4f00d45f39e0514be8750d9d1..3f70f02fcb122b3a14eb78e4bd5e8906a485d9a9 100644 --- a/test/behavior/sizeof_and_typeof.zig +++ b/test/behavior/sizeof_and_typeof.zig @@ -140,8 +140,6 @@ test "@sizeOf(T) == 0 doesn't force resolving struct size" { } test "@TypeOf() has no runtime side effects" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const S = struct { fn foo(comptime T: type, ptr: *T) T { ptr.* += 1; diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index 3b88636dca3c1c9d0cb71e19eb5cd56fab1bb22f..dc27328e6d0365758c412c647626065eeb335643 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -201,8 +201,6 @@ test "slicing pointer by length" { const x = @intToPtr([*]i32, 0x1000)[0..0x500]; const y = x[0x100..]; test "compile time slice of pointer to hard coded address" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try expect(@ptrToInt(x) == 0x1000); try expect(x.len == 0x500); diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 659acbf56be5f3c9abf97ea6f96a80d3a0a72ef5..a4a24ef3b34188c40230fb36bcdf7f801266a2c3 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -11,7 +11,6 @@ top_level_field: i32, test "top level fields" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; var instance = @This(){ .top_level_field = 1234, diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index f66446632bc9074c386f34b13d863e6f1f16bc11..dd096f89edf3c06390b303c064e94422ed5fbc61 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -215,7 +215,6 @@ fn poll() void { test "switch on global mutable var isn't constant-folded" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; while (state < 2) { poll(); diff --git a/test/behavior/this.zig b/test/behavior/this.zig index 8fd8af92a358409f69f0396ec2c0230d95f08e10..0bc765c8a7151c564ba7c394487438d39280199f 100644 --- a/test/behavior/this.zig +++ b/test/behavior/this.zig @@ -21,8 +21,6 @@ fn add(x: i32, y: i32) i32 { } test "this refer to module call private fn" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try expect(module.add(1, 2) == 3); } diff --git a/test/behavior/threadlocal.zig b/test/behavior/threadlocal.zig index a1214fff218d3f7c446542dd7952460150f424db..57740f75ce04a1ca3b65a67cda234270413ccf57 100644 --- a/test/behavior/threadlocal.zig +++ b/test/behavior/threadlocal.zig @@ -11,7 +11,6 @@ test "thread local variable" { else => return error.SkipZigTest, }; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { threadlocal var t: i32 = 1234; diff --git a/test/behavior/type.zig b/test/behavior/type.zig index 936d9663ad3e02bbaad2dde012f7bfca7cf7dabb..2fff4b05afcf64bd8c886134c454381a87cdc769 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -491,7 +491,6 @@ test "Type.Fn" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const some_opaque = opaque {}; const some_ptr = *some_opaque; diff --git a/test/behavior/type_info.zig b/test/behavior/type_info.zig index d55688c5eec16525b6c0c7d6e413b692bd2443cf..18b65b755e25c6ce7c0f6c6d48d816893d03417a 100644 --- a/test/behavior/type_info.zig +++ b/test/behavior/type_info.zig @@ -285,7 +285,6 @@ fn testUnion() !void { test "type info: struct info" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try testStruct(); comptime try testStruct(); diff --git a/test/behavior/undefined.zig b/test/behavior/undefined.zig index e0f3e00fffce94dd247c6b7fee6555d8ac4954a2..6c99b4bbcefa39388531cd4c9448fdc2500a9300 100644 --- a/test/behavior/undefined.zig +++ b/test/behavior/undefined.zig @@ -81,7 +81,6 @@ test "assign undefined to struct with method" { test "type name of undefined" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const x = undefined; try expect(mem.eql(u8, @typeName(@TypeOf(x)), "@TypeOf(undefined)")); diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 26232159b650cd54c75cc3d48f225adc6abd52c3..213d4c75ea08a2f0f5abad550e27feed1ef2c404 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1467,8 +1467,6 @@ test "packed union in packed struct" { } test "Namespace-like union" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const DepType = enum { git, http, diff --git a/test/behavior/var_args.zig b/test/behavior/var_args.zig index adb5491f7fc43f4141a34fba8bc2c04a9f1e61bf..94e2a81a9a617f68852730f4ba972f3a7a6161ba 100644 --- a/test/behavior/var_args.zig +++ b/test/behavior/var_args.zig @@ -14,8 +14,6 @@ fn add(args: anytype) i32 { } test "add arbitrary args" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try expect(add(.{ @as(i32, 1), @as(i32, 2), @as(i32, 3), @as(i32, 4) }) == 10); try expect(add(.{@as(i32, 1234)}) == 1234); try expect(add(.{}) == 0); diff --git a/test/behavior/while.zig b/test/behavior/while.zig index a6634b672f45904864dbb242cbb31295f09b9579..8d3e3a12da71cc6f092263d9c79763358a717a2a 100644 --- a/test/behavior/while.zig +++ b/test/behavior/while.zig @@ -5,7 +5,6 @@ const assert = std.debug.assert; test "while loop" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; var i: i32 = 0; while (i < 4) { -- 2.54.0 From 091595ac3711fcbc372ffe6d002890f9dd897598 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 19 May 2023 11:28:25 +0200 Subject: [PATCH 10/13] spirv: customize module-scope asm test This test passes just fine, but the provided assembly is not valid for spir-v. This adds a custom assembly test and enables the test for spir-v --- test/behavior/asm.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/behavior/asm.zig b/test/behavior/asm.zig index 5c446e5f859e14c3291476a6a3ede3f60d6e6d81..e365936798e04e449e66d5032eeda5d96a9eedac 100644 --- a/test/behavior/asm.zig +++ b/test/behavior/asm.zig @@ -15,6 +15,10 @@ comptime { \\.type this_is_my_alias, @function; \\.set this_is_my_alias, derp; ); + } else if (builtin.zig_backend == .stage2_spirv64) { + asm ( + \\%a = OpString "hello there" + ); } } @@ -24,7 +28,6 @@ test "module level assembly" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support inline assembly -- 2.54.0 From 77b8bf2b82ffa71fc8ecf85f6780130f27dbeaf3 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 19 May 2023 13:04:53 +0200 Subject: [PATCH 11/13] spirv: ptr_sub Implments the ptr_sub air tag. The code is unified with that of ptr_add. --- src/codegen/spirv.zig | 42 ++++++++++++++++++++++++++++++-------- test/behavior/pointers.zig | 1 - 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index d25a346640f1fc531b9dffa0d8b5f8b959cbb659..21a3dab1bd186513d44f47c647b90534413adfaf 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1743,6 +1743,7 @@ pub const DeclGen = struct { .shuffle => try self.airShuffle(inst), .ptr_add => try self.airPtrAdd(inst), + .ptr_sub => try self.airPtrSub(inst), .bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd), .bit_or => try self.airBinOpSimple(inst, .OpBitwiseOr), @@ -2126,14 +2127,7 @@ pub const DeclGen = struct { return result_id; } - fn airPtrAdd(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { - if (self.liveness.isUnused(inst)) return null; - const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; - const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; - const ptr_id = try self.resolve(bin_op.lhs); - const offset_id = try self.resolve(bin_op.rhs); - const ptr_ty = self.air.typeOf(bin_op.lhs); - const result_ty = self.air.typeOfIndex(inst); + fn ptrAdd(self: *DeclGen, result_ty: Type, ptr_ty: Type, ptr_id: IdRef, offset_id: IdRef) !IdRef { const result_ty_ref = try self.resolveType(result_ty, .direct); switch (ptr_ty.ptrSize()) { @@ -2153,6 +2147,38 @@ pub const DeclGen = struct { } } + fn airPtrAdd(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; + const ptr_id = try self.resolve(bin_op.lhs); + const offset_id = try self.resolve(bin_op.rhs); + const ptr_ty = self.air.typeOf(bin_op.lhs); + const result_ty = self.air.typeOfIndex(inst); + + return try self.ptrAdd(result_ty, ptr_ty, ptr_id, offset_id); + } + + fn airPtrSub(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; + const ptr_id = try self.resolve(bin_op.lhs); + const ptr_ty = self.air.typeOf(bin_op.lhs); + const offset_id = try self.resolve(bin_op.rhs); + const offset_ty = self.air.typeOf(bin_op.rhs); + const offset_ty_ref = try self.resolveType(offset_ty, .direct); + const result_ty = self.air.typeOfIndex(inst); + + const negative_offset_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpSNegate, .{ + .id_result_type = self.typeId(offset_ty_ref), + .id_result = negative_offset_id, + .operand = offset_id, + }); + return try self.ptrAdd(result_ty, ptr_ty, ptr_id, negative_offset_id); + } + fn cmp( self: *DeclGen, comptime op: std.math.CompareOperator, diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig index bbfbd1feb75b83287fba6eaa70e5ac5f66f2ceb6..5ab1b09c89ee2ebe02f5d16aa017209879463fd5 100644 --- a/test/behavior/pointers.zig +++ b/test/behavior/pointers.zig @@ -84,7 +84,6 @@ test "assigning integer to C pointer" { test "C pointer comparison and arithmetic" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { -- 2.54.0 From 65157d30ab90de01da583dd97ee2409d8ad8aeb0 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 19 May 2023 14:17:58 +0200 Subject: [PATCH 12/13] spirv: ptr_elem_val Implements the ptr_elem_val air tag. Implementation is unified with ptr_elem_ptr. --- src/codegen/spirv.zig | 50 +++++++++++++++++++++-------- test/behavior/align.zig | 2 -- test/behavior/call.zig | 2 -- test/behavior/cast.zig | 1 - test/behavior/error.zig | 1 - test/behavior/eval.zig | 2 -- test/behavior/floatop.zig | 1 - test/behavior/fn.zig | 2 -- test/behavior/for.zig | 2 -- test/behavior/generics.zig | 1 - test/behavior/optional.zig | 1 - test/behavior/packed-struct.zig | 1 - test/behavior/pointers.zig | 2 -- test/behavior/sizeof_and_typeof.zig | 1 - test/behavior/slice.zig | 1 - test/behavior/struct.zig | 2 -- test/behavior/switch.zig | 2 -- test/behavior/threadlocal.zig | 1 - test/behavior/type_info.zig | 1 - test/behavior/var_args.zig | 1 - test/behavior/while.zig | 2 -- 21 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 21a3dab1bd186513d44f47c647b90534413adfaf..b372a38ddf5cd1075f5c4a03a8c0e8dcea697f0a 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1765,6 +1765,7 @@ pub const DeclGen = struct { .slice_elem_ptr => try self.airSliceElemPtr(inst), .slice_elem_val => try self.airSliceElemVal(inst), .ptr_elem_ptr => try self.airPtrElemPtr(inst), + .ptr_elem_val => try self.airPtrElemVal(inst), .struct_field_val => try self.airStructFieldVal(inst), @@ -2482,29 +2483,52 @@ pub const DeclGen = struct { return try self.load(slice_ty, elem_ptr); } + fn ptrElemPtr(self: *DeclGen, ptr_ty: Type, ptr_id: IdRef, index_id: IdRef) !IdRef { + // Construct new pointer type for the resulting pointer + const elem_ty = ptr_ty.elemType2(); // use elemType() so that we get T for *[N]T. + const elem_ty_ref = try self.resolveType(elem_ty, .direct); + const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace()), 0); + if (ptr_ty.isSinglePointer()) { + // Pointer-to-array. In this case, the resulting pointer is not of the same type + // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain. + return try self.accessChain(elem_ptr_ty_ref, ptr_id, &.{index_id}); + } else { + // Resulting pointer type is the same as the ptr_ty, so use ptrAccessChain + return try self.ptrAccessChain(elem_ptr_ty_ref, ptr_id, index_id, &.{}); + } + } + fn airPtrElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; const ptr_ty = self.air.typeOf(bin_op.lhs); - const result_ty = self.air.typeOfIndex(inst); const elem_ty = ptr_ty.childType(); // TODO: Make this return a null ptr or something if (!elem_ty.hasRuntimeBitsIgnoreComptime()) return null; - const result_ty_ref = try self.resolveType(result_ty, .direct); - const base_ptr = try self.resolve(bin_op.lhs); - const rhs = try self.resolve(bin_op.rhs); - - if (ptr_ty.isSinglePointer()) { - // Pointer-to-array. In this case, the resulting pointer is not of the same type - // as the ptr_ty, and hence we need to use accessChain. - return try self.accessChain(result_ty_ref, base_ptr, &.{rhs}); - } else { - // Resulting pointer type is the same as the ptr_ty, so use ptrAccessChain - return try self.ptrAccessChain(result_ty_ref, base_ptr, rhs, &.{}); - } + const ptr_id = try self.resolve(bin_op.lhs); + const index_id = try self.resolve(bin_op.rhs); + return try self.ptrElemPtr(ptr_ty, ptr_id, index_id); + } + + fn airPtrElemVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const ptr_ty = self.air.typeOf(bin_op.lhs); + const ptr_id = try self.resolve(bin_op.lhs); + const index_id = try self.resolve(bin_op.rhs); + + const elem_ptr_id = try self.ptrElemPtr(ptr_ty, ptr_id, index_id); + + // If we have a pointer-to-array, construct an element pointer to use with load() + // If we pass ptr_ty directly, it will attempt to load the entire array rather than + // just an element. + var elem_ptr_info = ptr_ty.ptrInfo(); + elem_ptr_info.data.size = .One; + const elem_ptr_ty = Type.initPayload(&elem_ptr_info.base); + + return try self.load(elem_ptr_ty, elem_ptr_id); } fn airStructFieldVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { diff --git a/test/behavior/align.zig b/test/behavior/align.zig index bc1091ace772ed82438a23b6f78a667d864671fb..dd0ea3171146522e487e41f211740692a82c20f9 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -215,8 +215,6 @@ test "alignment and size of structs with 128-bit fields" { } test "@ptrCast preserves alignment of bigger source" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - var x: u32 align(16) = 1234; const ptr = @ptrCast(*u8, &x); try expect(@TypeOf(ptr) == *align(16) u8); diff --git a/test/behavior/call.zig b/test/behavior/call.zig index 76225d815dd33bc0c82abe7081f4136a08c27219..c1bd1ae76dc899847cc1e914070459b6cda30c81 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -385,8 +385,6 @@ test "generic function with generic function parameter" { } test "recursive inline call with comptime known argument" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const S = struct { inline fn foo(x: i32) i32 { if (x <= 0) { diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index f3a64b4b7ca9509e28e8538ee1efd1fa1ba90441..594bf683e5bd6b1187f18b47de32bbd22ce40228 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -322,7 +322,6 @@ test "peer result null and comptime_int" { test "*const ?[*]const T to [*c]const [*c]const T" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; var array = [_]u8{ 'o', 'k' }; const opt_array_ptr: ?[*]const u8 = &array; diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 89ce09401083bca1f3ab267c84711368f74046b5..618af87e1033246afb01e7e4cd82a8ffe6b7a734 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -22,7 +22,6 @@ test "error values" { } test "redefinition of error values allowed" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; shouldBeNotEqual(error.AnError, error.SecondError); } fn shouldBeNotEqual(a: anyerror, b: anyerror) void { diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index cd913dcc9ac759e799411980199ded8fb4a4d395..68d43ef0b660a003b1da221ee6bf42a5116f560b 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -47,8 +47,6 @@ test "inline variable gets result of const if" { } test "static function evaluation" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try expect(statically_added_number == 3); } const statically_added_number = staticAdd(1, 2); diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index 97054b70834b0f7fd53a123398327165e205fbc2..9391aa93987897ddc2553602ea6953616e125d2a 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -620,7 +620,6 @@ test "@floor" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; comptime try testFloor(); try testFloor(); diff --git a/test/behavior/fn.zig b/test/behavior/fn.zig index 608b992eb3e742baa572df76f76b05f4fb3838d2..4f05054522f1d0a72812f96b267408ebb9747fbd 100644 --- a/test/behavior/fn.zig +++ b/test/behavior/fn.zig @@ -12,8 +12,6 @@ fn testParamsAdd(a: i32, b: i32) i32 { } test "local variables" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - testLocVars(2); } fn testLocVars(b: i32) void { diff --git a/test/behavior/for.zig b/test/behavior/for.zig index c99ee2edeb78146da08041aef5ecc600c0eb5ba6..c41fccfc80a1900e23a3f0dcb2fe115798de1825 100644 --- a/test/behavior/for.zig +++ b/test/behavior/for.zig @@ -39,8 +39,6 @@ fn testBreakOuter() !void { } test "continue outer for loop" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try testContinueOuter(); comptime try testContinueOuter(); } diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index eddca9e7b1a95d04d6fc5e7ced69de6c93cb205b..97257d75a339a1cf3ee6f2723a42927536a4810c 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -19,7 +19,6 @@ fn checkSize(comptime T: type) usize { test "simple generic fn" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(max(i32, 3, -1) == 3); try expect(max(u8, 1, 100) == 100); diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index a813580a209a9d2cac13e62b81e3fc966771465b..4a043c6a6ec82a5e2efebc6fe2df06b54b7b980c 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -421,7 +421,6 @@ test "optional of noreturn used with orelse" { } test "orelse on C pointer" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO https://github.com/ziglang/zig/issues/6597 const foo: [*c]const u8 = "hey"; diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig index 7406c22f468dab92adb756d8b193a8be6216119c..0bd6390796d9ab77972b66393ee74449005a3d51 100644 --- a/test/behavior/packed-struct.zig +++ b/test/behavior/packed-struct.zig @@ -93,7 +93,6 @@ test "flags in packed structs" { test "consistent size of packed structs" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const TxData1 = packed struct { data: u8, _23: u23, full: bool = false }; const TxData2 = packed struct { data: u9, _22: u22, full: bool = false }; diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig index 5ab1b09c89ee2ebe02f5d16aa017209879463fd5..70bc6ad47e084c59d76d11e16c813edeff4f7fa2 100644 --- a/test/behavior/pointers.zig +++ b/test/behavior/pointers.zig @@ -19,7 +19,6 @@ fn testDerefPtr() !void { test "pointer arithmetic" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; var ptr: [*]const u8 = "abcd"; @@ -300,7 +299,6 @@ test "null terminated pointer" { test "allow any sentinel" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { diff --git a/test/behavior/sizeof_and_typeof.zig b/test/behavior/sizeof_and_typeof.zig index 3f70f02fcb122b3a14eb78e4bd5e8906a485d9a9..e463e51753cdb324cab7ae4dca3c6fa6947ee1e4 100644 --- a/test/behavior/sizeof_and_typeof.zig +++ b/test/behavior/sizeof_and_typeof.zig @@ -154,7 +154,6 @@ test "@TypeOf() has no runtime side effects" { test "branching logic inside @TypeOf" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { var data: i32 = 0; diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index dc27328e6d0365758c412c647626065eeb335643..79fde3482ca90b4a23b894a786dca58b13e10553 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -672,7 +672,6 @@ test "array mult of slice gives ptr to array" { test "slice bounds in comptime concatenation" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const bs = comptime blk: { const b = "........1........"; diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index a4a24ef3b34188c40230fb36bcdf7f801266a2c3..4553596c4177d58875538b1aa8ebf15a7ab23e3f 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -121,8 +121,6 @@ test "struct byval assign" { } test "call struct static method" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const result = StructWithNoFields.add(3, 4); try expect(result == 7); } diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index dd096f89edf3c06390b303c064e94422ed5fbc61..3f6cd3729873ebf56f039a7b2e59b0058a164342 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -348,8 +348,6 @@ fn returnsFalse() bool { } } test "switch on const enum with var" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try expect(!returnsFalse()); } diff --git a/test/behavior/threadlocal.zig b/test/behavior/threadlocal.zig index 57740f75ce04a1ca3b65a67cda234270413ccf57..763c87605c6afd5da89ac8de6c5dc59ed4f92089 100644 --- a/test/behavior/threadlocal.zig +++ b/test/behavior/threadlocal.zig @@ -46,7 +46,6 @@ test "reference a global threadlocal variable" { else => return error.SkipZigTest, }; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; _ = nrfx_uart_rx(&g_uart0); } diff --git a/test/behavior/type_info.zig b/test/behavior/type_info.zig index 18b65b755e25c6ce7c0f6c6d48d816893d03417a..2fdb112a729a26b0393bd84f0d873b83718c0d3d 100644 --- a/test/behavior/type_info.zig +++ b/test/behavior/type_info.zig @@ -512,7 +512,6 @@ test "type info for async frames" { test "Declarations are returned in declaration order" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { const a = 1; diff --git a/test/behavior/var_args.zig b/test/behavior/var_args.zig index 94e2a81a9a617f68852730f4ba972f3a7a6161ba..a2a0e7b4c62b1ea10bb9fdeb3b8d029169a07d0e 100644 --- a/test/behavior/var_args.zig +++ b/test/behavior/var_args.zig @@ -30,7 +30,6 @@ test "send void arg to var args" { test "pass args directly" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(addSomeStuff(.{ @as(i32, 1), @as(i32, 2), @as(i32, 3), @as(i32, 4) }) == 10); try expect(addSomeStuff(.{@as(i32, 1234)}) == 1234); diff --git a/test/behavior/while.zig b/test/behavior/while.zig index 8d3e3a12da71cc6f092263d9c79763358a717a2a..a051a8fd3bad6312d2d7b011c26fec2d1a8dc751 100644 --- a/test/behavior/while.zig +++ b/test/behavior/while.zig @@ -38,8 +38,6 @@ fn staticWhileLoop2() i32 { } test "while with continue expression" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - var sum: i32 = 0; { var i: i32 = 0; -- 2.54.0 From c92cc5798f0ea72ab1c77ae12c6124e5ab090730 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 20 May 2023 18:02:30 +0200 Subject: [PATCH 13/13] spirv: make constant handle float, errorset, errorunion This is in preparation of removing indirect lowering again. Also modifies constant() to accept a repr so that both direct as well as indirect representations can be generated. Indirect is not yet used, but will be used for globals. --- src/codegen/spirv.zig | 79 ++++++++++++++++++++++++++++++------ src/codegen/spirv/Module.zig | 10 +++++ 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index b372a38ddf5cd1075f5c4a03a8c0e8dcea697f0a..decdab6cde0ecb3e6a365fd9c683320f67283ad3 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -242,7 +242,7 @@ pub const DeclGen = struct { return self.spv.declPtr(spv_decl_index).result_id; } - return try self.constant(ty, val); + return try self.constant(ty, val, .direct); } const index = Air.refToIndex(inst).?; return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage. @@ -1021,14 +1021,16 @@ pub const DeclGen = struct { /// the constant is more complicated however, it needs to be lowered to an indirect constant, which /// is then loaded using OpLoad. Such values are loaded into the UniformConstant storage class by default. /// This function should only be called during function code generation. - fn constant(self: *DeclGen, ty: Type, val: Value) !IdRef { + fn constant(self: *DeclGen, ty: Type, val: Value, repr: Repr) !IdRef { const target = self.getTarget(); const section = &self.spv.sections.types_globals_constants; - const result_ty_ref = try self.resolveType(ty, .direct); + const result_ty_ref = try self.resolveType(ty, repr); const result_ty_id = self.typeId(result_ty_ref); - const result_id = self.spv.allocId(); + + log.debug("constant: ty = {}, val = {}", .{ ty.fmt(self.module), val.fmtValue(ty, self.module) }); if (val.isUndef()) { + const result_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_ty_id, .id_result = result_id, @@ -1039,24 +1041,76 @@ pub const DeclGen = struct { switch (ty.zigTypeTag()) { .Int => { if (ty.isSignedInt()) { - try self.genConstInt(result_ty_ref, result_id, val.toSignedInt(target)); + return try self.constInt(result_ty_ref, val.toSignedInt(target)); } else { - try self.genConstInt(result_ty_ref, result_id, val.toUnsignedInt(target)); + return try self.constInt(result_ty_ref, val.toUnsignedInt(target)); } }, - .Bool => { - const operands = .{ .id_result_type = result_ty_id, .id_result = result_id }; - if (val.toBool()) { - try section.emit(self.spv.gpa, .OpConstantTrue, operands); + .Bool => switch (repr) { + .direct => { + const result_id = self.spv.allocId(); + const operands = .{ .id_result_type = result_ty_id, .id_result = result_id }; + if (val.toBool()) { + try section.emit(self.spv.gpa, .OpConstantTrue, operands); + } else { + try section.emit(self.spv.gpa, .OpConstantFalse, operands); + } + return result_id; + }, + .indirect => return try self.constInt(result_ty_ref, @boolToInt(val.toBool())), + }, + .Float => { + const result_id = self.spv.allocId(); + switch (ty.floatBits(target)) { + 16 => try self.spv.emitConstant(result_ty_id, result_id, .{ .float32 = val.toFloat(f16) }), + 32 => try self.spv.emitConstant(result_ty_id, result_id, .{ .float32 = val.toFloat(f32) }), + 64 => try self.spv.emitConstant(result_ty_id, result_id, .{ .float64 = val.toFloat(f64) }), + 80, 128 => unreachable, // TODO + else => unreachable, + } + return result_id; + }, + .ErrorSet => { + const value = switch (val.tag()) { + .@"error" => blk: { + const err_name = val.castTag(.@"error").?.data.name; + const kv = try self.module.getErrorValue(err_name); + break :blk @intCast(u16, kv.value); + }, + .zero => 0, + else => unreachable, + }; + + return try self.constInt(result_ty_ref, value); + }, + .ErrorUnion => { + const payload_ty = ty.errorUnionPayload(); + const is_pl = val.errorUnionIsPayload(); + const error_val = if (!is_pl) val else Value.initTag(.zero); + + const eu_layout = self.errorUnionLayout(payload_ty); + if (!eu_layout.payload_has_bits) { + return try self.constant(Type.anyerror, error_val, repr); + } + + const payload_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef); + + var members: [2]IdRef = undefined; + if (eu_layout.error_first) { + members[0] = try self.constant(Type.anyerror, error_val, .indirect); + members[1] = try self.constant(payload_ty, payload_val, .indirect); } else { - try section.emit(self.spv.gpa, .OpConstantFalse, operands); + members[0] = try self.constant(payload_ty, payload_val, .indirect); + members[1] = try self.constant(Type.anyerror, error_val, .indirect); } + return try self.spv.constComposite(result_ty_ref, &members); }, // TODO: We can handle most pointers here (decl refs etc), because now they emit an extra // OpVariable that is not really required. else => { // The value cannot be generated directly, so generate it as an indirect constant, // and then perform an OpLoad. + const result_id = self.spv.allocId(); const alignment = ty.abiAlignment(target); const spv_decl_index = try self.spv.allocDecl(.global); @@ -1078,10 +1132,9 @@ pub const DeclGen = struct { }); // TODO: Convert bools? This logic should hook into `load`. It should be a dead // path though considering .Bool is handled above. + return result_id; }, } - - return result_id; } /// Turn a Zig type into a SPIR-V Type, and return its type result-id. diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 4bd6c834cefcd185507747e35d1f40d11167f056..5e7e6508fad0e4740bd6b2071aba4665905535d6 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -774,6 +774,16 @@ pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_cl return try self.resolveType(Type.initPayload(&payload.base)); } +pub fn constComposite(self: *Module, ty_ref: Type.Ref, members: []const IdRef) !IdRef { + const result_id = self.allocId(); + try self.sections.types_globals_constants.emit(self.gpa, .OpSpecConstantComposite, .{ + .id_result_type = self.typeId(ty_ref), + .id_result = result_id, + .constituents = members, + }); + return result_id; +} + pub fn emitConstant( self: *Module, ty_id: IdRef, -- 2.54.0