diff --git a/lib/std/spirv.zig b/lib/std/spirv.zig index 6f9fd74967053ec295ad14882d4abda7eedccc18..513f73c53bf7b203ab553f4318dbd68f37e68cf0 100644 --- a/lib/std/spirv.zig +++ b/lib/std/spirv.zig @@ -82,3 +82,33 @@ pub fn workgroupBarrier() void { .{ .acquire_release = true, .workgroup_memory = true }, ); } + +pub fn specConst(T: type, comptime default_value: T, comptime spec_id: u32) T { + switch (@typeInfo(T)) { + .bool => { + const op = if (default_value) "OpSpecConstantTrue" else "OpSpecConstantFalse"; + return asm ("%ret = " ++ op ++ " %ty\n" ++ + "OpDecorate %ret SpecId $spec_id" + : [ret] "" (-> T), + : [ty] "t" (T), + [spec_id] "c" (spec_id), + ); + }, + .int, .float => return asm ( + \\%ret = OpSpecConstant %ty $default_value + \\OpDecorate %ret SpecId $spec_id" + : [ret] "" (-> T), + : [ty] "t" (T), + [default_value] "c" (default_value), + [spec_id] "c" (spec_id), + ), + .vector => return asm ( + \\%ret = OpSpecConstantComposite %ty %default_value %spec_id + : [ret] "" (-> T), + : [ty] "t" (T), + [default_value] "c" (default_value), + [spec_id] "c" (spec_id), + ), + else => @compileError("unsupported spec constant type"), + } +} diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index f18f9028212b4916ed70a89d080e28a3bad75f3f..04549eb900aae5604849fbcdb261dc6083d608ee 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -58,6 +58,10 @@ const Operand = union(enum) { pub fn deinit(ass: *Assembler) void { const gpa = ass.cg.gpa; for (ass.errors.items) |err| gpa.free(err.msg); + for (ass.value_map.values()) |v| switch (v) { + .constant_composite => |cc| gpa.free(cc.values), + else => {}, + }; ass.tokens.deinit(gpa); ass.errors.deinit(gpa); ass.inst.operands.deinit(gpa); @@ -132,8 +136,18 @@ const AsmValue = union(enum) { value: Id, /// A type registered into the module's type system. ty: Id, - /// A pre-supplied constant integer value. - constant: u32, + /// A pre-supplied constant value, holding the raw bit pattern of the input. + /// For integers the value is sign-extended (for signed) or zero-extended + /// (for unsigned) to 64 bits. For floats, the value is the bit pattern + /// zero-extended from the float's width to 64 bits. + constant: u64, + /// A vector "c" input expanded by `processSpecConstVector`. + constant_composite: struct { + child: Id, + child_kind: std.lang.TypeId, + child_bit_width: u16, + values: []u64, + }, string: []const u8, /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue @@ -145,6 +159,7 @@ const AsmValue = union(enum) { .unresolved_forward_reference, // TODO: Lower this value as constant? .constant, + .constant_composite, .string, => unreachable, .value => |result| result, @@ -178,6 +193,12 @@ fn processInstruction(ass: *Assembler) !void { }; break :blk .{ .value = try cg.importInstructionSet(set_tag) }; }, + .OpSpecConstantComposite => blk: { + if (try ass.processSpecConstVector()) |result| { + break :blk result; + } + break :blk (try ass.processGenericInstruction()) orelse return; + }, else => switch (ass.inst.opcode.class()) { .type_declaration => try ass.processTypeInstruction(), else => (try ass.processGenericInstruction()) orelse return, @@ -398,6 +419,87 @@ fn processGenericInstruction(ass: *Assembler) !?AsmValue { return null; } +/// Handles `%ret = OpSpecConstantComposite %ty %vec %spec_id` where `%vec` is a +/// vector `"c"` input and `%spec_id` is a base SpecId `"c"` input. +/// returns null to fall back to normal processing. +fn processSpecConstVector(ass: *Assembler) !?AsmValue { + if (ass.inst.operands.items.len != 4) return null; + const vec_ref = switch (ass.inst.operands.items[2]) { + .ref_id => |i| i, + else => return null, + }; + const sid_ref = switch (ass.inst.operands.items[3]) { + .ref_id => |i| i, + else => return null, + }; + const cc = switch (try ass.resolveRef(vec_ref)) { + .constant_composite => |cc| cc, + else => return null, + }; + const spec_id_base = switch (try ass.resolveRef(sid_ref)) { + .constant => |v| v, + else => return null, + }; + + const cg = ass.cg; + const gpa = cg.gpa; + const ty_ref = switch (ass.inst.operands.items[0]) { + .ref_id => |i| i, + else => return ass.fail(0, "missing result type", .{}), + }; + const composite_ty_id = switch (try ass.resolveRef(ty_ref)) { + .ty => |id| id, + else => return ass.fail(0, "%ty must be a type", .{}), + }; + + const globals = &cg.sections.globals; + const annotations = &cg.sections.annotations; + const literal_words: usize = if (cc.child_bit_width <= @bitSizeOf(Word)) 1 else 2; + + const elem_ids = try gpa.alloc(Id, cc.values.len); + defer gpa.free(elem_ids); + for (cc.values, elem_ids, 0..) |value, *elem_id_out, i| { + const elem_id = cg.allocId(); + elem_id_out.* = elem_id; + + switch (cc.child_kind) { + .bool => { + const opcode: Opcode = if (value & 1 != 0) .OpSpecConstantTrue else .OpSpecConstantFalse; + try globals.emitRaw(gpa, opcode, 2); + globals.writeOperand(Id, cc.child); + globals.writeOperand(Id, elem_id); + }, + .int, .float => { + try globals.emitRaw(gpa, .OpSpecConstant, 2 + literal_words); + globals.writeOperand(Id, cc.child); + globals.writeOperand(Id, elem_id); + if (literal_words == 1) { + globals.writeWord(@truncate(value)); + } else { + globals.writeDoubleWord(value); + } + }, + else => unreachable, + } + + const spec_id_word = std.math.cast(u32, spec_id_base + i) orelse { + return ass.fail(0, "SpecId {} does not fit in 32 bits", .{spec_id_base + i}); + }; + try annotations.emitRaw(gpa, .OpDecorate, 3); + annotations.writeOperand(Id, elem_id); + annotations.writeWord(@intFromEnum(spec.Decoration.spec_id)); + annotations.writeWord(spec_id_word); + } + + const result_id = cg.allocId(); + try globals.emitRaw(gpa, .OpSpecConstantComposite, 2 + cc.values.len); + globals.writeOperand(Id, composite_ty_id); + globals.writeOperand(Id, result_id); + for (elem_ids) |id| globals.writeOperand(Id, id); + + return .{ .value = result_id }; +} + fn resolveMaybeForwardRef(ass: *Assembler, ref: AsmValue.Ref) !AsmValue { const value = ass.value_map.values()[ref]; switch (value) { @@ -579,7 +681,14 @@ fn parseValueEnum(ass: *Assembler, kind: spec.OperandKind) !void { return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); }; switch (value) { - .constant => |literal32| { + .constant => |literal| { + const literal32 = std.math.cast(u32, literal) orelse { + return ass.fail( + tok.start, + "placeholder value {} does not fit in 32 bits", + .{literal}, + ); + }; try ass.inst.operands.append(gpa, .{ .value = literal32 }); }, .string => |str| { @@ -646,7 +755,14 @@ fn parseLiteralInteger(ass: *Assembler) !void { return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); }; switch (value) { - .constant => |literal32| { + .constant => |literal| { + const literal32 = std.math.cast(u32, literal) orelse { + return ass.fail( + tok.start, + "placeholder value {} does not fit in 32 bits", + .{literal}, + ); + }; try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); }, else => { @@ -679,7 +795,14 @@ fn parseLiteralExtInstInteger(ass: *Assembler) !void { return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); }; switch (value) { - .constant => |literal32| { + .constant => |literal| { + const literal32 = std.math.cast(u32, literal) orelse { + return ass.fail( + tok.start, + "placeholder value {} does not fit in 32 bits", + .{literal}, + ); + }; try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); }, else => { @@ -767,8 +890,12 @@ fn parseContextDependentInt(ass: *Assembler, signedness: std.lang.Signedness, wi return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); }; switch (value) { - .constant => |literal32| { - try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); + .constant => |literal| { + if (width <= @bitSizeOf(spec.Word)) { + try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(literal) }); + } else { + try ass.inst.operands.append(gpa, .{ .literal64 = literal }); + } }, else => { return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}); @@ -815,6 +942,25 @@ fn parseContextDependentFloat(ass: *Assembler, comptime width: u16) !void { const Int = @Int(.unsigned, width); const tok = ass.currentToken(); + if (ass.eatToken(.placeholder)) { + const name = ass.tokenText(tok)[1..]; + const value = ass.value_map.get(name) orelse { + return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); + }; + switch (value) { + .constant => |literal| { + if (width <= @bitSizeOf(spec.Word)) { + try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(literal) }); + } else { + try ass.inst.operands.append(gpa, .{ .literal64 = literal }); + } + }, + else => { + return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}); + }, + } + return; + } try ass.expectToken(.value); const text = ass.tokenText(tok); diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index c077896d90d07183ce57378b99eece19af1b95f8..ea67f6d0c86dd6b9464f0ba47f8b4233d32d8e73 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -423,7 +423,7 @@ pub fn addEntryPointDeps( cg: *CodeGen, decl_index: Decl.Index, seen: *std.bit_set.Dynamic, - interface: *std.array_list.Managed(Id), + interface: *std.ArrayList(Id), ) !void { const decl = cg.declPtr(decl_index); const deps = cg.decl_deps.items[decl.begin_dep..decl.end_dep]; @@ -435,7 +435,7 @@ pub fn addEntryPointDeps( seen.set(@intFromEnum(decl_index)); if (decl.kind == .global) { - try interface.append(decl.result_id); + try interface.append(cg.gpa, decl.result_id); } for (deps) |dep| { @@ -1806,11 +1806,11 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { const struct_type = zcu.typeToStruct(ty).?; assert(struct_type.layout != .@"packed"); // packed structs use `bitpack` - var types = std.array_list.Managed(Type).init(gpa); - defer types.deinit(); + var types: std.ArrayList(Type) = .empty; + defer types.deinit(gpa); - var constituents = std.array_list.Managed(Id).init(gpa); - defer constituents.deinit(); + var constituents: std.ArrayList(Id) = .empty; + defer constituents.deinit(gpa); var it = struct_type.iterateRuntimeOrder(ip); while (it.next()) |field_index| { @@ -1824,8 +1824,8 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { const field_val = try val.fieldValue(pt, field_index); const field_id = try cg.constant(field_ty, field_val, .indirect); - try types.append(field_ty); - try constituents.append(field_id); + try types.append(gpa, field_ty); + try constituents.append(gpa, field_id); } const comp_ty_id = try cg.resolveType(ty, .direct); @@ -2366,11 +2366,11 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { return try cg.resolveType(.fromInterned(struct_type.packed_backing_int_type), .direct); } - var member_types = std.array_list.Managed(Id).init(gpa); - defer member_types.deinit(); + var member_types: std.ArrayList(Id) = .empty; + defer member_types.deinit(gpa); - var member_names = std.array_list.Managed([]const u8).init(gpa); - defer member_names.deinit(); + var member_names: std.ArrayList([]const u8) = .empty; + defer member_names.deinit(gpa); var it = struct_type.iterateRuntimeOrder(ip); while (it.next()) |field_index| { @@ -2378,8 +2378,8 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { if (!field_ty.hasRuntimeBits(zcu)) continue; const field_name = struct_type.field_names.get(ip)[field_index]; - try member_types.append(try cg.resolveType(field_ty, .indirect)); - try member_names.append(field_name.toSlice(ip)); + try member_types.append(gpa, try cg.resolveType(field_ty, .indirect)); + try member_names.append(gpa, field_name.toSlice(ip)); } const result_id = try cg.structType( @@ -8688,31 +8688,69 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { }); const ip = &zcu.intern_pool; - switch (ip.indexToKey(val.toIntern())) { - .int_type, - .ptr_type, - .array_type, - .vector_type, - .opt_type, - .anyframe_type, - .error_union_type, - .simple_type, - .struct_type, - .union_type, - .opaque_type, - .spirv_type, - .enum_type, - .func_type, - .error_set_type, - .inferred_error_set_type, - => unreachable, // types, not values - - .undef => return cg.fail("assembly input with 'c' constraint cannot be undefined", .{}), - - .int => try ass.value_map.put(gpa, in.name, .{ .constant = @intCast(val.toUnsignedInt(zcu)) }), - .enum_literal => |str| try ass.value_map.put(gpa, in.name, .{ .string = str.toSlice(ip) }), - - else => unreachable, // TODO + const target = cg.pt.zcu.getTarget(); + if (ip.indexToKey(val.toIntern()) == .undef) { + return cg.fail("assembly input with 'c' constraint cannot be undefined", .{}); + } + switch (input_ty.zigTypeTag(zcu)) { + .int => { + const bits: u64 = switch (input_ty.intInfo(zcu).signedness) { + .unsigned => val.toUnsignedInt(zcu), + .signed => @bitCast(val.toSignedInt(zcu)), + }; + try ass.value_map.put(gpa, in.name, .{ .constant = bits }); + }, + .float => { + const bits: u64 = switch (input_ty.floatBits(target)) { + 16 => @as(u16, @bitCast(val.toFloat(f16, zcu))), + 32 => @as(u32, @bitCast(val.toFloat(f32, zcu))), + 64 => @bitCast(val.toFloat(f64, zcu)), + else => return cg.fail("unsupported float width for 'c' constraint", .{}), + }; + try ass.value_map.put(gpa, in.name, .{ .constant = bits }); + }, + .vector => { + const child_ty = input_ty.childType(zcu); + const child_kind = child_ty.zigTypeTag(zcu); + const child_bit_width: u16 = switch (child_kind) { + .bool => 0, + .int => @intCast(child_ty.intInfo(zcu).bits), + .float => child_ty.floatBits(target), + else => return cg.fail("'c' constraint vector element must be bool, int, or float", .{}), + }; + const vec_len: usize = @intCast(input_ty.vectorLen(zcu)); + const values = try gpa.alloc(u64, vec_len); + errdefer gpa.free(values); + for (values, 0..) |*out, i| { + const elem: Value = try val.elemValue(cg.pt, i); + out.* = switch (child_kind) { + .bool => @intFromBool(elem.toBool()), + .int => switch (child_ty.intInfo(zcu).signedness) { + .unsigned => elem.toUnsignedInt(zcu), + .signed => @bitCast(elem.toSignedInt(zcu)), + }, + .float => switch (child_bit_width) { + 16 => @as(u16, @bitCast(elem.toFloat(f16, zcu))), + 32 => @as(u32, @bitCast(elem.toFloat(f32, zcu))), + 64 => @bitCast(elem.toFloat(f64, zcu)), + else => unreachable, + }, + else => unreachable, + }; + } + const child_ty_id = try cg.resolveType(child_ty, .direct); + try ass.value_map.put(gpa, in.name, .{ .constant_composite = .{ + .child = child_ty_id, + .child_kind = child_kind, + .child_bit_width = child_bit_width, + .values = values, + } }); + }, + .@"enum" => switch (ip.indexToKey(val.toIntern())) { + .enum_literal => |str| try ass.value_map.put(gpa, in.name, .{ .string = str.toSlice(ip) }), + else => unreachable, + }, + else => return cg.fail("unsupported type for 'c' constraint", .{}), } } else if (std.mem.eql(u8, in.constraint, "t")) { // type @@ -8779,7 +8817,7 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .just_declared, .unresolved_forward_reference => unreachable, .ty => return cg.fail("cannot return spir-v type as value from assembly", .{}), .value => |ref| return ref, - .constant, .string => return cg.fail("cannot return constant from assembly", .{}), + .constant, .constant_composite, .string => return cg.fail("cannot return constant from assembly", .{}), } // TODO: Multiple results // TODO: Check that the output type from assembly is the same as the type actually expected by Zig. diff --git a/src/link/SpirV/BinaryModule.zig b/src/link/SpirV/BinaryModule.zig index 55604fc194d6e70df9fbd54eb417b6e55426eea5..53172685dc5c55a36fa0c57ccd7226a5f1b54fd4 100644 --- a/src/link/SpirV/BinaryModule.zig +++ b/src/link/SpirV/BinaryModule.zig @@ -303,7 +303,7 @@ pub const Parser = struct { } }, .literal_context_dependent_number => { - assert(inst.opcode == .OpConstant or inst.opcode == .OpSpecConstantOp); + assert(inst.opcode == .OpConstant or inst.opcode == .OpSpecConstant); const bit_width = binary.arith_type_width.get(@enumFromInt(inst.operands[0])) orelse { log.err("invalid LiteralContextDependentNumber type {}", .{inst.operands[0]}); return error.InvalidId;