authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2024-02-15 17:25:33+03:30
committergravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2024-02-15 17:25:44+03:30
log44c31194e3d192d0991866c91c5b44a54ebf8fb1
treeafc69b37fe89ebc48f11e432183e8571d9c86f46
parent6fe90a913a8876e78692d5e89652559f72e7644f

spirv: use extended instructions whenever possible


3 files changed, 139 insertions(+), 81 deletions(-)

src/codegen/spirv.zig+101-76
......@@ -632,11 +632,15 @@ const DeclGen = struct {
632632 /// Checks whether the type can be directly translated to SPIR-V vectors
633633 fn isVector(self: *DeclGen, ty: Type) bool {
634634 const mod = self.module;
635 const target = self.getTarget();
635636 if (ty.zigTypeTag(mod) != .Vector) return false;
636637 const elem_ty = ty.childType(mod);
638
637639 const len = ty.vectorLen(mod);
638640 const is_scalar = elem_ty.isNumeric(mod) or elem_ty.toIntern() == .bool_type;
639 return is_scalar and len > 1 and len <= 4;
641 const spirv_len = len > 1 and len <= 4;
642 const opencl_len = if (target.os.tag == .opencl) (len == 8 or len == 16) else false;
643 return is_scalar and (spirv_len or opencl_len);
640644 }
641645
642646 fn arithmeticTypeInfo(self: *DeclGen, ty: Type) ArithmeticTypeInfo {
......@@ -1968,7 +1972,10 @@ const DeclGen = struct {
19681972 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
19691973 .id_result_type = self.typeId(return_ty_ref),
19701974 .id_result = decl_id,
1971 .function_control = .{}, // TODO: We can set inline here if the type requires it.
1975 .function_control = switch (fn_info.cc) {
1976 .Inline => .{ .Inline = true },
1977 else => .{},
1978 },
19721979 .function_type = prototype_id,
19731980 });
19741981
......@@ -2437,48 +2444,71 @@ const DeclGen = struct {
24372444
24382445 fn minMax(self: *DeclGen, result_ty: Type, op: std.math.CompareOperator, lhs_id: IdRef, rhs_id: IdRef) !IdRef {
24392446 const info = self.arithmeticTypeInfo(result_ty);
2447 const target = self.getTarget();
24402448
2441 var wip = try self.elementWise(result_ty, true);
2449 const use_backup_codegen = target.os.tag == .opencl and info.class != .float;
2450 var wip = try self.elementWise(result_ty, use_backup_codegen);
24422451 defer wip.deinit();
2452
24432453 for (wip.results, 0..) |*result_id, i| {
24442454 const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i);
24452455 const rhs_elem_id = try wip.elementAt(result_ty, rhs_id, i);
24462456
2447 // TODO: Use fmin for OpenCL
2448 const cmp_id = try self.cmp(op, Type.bool, wip.ty, lhs_elem_id, rhs_elem_id);
2449 const selection_id = switch (info.class) {
2450 .float => blk: {
2451 // cmp uses OpFOrd. When we have 0 [<>] nan this returns false,
2452 // but we want it to pick lhs. Therefore we also have to check if
2453 // rhs is nan. We don't need to care about the result when both
2454 // are nan.
2455 const rhs_is_nan_id = self.spv.allocId();
2456 const bool_ty_ref = try self.resolveType(Type.bool, .direct);
2457 try self.func.body.emit(self.spv.gpa, .OpIsNan, .{
2458 .id_result_type = self.typeId(bool_ty_ref),
2459 .id_result = rhs_is_nan_id,
2460 .x = rhs_elem_id,
2461 });
2462 const float_cmp_id = self.spv.allocId();
2463 try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{
2464 .id_result_type = self.typeId(bool_ty_ref),
2465 .id_result = float_cmp_id,
2466 .operand_1 = cmp_id,
2467 .operand_2 = rhs_is_nan_id,
2468 });
2469 break :blk float_cmp_id;
2470 },
2471 else => cmp_id,
2472 };
2457 if (use_backup_codegen) {
2458 const cmp_id = try self.cmp(op, Type.bool, wip.ty, lhs_elem_id, rhs_elem_id);
2459 result_id.* = self.spv.allocId();
2460 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2461 .id_result_type = wip.ty_id,
2462 .id_result = result_id.*,
2463 .condition = cmp_id,
2464 .object_1 = lhs_elem_id,
2465 .object_2 = rhs_elem_id,
2466 });
2467 } else {
2468 const ext_inst: Word = switch (target.os.tag) {
2469 .opencl => switch (op) {
2470 .lt => 28, // fmin
2471 .gt => 27, // fmax
2472 else => unreachable,
2473 },
2474 .vulkan => switch (info.class) {
2475 .float => switch (op) {
2476 .lt => 37, // FMin
2477 .gt => 40, // FMax
2478 else => unreachable,
2479 },
2480 .integer, .strange_integer => switch (info.signedness) {
2481 .signed => switch (op) {
2482 .lt => 39, // SMin
2483 .gt => 42, // SMax
2484 else => unreachable,
2485 },
2486 .unsigned => switch (op) {
2487 .lt => 38, // UMin
2488 .gt => 41, // UMax
2489 else => unreachable,
2490 },
2491 },
2492 .composite_integer => unreachable, // TODO
2493 .bool => unreachable,
2494 },
2495 else => unreachable,
2496 };
2497 const set_id = switch (target.os.tag) {
2498 .opencl => try self.spv.importInstructionSet(.opencl),
2499 .vulkan => try self.spv.importInstructionSet(.glsl),
2500 else => unreachable,
2501 };
24732502
2474 result_id.* = self.spv.allocId();
2475 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2476 .id_result_type = wip.ty_id,
2477 .id_result = result_id.*,
2478 .condition = selection_id,
2479 .object_1 = lhs_elem_id,
2480 .object_2 = rhs_elem_id,
2481 });
2503 result_id.* = self.spv.allocId();
2504 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2505 .id_result_type = wip.ty_id,
2506 .id_result = result_id.*,
2507 .set = set_id,
2508 .instruction = .{ .inst = ext_inst },
2509 .id_ref_4 = &.{ lhs_elem_id, rhs_elem_id },
2510 });
2511 }
24822512 }
24832513 return wip.finalize();
24842514 }
......@@ -2607,57 +2637,52 @@ const DeclGen = struct {
26072637 }
26082638
26092639 fn airAbs(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2610 const mod = self.module;
2640 const target = self.getTarget();
26112641 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
26122642 const operand_id = try self.resolve(ty_op.operand);
26132643 // Note: operand_ty may be signed, while ty is always unsigned!
26142644 const operand_ty = self.typeOf(ty_op.operand);
26152645 const result_ty = self.typeOfIndex(inst);
2616 const info = self.arithmeticTypeInfo(result_ty);
2617 const operand_scalar_ty = operand_ty.scalarType(mod);
2618 const operand_scalar_ty_ref = try self.resolveType(operand_scalar_ty, .direct);
2646 const operand_info = self.arithmeticTypeInfo(operand_ty);
26192647
2620 var wip = try self.elementWise(result_ty, true);
2648 var wip = try self.elementWise(result_ty, false);
26212649 defer wip.deinit();
26222650
2623 const zero_id = switch (info.class) {
2624 .float => try self.constFloat(operand_scalar_ty_ref, 0),
2625 .integer, .strange_integer => try self.constInt(operand_scalar_ty_ref, 0),
2626 .composite_integer => unreachable, // TODO
2627 .bool => unreachable,
2628 };
26292651 for (wip.results, 0..) |*result_id, i| {
26302652 const elem_id = try wip.elementAt(operand_ty, operand_id, i);
2631 // Idk why spir-v doesn't have a dedicated abs() instruction in the base
2632 // instruction set. For now we're just going to negate and check to avoid
2633 // importing the extinst.
2634 // TODO: Make this a call to compiler rt / ext inst
2635 const neg_id = self.spv.allocId();
2636 const args = .{
2637 .id_result_type = self.typeId(operand_scalar_ty_ref),
2638 .id_result = neg_id,
2639 .operand_1 = zero_id,
2640 .operand_2 = elem_id,
2653
2654 const ext_inst: Word = switch (target.os.tag) {
2655 .opencl => switch (operand_info.class) {
2656 .float => 23, // fabs
2657 .integer, .strange_integer => switch (operand_info.signedness) {
2658 .signed => 141, // s_abs
2659 .unsigned => 201, // u_abs
2660 },
2661 .composite_integer => unreachable, // TODO
2662 .bool => unreachable,
2663 },
2664 .vulkan => switch (operand_info.class) {
2665 .float => 4, // FAbs
2666 .integer, .strange_integer => 5, // SAbs
2667 .composite_integer => unreachable, // TODO
2668 .bool => unreachable,
2669 },
2670 else => unreachable,
26412671 };
2642 switch (info.class) {
2643 .float => try self.func.body.emit(self.spv.gpa, .OpFSub, args),
2644 .integer, .strange_integer => try self.func.body.emit(self.spv.gpa, .OpISub, args),
2645 .composite_integer => unreachable, // TODO
2646 .bool => unreachable,
2647 }
2648 const neg_norm_id = try self.normalize(wip.ty_ref, neg_id, info);
2649
2650 const gt_zero_id = try self.cmp(.gt, Type.bool, operand_scalar_ty, elem_id, zero_id);
2651 const abs_id = self.spv.allocId();
2652 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2653 .id_result_type = self.typeId(operand_scalar_ty_ref),
2654 .id_result = abs_id,
2655 .condition = gt_zero_id,
2656 .object_1 = elem_id,
2657 .object_2 = neg_norm_id,
2672 const set_id = switch (target.os.tag) {
2673 .opencl => try self.spv.importInstructionSet(.opencl),
2674 .vulkan => try self.spv.importInstructionSet(.glsl),
2675 else => unreachable,
2676 };
2677
2678 result_id.* = self.spv.allocId();
2679 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2680 .id_result_type = wip.ty_id,
2681 .id_result = result_id.*,
2682 .set = set_id,
2683 .instruction = .{ .inst = ext_inst },
2684 .id_ref_4 = &.{elem_id},
26582685 });
2659 // For Shader, we may need to cast from signed to unsigned here.
2660 result_id.* = try self.bitCast(wip.ty, operand_scalar_ty, abs_id);
26612686 }
26622687 return try wip.finalize();
26632688 }
src/codegen/spirv/Module.zig+36-2
......@@ -114,8 +114,10 @@ sections: struct {
114114 capabilities: Section = .{},
115115 /// OpExtension instructions
116116 extensions: Section = .{},
117 // OpExtInstImport instructions - skip for now.
118 // memory model defined by target, not required here.
117 /// OpExtInstImport
118 extended_instruction_set: Section = .{},
119 /// memory model defined by target
120 memory_model: Section = .{},
119121 /// OpEntryPoint instructions - Handled by `self.entry_points`.
120122 /// OpExecutionMode and OpExecutionModeId instructions.
121123 execution_modes: Section = .{},
......@@ -172,6 +174,9 @@ globals: struct {
172174 section: Section = .{},
173175} = .{},
174176
177/// The list of extended instruction sets that should be imported.
178extended_instruction_set: std.AutoHashMapUnmanaged(ExtendedInstructionSet, IdRef) = .{},
179
175180pub fn init(gpa: Allocator) Module {
176181 return .{
177182 .gpa = gpa,
......@@ -182,6 +187,8 @@ pub fn init(gpa: Allocator) Module {
182187pub fn deinit(self: *Module) void {
183188 self.sections.capabilities.deinit(self.gpa);
184189 self.sections.extensions.deinit(self.gpa);
190 self.sections.extended_instruction_set.deinit(self.gpa);
191 self.sections.memory_model.deinit(self.gpa);
185192 self.sections.execution_modes.deinit(self.gpa);
186193 self.sections.debug_strings.deinit(self.gpa);
187194 self.sections.debug_names.deinit(self.gpa);
......@@ -200,6 +207,8 @@ pub fn deinit(self: *Module) void {
200207 self.globals.globals.deinit(self.gpa);
201208 self.globals.section.deinit(self.gpa);
202209
210 self.extended_instruction_set.deinit(self.gpa);
211
203212 self.* = undefined;
204213}
205214
......@@ -448,6 +457,8 @@ pub fn flush(self: *Module, file: std.fs.File, target: std.Target) !void {
448457 &header,
449458 self.sections.capabilities.toWords(),
450459 self.sections.extensions.toWords(),
460 self.sections.extended_instruction_set.toWords(),
461 self.sections.memory_model.toWords(),
451462 entry_points.toWords(),
452463 self.sections.execution_modes.toWords(),
453464 source.toWords(),
......@@ -482,6 +493,29 @@ pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void {
482493 try self.declareDeclDeps(decl_index, func.decl_deps.keys());
483494}
484495
496pub const ExtendedInstructionSet = enum {
497 glsl,
498 opencl,
499};
500
501/// Imports or returns the existing id of an extended instruction set
502pub fn importInstructionSet(self: *Module, set: ExtendedInstructionSet) !IdRef {
503 const gop = try self.extended_instruction_set.getOrPut(self.gpa, set);
504 if (gop.found_existing) return gop.value_ptr.*;
505
506 const result_id = self.allocId();
507 try self.sections.extended_instruction_set.emit(self.gpa, .OpExtInstImport, .{
508 .id_result = result_id,
509 .name = switch (set) {
510 .glsl => "GLSL.std.450",
511 .opencl => "OpenCL.std",
512 },
513 });
514 gop.value_ptr.* = result_id;
515
516 return result_id;
517}
518
485519/// Fetch the result-id of an OpString instruction that encodes the path of the source
486520/// file of the decl. This function may also emit an OpSource with source-level information regarding
487521/// the decl.
src/link/SpirV.zig+2-3
......@@ -246,7 +246,7 @@ fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
246246 const gpa = spv.gpa;
247247 // TODO: Integrate with a hypothetical feature system
248248 const caps: []const spec.Capability = switch (target.os.tag) {
249 .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .Float64, .Float16, .GenericPointer },
249 .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .Float64, .Float16, .Vector16, .GenericPointer },
250250 .glsl450 => &.{.Shader},
251251 .vulkan => &.{ .Shader, .VariablePointersStorageBuffer, .Int8, .Int16, .Int64, .Float64, .Float16 },
252252 else => unreachable, // TODO
......@@ -279,8 +279,7 @@ fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void {
279279 else => unreachable,
280280 };
281281
282 // TODO: Put this in a proper section.
283 try spv.sections.extensions.emit(gpa, .OpMemoryModel, .{
282 try spv.sections.memory_model.emit(gpa, .OpMemoryModel, .{
284283 .addressing_model = addressing_model,
285284 .memory_model = memory_model,
286285 });