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 | 181 +++++++++++++++++++++++++-------------- test/behavior/basic.zig | 17 ---- test/behavior/enum.zig | 2 + test/behavior/memcpy.zig | 20 +++-- 4 files changed, 130 insertions(+), 90 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index d1def8a02e..9cfac608a1 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,56 +2028,120 @@ 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 { + 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(); + 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; - var lhs_id = try self.resolve(bin_op.lhs); - var rhs_id = try self.resolve(bin_op.rhs); - 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)); + 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)); - // 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, - } - - return result_id; + 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 { diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 0de278fc57..756d62f3ab 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 097caaad19..44a6026f2b 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 af86c09113..b7c5eb29d9 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"); + } } 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 9cfac608a1..5edafc5037 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 44a6026f2b..097caaad19 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 }; 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 5edafc5037..b86b4193ab 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 756d62f3ab..9fced18915 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 cc85594c50..01aec23223 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; 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 b86b4193ab..d8446ea045 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 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 d8446ea045..51cfba1a28 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( 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 51cfba1a28..bf75fee3fd 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 9fced18915..7f9e2ae78a 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 { 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 bf75fee3fd..9ed1dd0b1a 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 => { 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 9ed1dd0b1a..d25a346640 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 26d841a1fd..bbfbd1feb7 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; 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 4bd6666696..bc1091ace7 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 b4754a59a4..ed8b021613 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 d1b51207a4..99f08a6cae 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 7f9e2ae78a..3024ad58c0 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 4b8c363ac2..28eb00c6c1 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 87cea942d0..8d98f65b55 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 e7d59d7e75..76225d815d 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 847abc1798..f3a64b4b7c 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 7aa8fb6318..b01a431e28 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 5aa8c2f02b..c97350cd75 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 81b9ebdf50..52930ea318 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 097caaad19..255efc50fc 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 54d53eec4a..89ce094010 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 d22eba4fa0..cd913dcc9a 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 21fc87ff22..97054b7083 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 c84eb48d2e..608b992eb3 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 d7a2ba2125..95dbfeb4b2 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 427b08e6a4..a7d8e779cc 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 8c9eeb083e..c99ee2edeb 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 e8d064154b..eddca9e7b1 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 7369435cd3..c17f2c9891 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 a05ed83b9b..29a263b2ce 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 db6cad221f..648d4d9493 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 e62065cf25..a813580a20 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 36e37786f1..7406c22f46 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 41d7f56662..bb6d5b1359 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 b62838f243..aea84bc45a 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 e01e20e544..3f70f02fcb 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 3b88636dca..dc27328e6d 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 659acbf56b..a4a24ef3b3 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 f66446632b..dd096f89ed 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 8fd8af92a3..0bc765c8a7 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 a1214fff21..57740f75ce 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 936d9663ad..2fff4b05af 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 d55688c5ee..18b65b755e 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 e0f3e00fff..6c99b4bbce 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 26232159b6..213d4c75ea 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 adb5491f7f..94e2a81a9a 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 a6634b672f..8d3e3a12da 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) { 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 5c446e5f85..e365936798 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 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 d25a346640..21a3dab1bd 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 bbfbd1feb7..5ab1b09c89 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 { 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 | 48 +++++++++++++++++++++-------- 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, 36 insertions(+), 41 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 21a3dab1bd..b372a38ddf 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); + 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); + } - 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 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 bc1091ace7..dd0ea31711 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 76225d815d..c1bd1ae76d 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 f3a64b4b7c..594bf683e5 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 89ce094010..618af87e10 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 cd913dcc9a..68d43ef0b6 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 97054b7083..9391aa9398 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 608b992eb3..4f05054522 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 c99ee2edeb..c41fccfc80 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 eddca9e7b1..97257d75a3 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 a813580a20..4a043c6a6e 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 7406c22f46..0bd6390796 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 5ab1b09c89..70bc6ad47e 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 3f70f02fcb..e463e51753 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 dc27328e6d..79fde3482c 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 a4a24ef3b3..4553596c41 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 dd096f89ed..3f6cd37298 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 57740f75ce..763c87605c 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 18b65b755e..2fdb112a72 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 94e2a81a9a..a2a0e7b4c6 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 8d3e3a12da..a051a8fd3b 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; 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 | 81 +++++++++++++++++++++++++++++------- src/codegen/spirv/Module.zig | 10 +++++ 2 files changed, 77 insertions(+), 14 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index b372a38ddf..decdab6cde 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); - } else { - try section.emit(self.spv.gpa, .OpConstantFalse, 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 { + 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 4bd6c834ce..5e7e6508fa 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,