| author | |
| committer | |
| log | aeae71f462d5a7c6a84e46c6635839c483c6acb5 |
| tree | 94a827002a029579e558a3f868566f94300a17af |
| parent | dfe34405406cf9b8f15fb0fc079a343fea4267a6 |
| parent | d1484bf4b96177135183916b95198de25dc63356 |
| signature |
spirv: test runner support19 files changed, 2664 insertions(+), 636 deletions(-)
lib/std/builtin.zig+1-2| ... | @@ -160,8 +160,7 @@ pub const CallingConvention = enum { | ... | @@ -160,8 +160,7 @@ pub const CallingConvention = enum { |
| 160 | AAPCSVFP, | 160 | AAPCSVFP, |
| 161 | SysV, | 161 | SysV, |
| 162 | Win64, | 162 | Win64, |
| 163 | PtxKernel, | 163 | Kernel, |
| 164 | AmdgpuKernel, | ||
| 165 | }; | 164 | }; |
| 166 | 165 | ||
| 167 | /// This data structure is used by the Zig language code generation and | 166 | /// This data structure is used by the Zig language code generation and |
lib/std/start.zig+10-1| ... | @@ -24,7 +24,9 @@ pub const simplified_logic = | ... | @@ -24,7 +24,9 @@ pub const simplified_logic = |
| 24 | builtin.zig_backend == .stage2_aarch64 or | 24 | builtin.zig_backend == .stage2_aarch64 or |
| 25 | builtin.zig_backend == .stage2_arm or | 25 | builtin.zig_backend == .stage2_arm or |
| 26 | builtin.zig_backend == .stage2_riscv64 or | 26 | builtin.zig_backend == .stage2_riscv64 or |
| 27 | builtin.zig_backend == .stage2_sparc64; | 27 | builtin.zig_backend == .stage2_sparc64 or |
| 28 | builtin.cpu.arch == .spirv32 or | ||
| 29 | builtin.cpu.arch == .spirv64; | ||
| 28 | 30 | ||
| 29 | comptime { | 31 | comptime { |
| 30 | // No matter what, we import the root file, so that any export, test, comptime | 32 | // No matter what, we import the root file, so that any export, test, comptime |
| ... | @@ -43,6 +45,9 @@ comptime { | ... | @@ -43,6 +45,9 @@ comptime { |
| 43 | } | 45 | } |
| 44 | } else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) { | 46 | } else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) { |
| 45 | @export(wasiMain2, .{ .name = "_start" }); | 47 | @export(wasiMain2, .{ .name = "_start" }); |
| 48 | } else if (builtin.os.tag == .opencl) { | ||
| 49 | if (@hasDecl(root, "main")) | ||
| 50 | @export(spirvMain2, .{ .name = "main" }); | ||
| 46 | } else { | 51 | } else { |
| 47 | if (!@hasDecl(root, "_start")) { | 52 | if (!@hasDecl(root, "_start")) { |
| 48 | @export(_start2, .{ .name = "_start" }); | 53 | @export(_start2, .{ .name = "_start" }); |
| ... | @@ -127,6 +132,10 @@ fn wasiMain2() callconv(.C) noreturn { | ... | @@ -127,6 +132,10 @@ fn wasiMain2() callconv(.C) noreturn { |
| 127 | } | 132 | } |
| 128 | } | 133 | } |
| 129 | 134 | ||
| 135 | fn spirvMain2() callconv(.Kernel) void { | ||
| 136 | root.main(); | ||
| 137 | } | ||
| 138 | |||
| 130 | fn wWinMainCRTStartup2() callconv(.C) noreturn { | 139 | fn wWinMainCRTStartup2() callconv(.C) noreturn { |
| 131 | root.main(); | 140 | root.main(); |
| 132 | exit2(0); | 141 | exit2(0); |
lib/std/target.zig+8-2| ... | @@ -944,7 +944,7 @@ pub const Target = struct { | ... | @@ -944,7 +944,7 @@ pub const Target = struct { |
| 944 | }; | 944 | }; |
| 945 | } | 945 | } |
| 946 | 946 | ||
| 947 | pub fn isSPIRV(arch: Arch) bool { | 947 | pub fn isSpirV(arch: Arch) bool { |
| 948 | return switch (arch) { | 948 | return switch (arch) { |
| 949 | .spirv32, .spirv64 => true, | 949 | .spirv32, .spirv64 => true, |
| 950 | else => false, | 950 | else => false, |
| ... | @@ -1276,7 +1276,7 @@ pub const Target = struct { | ... | @@ -1276,7 +1276,7 @@ pub const Target = struct { |
| 1276 | .x86, .x86_64 => "x86", | 1276 | .x86, .x86_64 => "x86", |
| 1277 | .nvptx, .nvptx64 => "nvptx", | 1277 | .nvptx, .nvptx64 => "nvptx", |
| 1278 | .wasm32, .wasm64 => "wasm", | 1278 | .wasm32, .wasm64 => "wasm", |
| 1279 | .spirv32, .spirv64 => "spir-v", | 1279 | .spirv32, .spirv64 => "spirv", |
| 1280 | else => @tagName(arch), | 1280 | else => @tagName(arch), |
| 1281 | }; | 1281 | }; |
| 1282 | } | 1282 | } |
| ... | @@ -1329,6 +1329,7 @@ pub const Target = struct { | ... | @@ -1329,6 +1329,7 @@ pub const Target = struct { |
| 1329 | .amdgcn => comptime allCpusFromDecls(amdgpu.cpu), | 1329 | .amdgcn => comptime allCpusFromDecls(amdgpu.cpu), |
| 1330 | .riscv32, .riscv64 => comptime allCpusFromDecls(riscv.cpu), | 1330 | .riscv32, .riscv64 => comptime allCpusFromDecls(riscv.cpu), |
| 1331 | .sparc, .sparc64, .sparcel => comptime allCpusFromDecls(sparc.cpu), | 1331 | .sparc, .sparc64, .sparcel => comptime allCpusFromDecls(sparc.cpu), |
| 1332 | .spirv32, .spirv64 => comptime allCpusFromDecls(spirv.cpu), | ||
| 1332 | .s390x => comptime allCpusFromDecls(s390x.cpu), | 1333 | .s390x => comptime allCpusFromDecls(s390x.cpu), |
| 1333 | .x86, .x86_64 => comptime allCpusFromDecls(x86.cpu), | 1334 | .x86, .x86_64 => comptime allCpusFromDecls(x86.cpu), |
| 1334 | .xtensa => comptime allCpusFromDecls(xtensa.cpu), | 1335 | .xtensa => comptime allCpusFromDecls(xtensa.cpu), |
| ... | @@ -1392,6 +1393,7 @@ pub const Target = struct { | ... | @@ -1392,6 +1393,7 @@ pub const Target = struct { |
| 1392 | .amdgcn => &amdgpu.cpu.generic, | 1393 | .amdgcn => &amdgpu.cpu.generic, |
| 1393 | .riscv32 => &riscv.cpu.generic_rv32, | 1394 | .riscv32 => &riscv.cpu.generic_rv32, |
| 1394 | .riscv64 => &riscv.cpu.generic_rv64, | 1395 | .riscv64 => &riscv.cpu.generic_rv64, |
| 1396 | .spirv32, .spirv64 => &spirv.cpu.generic, | ||
| 1395 | .sparc, .sparcel => &sparc.cpu.generic, | 1397 | .sparc, .sparcel => &sparc.cpu.generic, |
| 1396 | .sparc64 => &sparc.cpu.v9, // 64-bit SPARC needs v9 as the baseline | 1398 | .sparc64 => &sparc.cpu.v9, // 64-bit SPARC needs v9 as the baseline |
| 1397 | .s390x => &s390x.cpu.generic, | 1399 | .s390x => &s390x.cpu.generic, |
| ... | @@ -1532,6 +1534,10 @@ pub const Target = struct { | ... | @@ -1532,6 +1534,10 @@ pub const Target = struct { |
| 1532 | return !self.cpu.arch.isWasm(); | 1534 | return !self.cpu.arch.isWasm(); |
| 1533 | } | 1535 | } |
| 1534 | 1536 | ||
| 1537 | pub fn isSpirV(self: Target) bool { | ||
| 1538 | return self.cpu.arch.isSpirV(); | ||
| 1539 | } | ||
| 1540 | |||
| 1535 | pub const FloatAbi = enum { | 1541 | pub const FloatAbi = enum { |
| 1536 | hard, | 1542 | hard, |
| 1537 | soft, | 1543 | soft, |
lib/std/target/spirv.zig+8| ... | @@ -2081,3 +2081,11 @@ pub const all_features = blk: { | ... | @@ -2081,3 +2081,11 @@ pub const all_features = blk: { |
| 2081 | } | 2081 | } |
| 2082 | break :blk result; | 2082 | break :blk result; |
| 2083 | }; | 2083 | }; |
| 2084 | |||
| 2085 | pub const cpu = struct { | ||
| 2086 | pub const generic = CpuModel{ | ||
| 2087 | .name = "generic", | ||
| 2088 | .llvm_name = "generic", | ||
| 2089 | .features = featureSet(&[_]Feature{}), | ||
| 2090 | }; | ||
| 2091 | }; |
src/Compilation.zig+7-5| ... | @@ -722,10 +722,11 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { | ... | @@ -722,10 +722,11 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { |
| 722 | // Once they are capable this condition could be removed. When removing this condition, | 722 | // Once they are capable this condition could be removed. When removing this condition, |
| 723 | // also test the use case of `build-obj -fcompiler-rt` with the native backends | 723 | // also test the use case of `build-obj -fcompiler-rt` with the native backends |
| 724 | // and make sure the compiler-rt symbols are emitted. | 724 | // and make sure the compiler-rt symbols are emitted. |
| 725 | const capable_of_building_compiler_rt = build_options.have_llvm and options.target.os.tag != .plan9; | 725 | const is_p9 = options.target.os.tag == .plan9; |
| 726 | 726 | const is_spv = options.target.cpu.arch.isSpirV(); | |
| 727 | const capable_of_building_zig_libc = build_options.have_llvm and options.target.os.tag != .plan9; | 727 | const capable_of_building_compiler_rt = build_options.have_llvm and !is_p9 and !is_spv; |
| 728 | const capable_of_building_ssp = build_options.have_llvm and options.target.os.tag != .plan9; | 728 | const capable_of_building_zig_libc = build_options.have_llvm and !is_p9 and !is_spv; |
| 729 | const capable_of_building_ssp = build_options.have_llvm and !is_p9 and !is_spv; | ||
| 729 | 730 | ||
| 730 | const comp: *Compilation = comp: { | 731 | const comp: *Compilation = comp: { |
| 731 | // For allocations that have the same lifetime as Compilation. This arena is used only during this | 732 | // For allocations that have the same lifetime as Compilation. This arena is used only during this |
| ... | @@ -1948,8 +1949,9 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void | ... | @@ -1948,8 +1949,9 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void |
| 1948 | .sub_path = std.fs.path.basename(sub_path), | 1949 | .sub_path = std.fs.path.basename(sub_path), |
| 1949 | }; | 1950 | }; |
| 1950 | } | 1951 | } |
| 1951 | comp.bin_file.destroy(); | 1952 | var old_bin_file = comp.bin_file; |
| 1952 | comp.bin_file = try link.File.openPath(comp.gpa, options); | 1953 | comp.bin_file = try link.File.openPath(comp.gpa, options); |
| 1954 | old_bin_file.destroy(); | ||
| 1953 | } | 1955 | } |
| 1954 | 1956 | ||
| 1955 | // For compiling C objects, we rely on the cache hash system to avoid duplicating work. | 1957 | // For compiling C objects, we rely on the cache hash system to avoid duplicating work. |
src/Sema.zig+15-17| ... | @@ -8883,7 +8883,7 @@ fn funcCommon( | ... | @@ -8883,7 +8883,7 @@ fn funcCommon( |
| 8883 | }; | 8883 | }; |
| 8884 | return sema.failWithOwnedErrorMsg(msg); | 8884 | return sema.failWithOwnedErrorMsg(msg); |
| 8885 | } | 8885 | } |
| 8886 | if (!ret_poison and !Type.fnCallingConventionAllowsZigTypes(cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) { | 8886 | if (!ret_poison and !Type.fnCallingConventionAllowsZigTypes(target, cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) { |
| 8887 | const msg = msg: { | 8887 | const msg = msg: { |
| 8888 | const msg = try sema.errMsg(block, ret_ty_src, "return type '{}' not allowed in function with calling convention '{s}'", .{ | 8888 | const msg = try sema.errMsg(block, ret_ty_src, "return type '{}' not allowed in function with calling convention '{s}'", .{ |
| 8889 | return_type.fmt(sema.mod), @tagName(cc_resolved), | 8889 | return_type.fmt(sema.mod), @tagName(cc_resolved), |
| ... | @@ -8961,13 +8961,9 @@ fn funcCommon( | ... | @@ -8961,13 +8961,9 @@ fn funcCommon( |
| 8961 | .x86_64 => null, | 8961 | .x86_64 => null, |
| 8962 | else => @as([]const u8, "x86_64"), | 8962 | else => @as([]const u8, "x86_64"), |
| 8963 | }, | 8963 | }, |
| 8964 | .PtxKernel => switch (arch) { | 8964 | .Kernel => switch (arch) { |
| 8965 | .nvptx, .nvptx64 => null, | 8965 | .nvptx, .nvptx64, .amdgcn, .spirv32, .spirv64 => null, |
| 8966 | else => @as([]const u8, "nvptx and nvptx64"), | 8966 | else => @as([]const u8, "nvptx, amdgcn and SPIR-V"), |
| 8967 | }, | ||
| 8968 | .AmdgpuKernel => switch (arch) { | ||
| 8969 | .amdgcn => null, | ||
| 8970 | else => @as([]const u8, "amdgcn"), | ||
| 8971 | }, | 8967 | }, |
| 8972 | }) |allowed_platform| { | 8968 | }) |allowed_platform| { |
| 8973 | return sema.fail(block, cc_src, "callconv '{s}' is only available on {s}, not {s}", .{ | 8969 | return sema.fail(block, cc_src, "callconv '{s}' is only available on {s}, not {s}", .{ |
| ... | @@ -9093,10 +9089,11 @@ fn analyzeParameter( | ... | @@ -9093,10 +9089,11 @@ fn analyzeParameter( |
| 9093 | comptime_params[i] = param.is_comptime or requires_comptime; | 9089 | comptime_params[i] = param.is_comptime or requires_comptime; |
| 9094 | const this_generic = param.ty.tag() == .generic_poison; | 9090 | const this_generic = param.ty.tag() == .generic_poison; |
| 9095 | is_generic.* = is_generic.* or this_generic; | 9091 | is_generic.* = is_generic.* or this_generic; |
| 9096 | if (param.is_comptime and !Type.fnCallingConventionAllowsZigTypes(cc)) { | 9092 | const target = sema.mod.getTarget(); |
| 9093 | if (param.is_comptime and !Type.fnCallingConventionAllowsZigTypes(target, cc)) { | ||
| 9097 | return sema.fail(block, param_src, "comptime parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)}); | 9094 | return sema.fail(block, param_src, "comptime parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)}); |
| 9098 | } | 9095 | } |
| 9099 | if (this_generic and !sema.no_partial_func_ty and !Type.fnCallingConventionAllowsZigTypes(cc)) { | 9096 | if (this_generic and !sema.no_partial_func_ty and !Type.fnCallingConventionAllowsZigTypes(target, cc)) { |
| 9100 | return sema.fail(block, param_src, "generic parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)}); | 9097 | return sema.fail(block, param_src, "generic parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)}); |
| 9101 | } | 9098 | } |
| 9102 | if (!param.ty.isValidParamType()) { | 9099 | if (!param.ty.isValidParamType()) { |
| ... | @@ -9112,7 +9109,7 @@ fn analyzeParameter( | ... | @@ -9112,7 +9109,7 @@ fn analyzeParameter( |
| 9112 | }; | 9109 | }; |
| 9113 | return sema.failWithOwnedErrorMsg(msg); | 9110 | return sema.failWithOwnedErrorMsg(msg); |
| 9114 | } | 9111 | } |
| 9115 | if (!this_generic and !Type.fnCallingConventionAllowsZigTypes(cc) and !try sema.validateExternType(param.ty, .param_ty)) { | 9112 | if (!this_generic and !Type.fnCallingConventionAllowsZigTypes(target, cc) and !try sema.validateExternType(param.ty, .param_ty)) { |
| 9116 | const msg = msg: { | 9113 | const msg = msg: { |
| 9117 | const msg = try sema.errMsg(block, param_src, "parameter of type '{}' not allowed in function with calling convention '{s}'", .{ | 9114 | const msg = try sema.errMsg(block, param_src, "parameter of type '{}' not allowed in function with calling convention '{s}'", .{ |
| 9118 | param.ty.fmt(sema.mod), @tagName(cc), | 9115 | param.ty.fmt(sema.mod), @tagName(cc), |
| ... | @@ -22786,12 +22783,13 @@ fn validateExternType( | ... | @@ -22786,12 +22783,13 @@ fn validateExternType( |
| 22786 | }, | 22783 | }, |
| 22787 | .Fn => { | 22784 | .Fn => { |
| 22788 | if (position != .other) return false; | 22785 | if (position != .other) return false; |
| 22789 | return switch (ty.fnCallingConvention()) { | 22786 | const target = sema.mod.getTarget(); |
| 22790 | // For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI. | 22787 | // For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI. |
| 22791 | // The goal is to experiment with more integrated CPU/GPU code. | 22788 | // The goal is to experiment with more integrated CPU/GPU code. |
| 22792 | .PtxKernel => true, | 22789 | if (ty.fnCallingConvention() == .Kernel and (target.cpu.arch == .nvptx or target.cpu.arch == .nvptx64)) { |
| 22793 | else => !Type.fnCallingConventionAllowsZigTypes(ty.fnCallingConvention()), | 22790 | return true; |
| 22794 | }; | 22791 | } |
| 22792 | return !Type.fnCallingConventionAllowsZigTypes(target, ty.fnCallingConvention()); | ||
| 22795 | }, | 22793 | }, |
| 22796 | .Enum => { | 22794 | .Enum => { |
| 22797 | var buf: Type.Payload.Bits = undefined; | 22795 | var buf: Type.Payload.Bits = undefined; |
src/codegen/llvm.zig+1-4| ... | @@ -10350,11 +10350,8 @@ fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) llvm.Ca | ... | @@ -10350,11 +10350,8 @@ fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) llvm.Ca |
| 10350 | .Signal => .AVR_SIGNAL, | 10350 | .Signal => .AVR_SIGNAL, |
| 10351 | .SysV => .X86_64_SysV, | 10351 | .SysV => .X86_64_SysV, |
| 10352 | .Win64 => .Win64, | 10352 | .Win64 => .Win64, |
| 10353 | .PtxKernel => return switch (target.cpu.arch) { | 10353 | .Kernel => return switch (target.cpu.arch) { |
| 10354 | .nvptx, .nvptx64 => .PTX_Kernel, | 10354 | .nvptx, .nvptx64 => .PTX_Kernel, |
| 10355 | else => unreachable, | ||
| 10356 | }, | ||
| 10357 | .AmdgpuKernel => return switch (target.cpu.arch) { | ||
| 10358 | .amdgcn => .AMDGPU_KERNEL, | 10355 | .amdgcn => .AMDGPU_KERNEL, |
| 10359 | else => unreachable, | 10356 | else => unreachable, |
| 10360 | }, | 10357 | }, |
src/codegen/spirv.zig+1814-237| ... | @@ -19,6 +19,7 @@ const Word = spec.Word; | ... | @@ -19,6 +19,7 @@ const Word = spec.Word; |
| 19 | const IdRef = spec.IdRef; | 19 | const IdRef = spec.IdRef; |
| 20 | const IdResult = spec.IdResult; | 20 | const IdResult = spec.IdResult; |
| 21 | const IdResultType = spec.IdResultType; | 21 | const IdResultType = spec.IdResultType; |
| 22 | const StorageClass = spec.StorageClass; | ||
| 22 | 23 | ||
| 23 | const SpvModule = @import("spirv/Module.zig"); | 24 | const SpvModule = @import("spirv/Module.zig"); |
| 24 | const SpvSection = @import("spirv/Section.zig"); | 25 | const SpvSection = @import("spirv/Section.zig"); |
| ... | @@ -32,11 +33,14 @@ const IncomingBlock = struct { | ... | @@ -32,11 +33,14 @@ const IncomingBlock = struct { |
| 32 | break_value_id: IdRef, | 33 | break_value_id: IdRef, |
| 33 | }; | 34 | }; |
| 34 | 35 | ||
| 35 | pub const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct { | 36 | const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct { |
| 36 | label_id: IdRef, | 37 | label_id: IdRef, |
| 37 | incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock), | 38 | incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock), |
| 38 | }); | 39 | }); |
| 39 | 40 | ||
| 41 | /// Maps Zig decl indices to linking SPIR-V linking information. | ||
| 42 | pub const DeclLinkMap = std.AutoHashMap(Module.Decl.Index, SpvModule.Decl.Index); | ||
| 43 | |||
| 40 | /// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that. | 44 | /// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that. |
| 41 | pub const DeclGen = struct { | 45 | pub const DeclGen = struct { |
| 42 | /// A general-purpose allocator that can be used for any allocations for this DeclGen. | 46 | /// A general-purpose allocator that can be used for any allocations for this DeclGen. |
| ... | @@ -59,7 +63,8 @@ pub const DeclGen = struct { | ... | @@ -59,7 +63,8 @@ pub const DeclGen = struct { |
| 59 | /// Note: If the declaration is not a function, this value will be undefined! | 63 | /// Note: If the declaration is not a function, this value will be undefined! |
| 60 | liveness: Liveness, | 64 | liveness: Liveness, |
| 61 | 65 | ||
| 62 | ids: *const std.AutoHashMap(Decl.Index, IdResult), | 66 | /// Maps Zig Decl indices to SPIR-V globals. |
| 67 | decl_link: *DeclLinkMap, | ||
| 63 | 68 | ||
| 64 | /// An array of function argument result-ids. Each index corresponds with the | 69 | /// An array of function argument result-ids. Each index corresponds with the |
| 65 | /// function argument of the same index. | 70 | /// function argument of the same index. |
| ... | @@ -133,13 +138,23 @@ pub const DeclGen = struct { | ... | @@ -133,13 +138,23 @@ pub const DeclGen = struct { |
| 133 | class: Class, | 138 | class: Class, |
| 134 | }; | 139 | }; |
| 135 | 140 | ||
| 141 | /// Data can be lowered into in two basic representations: indirect, which is when | ||
| 142 | /// a type is stored in memory, and direct, which is how a type is stored when its | ||
| 143 | /// a direct SPIR-V value. | ||
| 144 | const Repr = enum { | ||
| 145 | /// A SPIR-V value as it would be used in operations. | ||
| 146 | direct, | ||
| 147 | /// A SPIR-V value as it is stored in memory. | ||
| 148 | indirect, | ||
| 149 | }; | ||
| 150 | |||
| 136 | /// Initialize the common resources of a DeclGen. Some fields are left uninitialized, | 151 | /// Initialize the common resources of a DeclGen. Some fields are left uninitialized, |
| 137 | /// only set when `gen` is called. | 152 | /// only set when `gen` is called. |
| 138 | pub fn init( | 153 | pub fn init( |
| 139 | allocator: Allocator, | 154 | allocator: Allocator, |
| 140 | module: *Module, | 155 | module: *Module, |
| 141 | spv: *SpvModule, | 156 | spv: *SpvModule, |
| 142 | ids: *const std.AutoHashMap(Decl.Index, IdResult), | 157 | decl_link: *DeclLinkMap, |
| 143 | ) DeclGen { | 158 | ) DeclGen { |
| 144 | return .{ | 159 | return .{ |
| 145 | .gpa = allocator, | 160 | .gpa = allocator, |
| ... | @@ -148,7 +163,7 @@ pub const DeclGen = struct { | ... | @@ -148,7 +163,7 @@ pub const DeclGen = struct { |
| 148 | .decl_index = undefined, | 163 | .decl_index = undefined, |
| 149 | .air = undefined, | 164 | .air = undefined, |
| 150 | .liveness = undefined, | 165 | .liveness = undefined, |
| 151 | .ids = ids, | 166 | .decl_link = decl_link, |
| 152 | .next_arg_index = undefined, | 167 | .next_arg_index = undefined, |
| 153 | .current_block_label_id = undefined, | 168 | .current_block_label_id = undefined, |
| 154 | .error_msg = undefined, | 169 | .error_msg = undefined, |
| ... | @@ -215,19 +230,50 @@ pub const DeclGen = struct { | ... | @@ -215,19 +230,50 @@ pub const DeclGen = struct { |
| 215 | /// Fetch the result-id for a previously generated instruction or constant. | 230 | /// Fetch the result-id for a previously generated instruction or constant. |
| 216 | fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !IdRef { | 231 | fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !IdRef { |
| 217 | if (self.air.value(inst)) |val| { | 232 | if (self.air.value(inst)) |val| { |
| 218 | return self.genConstant(self.air.typeOf(inst), val); | 233 | const ty = self.air.typeOf(inst); |
| 234 | if (ty.zigTypeTag() == .Fn) { | ||
| 235 | const fn_decl_index = switch (val.tag()) { | ||
| 236 | .extern_fn => val.castTag(.extern_fn).?.data.owner_decl, | ||
| 237 | .function => val.castTag(.function).?.data.owner_decl, | ||
| 238 | else => unreachable, | ||
| 239 | }; | ||
| 240 | const spv_decl_index = try self.resolveDecl(fn_decl_index); | ||
| 241 | return self.spv.declPtr(spv_decl_index).result_id; | ||
| 242 | } | ||
| 243 | |||
| 244 | return try self.constant(ty, val); | ||
| 219 | } | 245 | } |
| 220 | const index = Air.refToIndex(inst).?; | 246 | const index = Air.refToIndex(inst).?; |
| 221 | return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage. | 247 | return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage. |
| 222 | } | 248 | } |
| 223 | 249 | ||
| 250 | /// Fetch or allocate a result id for decl index. This function also marks the decl as alive. | ||
| 251 | /// Note: Function does not actually generate the decl. | ||
| 252 | fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !SpvModule.Decl.Index { | ||
| 253 | const decl = self.module.declPtr(decl_index); | ||
| 254 | self.module.markDeclAlive(decl); | ||
| 255 | |||
| 256 | const entry = try self.decl_link.getOrPut(decl_index); | ||
| 257 | if (!entry.found_existing) { | ||
| 258 | // TODO: Extern fn? | ||
| 259 | const kind: SpvModule.DeclKind = if (decl.val.tag() == .function) | ||
| 260 | .func | ||
| 261 | else | ||
| 262 | .global; | ||
| 263 | |||
| 264 | entry.value_ptr.* = try self.spv.allocDecl(kind); | ||
| 265 | } | ||
| 266 | |||
| 267 | return entry.value_ptr.*; | ||
| 268 | } | ||
| 269 | |||
| 224 | /// Start a new SPIR-V block, Emits the label of the new block, and stores which | 270 | /// Start a new SPIR-V block, Emits the label of the new block, and stores which |
| 225 | /// block we are currently generating. | 271 | /// block we are currently generating. |
| 226 | /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to | 272 | /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to |
| 227 | /// keep track of the previous block. | 273 | /// keep track of the previous block. |
| 228 | fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void { | 274 | fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void { |
| 229 | try self.func.body.emit(self.spv.gpa, .OpLabel, .{ .id_result = label_id }); | 275 | try self.func.body.emit(self.spv.gpa, .OpLabel, .{ .id_result = label_id }); |
| 230 | self.current_block_label_id = label_id.toRef(); | 276 | self.current_block_label_id = label_id; |
| 231 | } | 277 | } |
| 232 | 278 | ||
| 233 | /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need | 279 | /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need |
| ... | @@ -325,150 +371,752 @@ pub const DeclGen = struct { | ... | @@ -325,150 +371,752 @@ pub const DeclGen = struct { |
| 325 | // As of yet, there is no vector support in the self-hosted compiler. | 371 | // As of yet, there is no vector support in the self-hosted compiler. |
| 326 | .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}), | 372 | .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}), |
| 327 | // TODO: For which types is this the case? | 373 | // TODO: For which types is this the case? |
| 328 | else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmtDebug()}), | 374 | else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmt(self.module)}), |
| 329 | }; | 375 | }; |
| 330 | } | 376 | } |
| 331 | 377 | ||
| 332 | /// Generate a constant representing `val`. | 378 | fn genConstInt(self: *DeclGen, ty_ref: SpvType.Ref, result_id: IdRef, value: anytype) !void { |
| 333 | /// TODO: Deduplication? | 379 | const ty = self.spv.typeRefType(ty_ref); |
| 334 | fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!IdRef { | 380 | const ty_id = self.typeId(ty_ref); |
| 335 | if (ty.zigTypeTag() == .Fn) { | 381 | |
| 336 | const fn_decl_index = switch (val.tag()) { | 382 | const Lit = spec.LiteralContextDependentNumber; |
| 337 | .extern_fn => val.castTag(.extern_fn).?.data.owner_decl, | 383 | const literal = switch (ty.intSignedness()) { |
| 338 | .function => val.castTag(.function).?.data.owner_decl, | 384 | .signed => switch (ty.intFloatBits()) { |
| 385 | 1...32 => Lit{ .int32 = @intCast(i32, value) }, | ||
| 386 | 33...64 => Lit{ .int64 = @intCast(i64, value) }, | ||
| 387 | else => unreachable, // TODO: composite integer literals | ||
| 388 | }, | ||
| 389 | .unsigned => switch (ty.intFloatBits()) { | ||
| 390 | 1...32 => Lit{ .uint32 = @intCast(u32, value) }, | ||
| 391 | 33...64 => Lit{ .uint64 = @intCast(u64, value) }, | ||
| 339 | else => unreachable, | 392 | else => unreachable, |
| 393 | }, | ||
| 394 | }; | ||
| 395 | |||
| 396 | try self.spv.emitConstant(ty_id, result_id, literal); | ||
| 397 | } | ||
| 398 | |||
| 399 | fn constInt(self: *DeclGen, ty_ref: SpvType.Ref, value: anytype) !IdRef { | ||
| 400 | const result_id = self.spv.allocId(); | ||
| 401 | try self.genConstInt(ty_ref, result_id, value); | ||
| 402 | return result_id; | ||
| 403 | } | ||
| 404 | |||
| 405 | fn genUndef(self: *DeclGen, ty_ref: SpvType.Ref) Error!IdRef { | ||
| 406 | const result_id = self.spv.allocId(); | ||
| 407 | try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id }); | ||
| 408 | return result_id; | ||
| 409 | } | ||
| 410 | |||
| 411 | const IndirectConstantLowering = struct { | ||
| 412 | const undef = 0xAA; | ||
| 413 | |||
| 414 | dg: *DeclGen, | ||
| 415 | /// Cached reference of the u32 type. | ||
| 416 | u32_ty_ref: SpvType.Ref, | ||
| 417 | /// Cached type id of the u32 type. | ||
| 418 | u32_ty_id: IdRef, | ||
| 419 | /// The members of the resulting structure type | ||
| 420 | members: std.ArrayList(SpvType.Payload.Struct.Member), | ||
| 421 | /// The initializers of each of the members. | ||
| 422 | initializers: std.ArrayList(IdRef), | ||
| 423 | /// The current size of the structure. Includes | ||
| 424 | /// the bytes in partial_word. | ||
| 425 | size: u32 = 0, | ||
| 426 | /// The partially filled last constant. | ||
| 427 | /// If full, its flushed. | ||
| 428 | partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{}, | ||
| 429 | /// The declaration dependencies of the constant we are lowering. | ||
| 430 | decl_deps: std.ArrayList(SpvModule.Decl.Index), | ||
| 431 | |||
| 432 | /// Utility function to get the section that instructions should be lowered to. | ||
| 433 | fn section(self: *@This()) *SpvSection { | ||
| 434 | return &self.dg.spv.globals.section; | ||
| 435 | } | ||
| 436 | |||
| 437 | /// Flush the partial_word to the members. If the partial_word is not | ||
| 438 | /// filled, this adds padding bytes (which are undefined). | ||
| 439 | fn flush(self: *@This()) !void { | ||
| 440 | if (self.partial_word.len == 0) { | ||
| 441 | // No need to add it there. | ||
| 442 | return; | ||
| 443 | } | ||
| 444 | |||
| 445 | for (self.partial_word.unusedCapacitySlice()) |*unused| { | ||
| 446 | // TODO: Perhaps we should generate OpUndef for these bytes? | ||
| 447 | unused.* = undef; | ||
| 448 | } | ||
| 449 | |||
| 450 | const word = @bitCast(Word, self.partial_word.buffer); | ||
| 451 | const result_id = self.dg.spv.allocId(); | ||
| 452 | // TODO: Integrate with caching mechanism | ||
| 453 | try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word }); | ||
| 454 | try self.members.append(.{ .ty = self.u32_ty_ref }); | ||
| 455 | try self.initializers.append(result_id); | ||
| 456 | |||
| 457 | self.partial_word.len = 0; | ||
| 458 | self.size = std.mem.alignForwardGeneric(u32, self.size, @sizeOf(Word)); | ||
| 459 | } | ||
| 460 | |||
| 461 | /// Fill the buffer with undefined values until the size is aligned to `align`. | ||
| 462 | fn fillToAlign(self: *@This(), alignment: u32) !void { | ||
| 463 | const target_size = std.mem.alignForwardGeneric(u32, self.size, alignment); | ||
| 464 | try self.addUndef(target_size - self.size); | ||
| 465 | } | ||
| 466 | |||
| 467 | fn addUndef(self: *@This(), amt: u64) !void { | ||
| 468 | for (0..@intCast(usize, amt)) |_| { | ||
| 469 | try self.addByte(undef); | ||
| 470 | } | ||
| 471 | } | ||
| 472 | |||
| 473 | /// Add a single byte of data to the constant. | ||
| 474 | fn addByte(self: *@This(), data: u8) !void { | ||
| 475 | self.partial_word.append(data) catch { | ||
| 476 | try self.flush(); | ||
| 477 | self.partial_word.append(data) catch unreachable; | ||
| 340 | }; | 478 | }; |
| 341 | const decl = self.module.declPtr(fn_decl_index); | 479 | self.size += 1; |
| 342 | self.module.markDeclAlive(decl); | 480 | } |
| 343 | return self.ids.get(fn_decl_index).?.toRef(); | 481 | |
| 482 | /// Add many bytes of data to the constnat. | ||
| 483 | fn addBytes(self: *@This(), data: []const u8) !void { | ||
| 484 | // TODO: Improve performance by adding in bulk, or something? | ||
| 485 | for (data) |byte| { | ||
| 486 | try self.addByte(byte); | ||
| 487 | } | ||
| 344 | } | 488 | } |
| 345 | 489 | ||
| 490 | fn addPtr(self: *@This(), ptr_ty_ref: SpvType.Ref, ptr_id: IdRef) !void { | ||
| 491 | // TODO: Double check pointer sizes here. | ||
| 492 | // shared pointers might be u32... | ||
| 493 | const target = self.dg.getTarget(); | ||
| 494 | const width = @divExact(target.cpu.arch.ptrBitWidth(), 8); | ||
| 495 | if (self.size % width != 0) { | ||
| 496 | return self.dg.todo("misaligned pointer constants", .{}); | ||
| 497 | } | ||
| 498 | try self.members.append(.{ .ty = ptr_ty_ref }); | ||
| 499 | try self.initializers.append(ptr_id); | ||
| 500 | self.size += width; | ||
| 501 | } | ||
| 502 | |||
| 503 | fn addNullPtr(self: *@This(), ptr_ty_ref: SpvType.Ref) !void { | ||
| 504 | const result_id = self.dg.spv.allocId(); | ||
| 505 | try self.dg.spv.sections.types_globals_constants.emit(self.dg.spv.gpa, .OpConstantNull, .{ | ||
| 506 | .id_result_type = self.dg.typeId(ptr_ty_ref), | ||
| 507 | .id_result = result_id, | ||
| 508 | }); | ||
| 509 | try self.addPtr(ptr_ty_ref, result_id); | ||
| 510 | } | ||
| 511 | |||
| 512 | fn addConstInt(self: *@This(), comptime T: type, value: T) !void { | ||
| 513 | if (@bitSizeOf(T) % 8 != 0) { | ||
| 514 | @compileError("todo: non byte aligned int constants"); | ||
| 515 | } | ||
| 516 | |||
| 517 | // TODO: Swap endianness if the compiler is big endian. | ||
| 518 | try self.addBytes(std.mem.asBytes(&value)); | ||
| 519 | } | ||
| 520 | |||
| 521 | fn addConstBool(self: *@This(), value: bool) !void { | ||
| 522 | try self.addByte(@boolToInt(value)); // TODO: Keep in sync with something? | ||
| 523 | } | ||
| 524 | |||
| 525 | fn addInt(self: *@This(), ty: Type, val: Value) !void { | ||
| 526 | const target = self.dg.getTarget(); | ||
| 527 | const int_info = ty.intInfo(target); | ||
| 528 | const int_bits = switch (int_info.signedness) { | ||
| 529 | .signed => @bitCast(u64, val.toSignedInt(target)), | ||
| 530 | .unsigned => val.toUnsignedInt(target), | ||
| 531 | }; | ||
| 532 | |||
| 533 | // TODO: Swap endianess if the compiler is big endian. | ||
| 534 | const len = ty.abiSize(target); | ||
| 535 | try self.addBytes(std.mem.asBytes(&int_bits)[0..@intCast(usize, len)]); | ||
| 536 | } | ||
| 537 | |||
| 538 | fn addDeclRef(self: *@This(), ty: Type, decl_index: Decl.Index) !void { | ||
| 539 | const dg = self.dg; | ||
| 540 | |||
| 541 | const ty_ref = try self.dg.resolveType(ty, .indirect); | ||
| 542 | const ty_id = dg.typeId(ty_ref); | ||
| 543 | |||
| 544 | const decl = dg.module.declPtr(decl_index); | ||
| 545 | const spv_decl_index = try dg.resolveDecl(decl_index); | ||
| 546 | |||
| 547 | switch (decl.val.tag()) { | ||
| 548 | .function => { | ||
| 549 | // TODO: Properly lower function pointers. For now we are going to hack around it and | ||
| 550 | // just generate an empty pointer. Function pointers are represented by usize for now, | ||
| 551 | // though. | ||
| 552 | try self.addInt(Type.usize, Value.initTag(.zero)); | ||
| 553 | return; | ||
| 554 | }, | ||
| 555 | .extern_fn => unreachable, // TODO | ||
| 556 | else => { | ||
| 557 | const result_id = dg.spv.allocId(); | ||
| 558 | log.debug("addDeclRef {s} = {}", .{ decl.name, result_id.id }); | ||
| 559 | |||
| 560 | try self.decl_deps.append(spv_decl_index); | ||
| 561 | |||
| 562 | const decl_id = dg.spv.declPtr(spv_decl_index).result_id; | ||
| 563 | // TODO: Do we need a storage class cast here? | ||
| 564 | // TODO: We can probably eliminate these casts | ||
| 565 | try dg.spv.globals.section.emitSpecConstantOp(dg.spv.gpa, .OpBitcast, .{ | ||
| 566 | .id_result_type = ty_id, | ||
| 567 | .id_result = result_id, | ||
| 568 | .operand = decl_id, | ||
| 569 | }); | ||
| 570 | |||
| 571 | try self.addPtr(ty_ref, result_id); | ||
| 572 | }, | ||
| 573 | } | ||
| 574 | } | ||
| 575 | |||
| 576 | fn lower(self: *@This(), ty: Type, val: Value) !void { | ||
| 577 | const target = self.dg.getTarget(); | ||
| 578 | const dg = self.dg; | ||
| 579 | |||
| 580 | if (val.isUndef()) { | ||
| 581 | const size = ty.abiSize(target); | ||
| 582 | return try self.addUndef(size); | ||
| 583 | } | ||
| 584 | |||
| 585 | switch (ty.zigTypeTag()) { | ||
| 586 | .Int => try self.addInt(ty, val), | ||
| 587 | .Bool => try self.addConstBool(val.toBool()), | ||
| 588 | .Array => switch (val.tag()) { | ||
| 589 | .aggregate => { | ||
| 590 | const elem_vals = val.castTag(.aggregate).?.data; | ||
| 591 | const elem_ty = ty.elemType(); | ||
| 592 | const len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way. | ||
| 593 | for (elem_vals[0..len]) |elem_val| { | ||
| 594 | try self.lower(elem_ty, elem_val); | ||
| 595 | } | ||
| 596 | }, | ||
| 597 | .repeated => { | ||
| 598 | const elem_val = val.castTag(.repeated).?.data; | ||
| 599 | const elem_ty = ty.elemType(); | ||
| 600 | const len = @intCast(u32, ty.arrayLen()); | ||
| 601 | for (0..len) |_| { | ||
| 602 | try self.lower(elem_ty, elem_val); | ||
| 603 | } | ||
| 604 | if (ty.sentinel()) |sentinel| { | ||
| 605 | try self.lower(elem_ty, sentinel); | ||
| 606 | } | ||
| 607 | }, | ||
| 608 | .str_lit => { | ||
| 609 | const str_lit = val.castTag(.str_lit).?.data; | ||
| 610 | const bytes = dg.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len]; | ||
| 611 | try self.addBytes(bytes); | ||
| 612 | if (ty.sentinel()) |sentinel| { | ||
| 613 | try self.addByte(@intCast(u8, sentinel.toUnsignedInt(target))); | ||
| 614 | } | ||
| 615 | }, | ||
| 616 | .bytes => { | ||
| 617 | const bytes = val.castTag(.bytes).?.data; | ||
| 618 | try self.addBytes(bytes); | ||
| 619 | }, | ||
| 620 | else => |tag| return dg.todo("indirect array constant with tag {s}", .{@tagName(tag)}), | ||
| 621 | }, | ||
| 622 | .Pointer => switch (val.tag()) { | ||
| 623 | .decl_ref_mut => { | ||
| 624 | const decl_index = val.castTag(.decl_ref_mut).?.data.decl_index; | ||
| 625 | try self.addDeclRef(ty, decl_index); | ||
| 626 | }, | ||
| 627 | .decl_ref => { | ||
| 628 | const decl_index = val.castTag(.decl_ref).?.data; | ||
| 629 | try self.addDeclRef(ty, decl_index); | ||
| 630 | }, | ||
| 631 | .slice => { | ||
| 632 | const slice = val.castTag(.slice).?.data; | ||
| 633 | |||
| 634 | var buf: Type.SlicePtrFieldTypeBuffer = undefined; | ||
| 635 | const ptr_ty = ty.slicePtrFieldType(&buf); | ||
| 636 | |||
| 637 | try self.lower(ptr_ty, slice.ptr); | ||
| 638 | try self.addInt(Type.usize, slice.len); | ||
| 639 | }, | ||
| 640 | else => |tag| return dg.todo("pointer value of type {s}", .{@tagName(tag)}), | ||
| 641 | }, | ||
| 642 | .Struct => { | ||
| 643 | if (ty.isSimpleTupleOrAnonStruct()) { | ||
| 644 | unreachable; // TODO | ||
| 645 | } else { | ||
| 646 | const struct_ty = ty.castTag(.@"struct").?.data; | ||
| 647 | |||
| 648 | if (struct_ty.layout == .Packed) { | ||
| 649 | return dg.todo("packed struct constants", .{}); | ||
| 650 | } | ||
| 651 | |||
| 652 | const struct_begin = self.size; | ||
| 653 | const field_vals = val.castTag(.aggregate).?.data; | ||
| 654 | for (struct_ty.fields.values(), 0..) |field, i| { | ||
| 655 | if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; | ||
| 656 | try self.lower(field.ty, field_vals[i]); | ||
| 657 | |||
| 658 | // Add padding if required. | ||
| 659 | // TODO: Add to type generation as well? | ||
| 660 | const unpadded_field_end = self.size - struct_begin; | ||
| 661 | const padded_field_end = ty.structFieldOffset(i + 1, target); | ||
| 662 | const padding = padded_field_end - unpadded_field_end; | ||
| 663 | try self.addUndef(padding); | ||
| 664 | } | ||
| 665 | } | ||
| 666 | }, | ||
| 667 | .Optional => { | ||
| 668 | var opt_buf: Type.Payload.ElemType = undefined; | ||
| 669 | const payload_ty = ty.optionalChild(&opt_buf); | ||
| 670 | const has_payload = !val.isNull(); | ||
| 671 | const abi_size = ty.abiSize(target); | ||
| 672 | |||
| 673 | if (!payload_ty.hasRuntimeBits()) { | ||
| 674 | try self.addConstBool(has_payload); | ||
| 675 | return; | ||
| 676 | } else if (ty.optionalReprIsPayload()) { | ||
| 677 | // Optional representation is a nullable pointer. | ||
| 678 | if (val.castTag(.opt_payload)) |payload| { | ||
| 679 | try self.lower(payload_ty, payload.data); | ||
| 680 | } else if (has_payload) { | ||
| 681 | try self.lower(payload_ty, val); | ||
| 682 | } else { | ||
| 683 | const ptr_ty_ref = try dg.resolveType(ty, .indirect); | ||
| 684 | try self.addNullPtr(ptr_ty_ref); | ||
| 685 | } | ||
| 686 | return; | ||
| 687 | } | ||
| 688 | |||
| 689 | // Optional representation is a structure. | ||
| 690 | // { Payload, Bool } | ||
| 691 | |||
| 692 | // Subtract 1 for @sizeOf(bool). | ||
| 693 | // TODO: Make this not hardcoded. | ||
| 694 | const payload_size = payload_ty.abiSize(target); | ||
| 695 | const padding = abi_size - payload_size - 1; | ||
| 696 | |||
| 697 | if (val.castTag(.opt_payload)) |payload| { | ||
| 698 | try self.lower(payload_ty, payload.data); | ||
| 699 | } else { | ||
| 700 | try self.addUndef(payload_size); | ||
| 701 | } | ||
| 702 | try self.addConstBool(has_payload); | ||
| 703 | try self.addUndef(padding); | ||
| 704 | }, | ||
| 705 | .Enum => { | ||
| 706 | var int_val_buffer: Value.Payload.U64 = undefined; | ||
| 707 | const int_val = val.enumToInt(ty, &int_val_buffer); | ||
| 708 | |||
| 709 | var int_ty_buffer: Type.Payload.Bits = undefined; | ||
| 710 | const int_ty = ty.intTagType(&int_ty_buffer); | ||
| 711 | |||
| 712 | try self.lower(int_ty, int_val); | ||
| 713 | }, | ||
| 714 | .Union => { | ||
| 715 | const tag_and_val = val.castTag(.@"union").?.data; | ||
| 716 | const layout = ty.unionGetLayout(target); | ||
| 717 | |||
| 718 | if (layout.payload_size == 0) { | ||
| 719 | return try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag); | ||
| 720 | } | ||
| 721 | |||
| 722 | const union_ty = ty.cast(Type.Payload.Union).?.data; | ||
| 723 | if (union_ty.layout == .Packed) { | ||
| 724 | return dg.todo("packed union constants", .{}); | ||
| 725 | } | ||
| 726 | |||
| 727 | const active_field = ty.unionTagFieldIndex(tag_and_val.tag, dg.module).?; | ||
| 728 | const active_field_ty = union_ty.fields.values()[active_field].ty; | ||
| 729 | |||
| 730 | const has_tag = layout.tag_size != 0; | ||
| 731 | const tag_first = layout.tag_align >= layout.payload_align; | ||
| 732 | |||
| 733 | if (has_tag and tag_first) { | ||
| 734 | try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag); | ||
| 735 | } | ||
| 736 | |||
| 737 | const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: { | ||
| 738 | try self.lower(active_field_ty, tag_and_val.val); | ||
| 739 | break :blk active_field_ty.abiSize(target); | ||
| 740 | } else 0; | ||
| 741 | |||
| 742 | const payload_padding_len = layout.payload_size - active_field_size; | ||
| 743 | try self.addUndef(payload_padding_len); | ||
| 744 | |||
| 745 | if (has_tag and !tag_first) { | ||
| 746 | try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag); | ||
| 747 | } | ||
| 748 | |||
| 749 | try self.addUndef(layout.padding); | ||
| 750 | }, | ||
| 751 | .ErrorSet => switch (val.tag()) { | ||
| 752 | .@"error" => { | ||
| 753 | const err_name = val.castTag(.@"error").?.data.name; | ||
| 754 | const kv = try dg.module.getErrorValue(err_name); | ||
| 755 | try self.addConstInt(u16, @intCast(u16, kv.value)); | ||
| 756 | }, | ||
| 757 | .zero => { | ||
| 758 | // Unactivated error set. | ||
| 759 | try self.addConstInt(u16, 0); | ||
| 760 | }, | ||
| 761 | else => unreachable, | ||
| 762 | }, | ||
| 763 | .ErrorUnion => { | ||
| 764 | const payload_ty = ty.errorUnionPayload(); | ||
| 765 | const is_pl = val.errorUnionIsPayload(); | ||
| 766 | const error_val = if (!is_pl) val else Value.initTag(.zero); | ||
| 767 | |||
| 768 | if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { | ||
| 769 | return try self.lower(Type.anyerror, error_val); | ||
| 770 | } | ||
| 771 | |||
| 772 | const payload_align = payload_ty.abiAlignment(target); | ||
| 773 | const error_align = Type.anyerror.abiAlignment(target); | ||
| 774 | |||
| 775 | const payload_size = payload_ty.abiSize(target); | ||
| 776 | const error_size = Type.anyerror.abiAlignment(target); | ||
| 777 | const ty_size = ty.abiSize(target); | ||
| 778 | const padding = ty_size - payload_size - error_size; | ||
| 779 | |||
| 780 | const payload_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef); | ||
| 781 | |||
| 782 | if (error_align > payload_align) { | ||
| 783 | try self.lower(Type.anyerror, error_val); | ||
| 784 | try self.lower(payload_ty, payload_val); | ||
| 785 | } else { | ||
| 786 | try self.lower(payload_ty, payload_val); | ||
| 787 | try self.lower(Type.anyerror, error_val); | ||
| 788 | } | ||
| 789 | |||
| 790 | try self.addUndef(padding); | ||
| 791 | }, | ||
| 792 | else => |tag| return dg.todo("indirect constant of type {s}", .{@tagName(tag)}), | ||
| 793 | } | ||
| 794 | } | ||
| 795 | }; | ||
| 796 | |||
| 797 | /// Returns a pointer to `val`. The value is placed directly | ||
| 798 | /// into the storage class `storage_class`, and this is also where the resulting | ||
| 799 | /// pointer points to. Note: result is not necessarily an OpVariable instruction! | ||
| 800 | fn lowerIndirectConstant( | ||
| 801 | self: *DeclGen, | ||
| 802 | spv_decl_index: SpvModule.Decl.Index, | ||
| 803 | ty: Type, | ||
| 804 | val: Value, | ||
| 805 | storage_class: StorageClass, | ||
| 806 | cast_to_generic: bool, | ||
| 807 | alignment: u32, | ||
| 808 | ) Error!void { | ||
| 809 | // To simplify constant generation, we're going to generate constants as a word-array, and | ||
| 810 | // pointer cast the result to the right type. | ||
| 811 | // This means that the final constant will be generated as follows: | ||
| 812 | // %T = OpTypeStruct %members... | ||
| 813 | // %P = OpTypePointer %T | ||
| 814 | // %U = OpTypePointer %ty | ||
| 815 | // %1 = OpConstantComposite %T %initializers... | ||
| 816 | // %2 = OpVariable %P %1 | ||
| 817 | // %result_id = OpSpecConstantOp OpBitcast %U %2 | ||
| 818 | // | ||
| 819 | // The members consist of two options: | ||
| 820 | // - Literal values: ints, strings, etc. These are generated as u32 words. | ||
| 821 | // - Relocations, such as pointers: These are generated by embedding the pointer into the | ||
| 822 | // to-be-generated structure. There are two options here, depending on the alignment of the | ||
| 823 | // pointer value itself (not the alignment of the pointee). | ||
| 824 | // - Natively or over-aligned values. These can just be generated directly. | ||
| 825 | // - Underaligned pointers. These need to be packed into the word array by using a mixture of | ||
| 826 | // OpSpecConstantOp instructions such as OpConvertPtrToU, OpBitcast, OpShift, etc. | ||
| 827 | |||
| 828 | // TODO: Implement alignment here. | ||
| 829 | // This is hoing to require some hacks because there is no real way to | ||
| 830 | // set an OpVariable's alignment. | ||
| 831 | _ = alignment; | ||
| 832 | |||
| 833 | assert(storage_class != .Generic and storage_class != .Function); | ||
| 834 | |||
| 835 | log.debug("lowerIndirectConstant: ty = {}, val = {}", .{ ty.fmt(self.module), val.fmtDebug() }); | ||
| 836 | |||
| 837 | const section = &self.spv.globals.section; | ||
| 838 | |||
| 839 | const ty_ref = try self.resolveType(ty, .indirect); | ||
| 840 | const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, 0); | ||
| 841 | |||
| 842 | // const target = self.getTarget(); | ||
| 843 | |||
| 844 | // TODO: Fix the resulting global linking for these paths. | ||
| 845 | // if (val.isUndef()) { | ||
| 846 | // // Special case: the entire value is undefined. In this case, we can just | ||
| 847 | // // generate an OpVariable with no initializer. | ||
| 848 | // return try section.emit(self.spv.gpa, .OpVariable, .{ | ||
| 849 | // .id_result_type = self.typeId(ptr_ty_ref), | ||
| 850 | // .id_result = result_id, | ||
| 851 | // .storage_class = storage_class, | ||
| 852 | // }); | ||
| 853 | // } else if (ty.abiSize(target) == 0) { | ||
| 854 | // // Special case: if the type has no size, then return an undefined pointer. | ||
| 855 | // return try section.emit(self.spv.gpa, .OpUndef, .{ | ||
| 856 | // .id_result_type = self.typeId(ptr_ty_ref), | ||
| 857 | // .id_result = result_id, | ||
| 858 | // }); | ||
| 859 | // } | ||
| 860 | |||
| 861 | // TODO: Capture the above stuff in here as well... | ||
| 862 | const begin_inst = self.spv.beginGlobal(); | ||
| 863 | |||
| 864 | const u32_ty_ref = try self.intType(.unsigned, 32); | ||
| 865 | var icl = IndirectConstantLowering{ | ||
| 866 | .dg = self, | ||
| 867 | .u32_ty_ref = u32_ty_ref, | ||
| 868 | .u32_ty_id = self.typeId(u32_ty_ref), | ||
| 869 | .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa), | ||
| 870 | .initializers = std.ArrayList(IdRef).init(self.gpa), | ||
| 871 | .decl_deps = std.ArrayList(SpvModule.Decl.Index).init(self.gpa), | ||
| 872 | }; | ||
| 873 | |||
| 874 | defer icl.members.deinit(); | ||
| 875 | defer icl.initializers.deinit(); | ||
| 876 | defer icl.decl_deps.deinit(); | ||
| 877 | |||
| 878 | try icl.lower(ty, val); | ||
| 879 | try icl.flush(); | ||
| 880 | |||
| 881 | const constant_struct_ty_ref = try self.spv.simpleStructType(icl.members.items); | ||
| 882 | const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, 0); | ||
| 883 | |||
| 884 | const constant_struct_id = self.spv.allocId(); | ||
| 885 | try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ | ||
| 886 | .id_result_type = self.typeId(constant_struct_ty_ref), | ||
| 887 | .id_result = constant_struct_id, | ||
| 888 | .constituents = icl.initializers.items, | ||
| 889 | }); | ||
| 890 | |||
| 891 | const var_id = self.spv.allocId(); | ||
| 892 | self.spv.globalPtr(spv_decl_index).?.result_id = var_id; | ||
| 893 | try section.emit(self.spv.gpa, .OpVariable, .{ | ||
| 894 | .id_result_type = self.typeId(ptr_constant_struct_ty_ref), | ||
| 895 | .id_result = var_id, | ||
| 896 | .storage_class = storage_class, | ||
| 897 | .initializer = constant_struct_id, | ||
| 898 | }); | ||
| 899 | // TODO: Set alignment of OpVariable. | ||
| 900 | // TODO: We may be able to eliminate these casts. | ||
| 901 | |||
| 902 | const const_ptr_id = try self.makePointerConstant(section, ptr_constant_struct_ty_ref, var_id); | ||
| 903 | const result_id = self.spv.declPtr(spv_decl_index).result_id; | ||
| 904 | |||
| 905 | const bitcast_result_id = if (cast_to_generic) | ||
| 906 | self.spv.allocId() | ||
| 907 | else | ||
| 908 | result_id; | ||
| 909 | |||
| 910 | try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{ | ||
| 911 | .id_result_type = self.typeId(ptr_ty_ref), | ||
| 912 | .id_result = bitcast_result_id, | ||
| 913 | .operand = const_ptr_id, | ||
| 914 | }); | ||
| 915 | |||
| 916 | if (cast_to_generic) { | ||
| 917 | const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0); | ||
| 918 | try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ | ||
| 919 | .id_result_type = self.typeId(generic_ptr_ty_ref), | ||
| 920 | .id_result = result_id, | ||
| 921 | .pointer = bitcast_result_id, | ||
| 922 | }); | ||
| 923 | } | ||
| 924 | |||
| 925 | try self.spv.declareDeclDeps(spv_decl_index, icl.decl_deps.items); | ||
| 926 | self.spv.endGlobal(spv_decl_index, begin_inst); | ||
| 927 | } | ||
| 928 | |||
| 929 | /// This function generates a load for a constant in direct (ie, non-memory) representation. | ||
| 930 | /// When the constant is simple, it can be generated directly using OpConstant instructions. When | ||
| 931 | /// the constant is more complicated however, it needs to be lowered to an indirect constant, which | ||
| 932 | /// is then loaded using OpLoad. Such values are loaded into the UniformConstant storage class by default. | ||
| 933 | /// This function should only be called during function code generation. | ||
| 934 | fn constant(self: *DeclGen, ty: Type, val: Value) !IdRef { | ||
| 346 | const target = self.getTarget(); | 935 | const target = self.getTarget(); |
| 347 | const section = &self.spv.sections.types_globals_constants; | 936 | const section = &self.spv.sections.types_globals_constants; |
| 937 | const result_ty_ref = try self.resolveType(ty, .direct); | ||
| 938 | const result_ty_id = self.typeId(result_ty_ref); | ||
| 348 | const result_id = self.spv.allocId(); | 939 | const result_id = self.spv.allocId(); |
| 349 | const result_type_id = try self.resolveTypeId(ty); | ||
| 350 | 940 | ||
| 351 | if (val.isUndef()) { | 941 | if (val.isUndef()) { |
| 352 | try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_type_id, .id_result = result_id }); | 942 | try section.emit(self.spv.gpa, .OpUndef, .{ |
| 353 | return result_id.toRef(); | 943 | .id_result_type = result_ty_id, |
| 944 | .id_result = result_id, | ||
| 945 | }); | ||
| 946 | return result_id; | ||
| 354 | } | 947 | } |
| 355 | 948 | ||
| 356 | switch (ty.zigTypeTag()) { | 949 | switch (ty.zigTypeTag()) { |
| 357 | .Int => { | 950 | .Int => { |
| 358 | const int_info = ty.intInfo(target); | 951 | const int_bits = if (ty.isSignedInt()) |
| 359 | const backing_bits = self.backingIntBits(int_info.bits) orelse { | 952 | @bitCast(u64, val.toSignedInt(target)) |
| 360 | // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits. | 953 | else |
| 361 | return self.todo("implement composite int constants for {}", .{ty.fmtDebug()}); | 954 | val.toUnsignedInt(target); |
| 362 | }; | 955 | try self.genConstInt(result_ty_ref, result_id, int_bits); |
| 363 | |||
| 364 | // We can just use toSignedInt/toUnsignedInt here as it returns u64 - a type large enough to hold any | ||
| 365 | // SPIR-V native type (up to i/u64 with Int64). If SPIR-V ever supports native ints of a larger size, this | ||
| 366 | // might need to be updated. | ||
| 367 | assert(self.largestSupportedIntBits() <= @bitSizeOf(u64)); | ||
| 368 | |||
| 369 | // Note, value is required to be sign-extended, so we don't need to mask off the upper bits. | ||
| 370 | // See https://www.khronos.org/registry/SPIR-V/specs/unified1/SPIRV.html#Literal | ||
| 371 | var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt(target)) else val.toUnsignedInt(target); | ||
| 372 | |||
| 373 | const value: spec.LiteralContextDependentNumber = switch (backing_bits) { | ||
| 374 | 1...32 => .{ .uint32 = @truncate(u32, int_bits) }, | ||
| 375 | 33...64 => .{ .uint64 = int_bits }, | ||
| 376 | else => unreachable, | ||
| 377 | }; | ||
| 378 | |||
| 379 | try section.emit(self.spv.gpa, .OpConstant, .{ | ||
| 380 | .id_result_type = result_type_id, | ||
| 381 | .id_result = result_id, | ||
| 382 | .value = value, | ||
| 383 | }); | ||
| 384 | }, | 956 | }, |
| 385 | .Bool => { | 957 | .Bool => { |
| 386 | const operands = .{ .id_result_type = result_type_id, .id_result = result_id }; | 958 | const operands = .{ .id_result_type = result_ty_id, .id_result = result_id }; |
| 387 | if (val.toBool()) { | 959 | if (val.toBool()) { |
| 388 | try section.emit(self.spv.gpa, .OpConstantTrue, operands); | 960 | try section.emit(self.spv.gpa, .OpConstantTrue, operands); |
| 389 | } else { | 961 | } else { |
| 390 | try section.emit(self.spv.gpa, .OpConstantFalse, operands); | 962 | try section.emit(self.spv.gpa, .OpConstantFalse, operands); |
| 391 | } | 963 | } |
| 392 | }, | 964 | }, |
| 393 | .Float => { | 965 | // TODO: We can handle most pointers here (decl refs etc), because now they emit an extra |
| 394 | // At this point we are guaranteed that the target floating point type is supported, otherwise the function | 966 | // OpVariable that is not really required. |
| 395 | // would have exited at resolveTypeId(ty). | 967 | else => { |
| 396 | 968 | // The value cannot be generated directly, so generate it as an indirect constant, | |
| 397 | const value: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) { | 969 | // and then perform an OpLoad. |
| 398 | // Prevent upcasting to f32 by bitcasting and writing as a uint32. | 970 | const alignment = ty.abiAlignment(target); |
| 399 | 16 => .{ .uint32 = @bitCast(u16, val.toFloat(f16)) }, | 971 | const spv_decl_index = try self.spv.allocDecl(.global); |
| 400 | 32 => .{ .float32 = val.toFloat(f32) }, | 972 | |
| 401 | 64 => .{ .float64 = val.toFloat(f64) }, | 973 | try self.lowerIndirectConstant( |
| 402 | 128 => unreachable, // Filtered out in the call to resolveTypeId. | 974 | spv_decl_index, |
| 403 | // TODO: Insert case for long double when the layout for that is determined? | 975 | ty, |
| 404 | else => unreachable, | 976 | val, |
| 405 | }; | 977 | .UniformConstant, |
| 406 | 978 | false, | |
| 407 | try section.emit(self.spv.gpa, .OpConstant, .{ | 979 | alignment, |
| 408 | .id_result_type = result_type_id, | 980 | ); |
| 981 | try self.func.decl_deps.append(self.spv.gpa, spv_decl_index); | ||
| 982 | |||
| 983 | try self.func.body.emit(self.spv.gpa, .OpLoad, .{ | ||
| 984 | .id_result_type = result_ty_id, | ||
| 409 | .id_result = result_id, | 985 | .id_result = result_id, |
| 410 | .value = value, | 986 | .pointer = self.spv.declPtr(spv_decl_index).result_id, |
| 411 | }); | 987 | }); |
| 988 | // TODO: Convert bools? This logic should hook into `load`. It should be a dead | ||
| 989 | // path though considering .Bool is handled above. | ||
| 412 | }, | 990 | }, |
| 413 | .Vector => switch (val.tag()) { | ||
| 414 | .aggregate => { | ||
| 415 | const elem_vals = val.castTag(.aggregate).?.data; | ||
| 416 | const vector_len = @intCast(usize, ty.vectorLen()); | ||
| 417 | const elem_ty = ty.elemType(); | ||
| 418 | |||
| 419 | const elem_refs = try self.gpa.alloc(IdRef, vector_len); | ||
| 420 | defer self.gpa.free(elem_refs); | ||
| 421 | for (elem_refs, 0..) |*elem, i| { | ||
| 422 | elem.* = try self.genConstant(elem_ty, elem_vals[i]); | ||
| 423 | } | ||
| 424 | try section.emit(self.spv.gpa, .OpConstantComposite, .{ | ||
| 425 | .id_result_type = result_type_id, | ||
| 426 | .id_result = result_id, | ||
| 427 | .constituents = elem_refs, | ||
| 428 | }); | ||
| 429 | }, | ||
| 430 | else => unreachable, // TODO | ||
| 431 | }, | ||
| 432 | .Void => unreachable, | ||
| 433 | .Fn => unreachable, | ||
| 434 | else => return self.todo("constant generation of type {}", .{ty.fmtDebug()}), | ||
| 435 | } | 991 | } |
| 436 | 992 | ||
| 437 | return result_id.toRef(); | 993 | return result_id; |
| 438 | } | 994 | } |
| 439 | 995 | ||
| 440 | /// Turn a Zig type into a SPIR-V Type, and return its type result-id. | 996 | /// Turn a Zig type into a SPIR-V Type, and return its type result-id. |
| 441 | fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType { | 997 | fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType { |
| 442 | const type_ref = try self.resolveType(ty); | 998 | const type_ref = try self.resolveType(ty, .direct); |
| 443 | return self.spv.typeResultId(type_ref); | 999 | return self.typeId(type_ref); |
| 1000 | } | ||
| 1001 | |||
| 1002 | fn typeId(self: *DeclGen, ty_ref: SpvType.Ref) IdRef { | ||
| 1003 | return self.spv.typeId(ty_ref); | ||
| 1004 | } | ||
| 1005 | |||
| 1006 | /// Create an integer type suitable for storing at least 'bits' bits. | ||
| 1007 | fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvType.Ref { | ||
| 1008 | const backing_bits = self.backingIntBits(bits) orelse { | ||
| 1009 | // TODO: Integers too big for any native type are represented as "composite integers": | ||
| 1010 | // An array of largestSupportedIntBits. | ||
| 1011 | return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits }); | ||
| 1012 | }; | ||
| 1013 | |||
| 1014 | return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits)); | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | /// Create an integer type that represents 'usize'. | ||
| 1018 | fn sizeType(self: *DeclGen) !SpvType.Ref { | ||
| 1019 | return try self.intType(.unsigned, self.getTarget().cpu.arch.ptrBitWidth()); | ||
| 1020 | } | ||
| 1021 | |||
| 1022 | /// Generate a union type, optionally with a known field. If the tag alignment is greater | ||
| 1023 | /// than that of the payload, a regular union (non-packed, with both tag and payload), will | ||
| 1024 | /// be generated as follows: | ||
| 1025 | /// If the active field is known: | ||
| 1026 | /// struct { | ||
| 1027 | /// tag: TagType, | ||
| 1028 | /// payload: ActivePayloadType, | ||
| 1029 | /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8, | ||
| 1030 | /// padding: [padding_size]u8, | ||
| 1031 | /// } | ||
| 1032 | /// If the payload alignment is greater than that of the tag: | ||
| 1033 | /// struct { | ||
| 1034 | /// payload: ActivePayloadType, | ||
| 1035 | /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8, | ||
| 1036 | /// tag: TagType, | ||
| 1037 | /// padding: [padding_size]u8, | ||
| 1038 | /// } | ||
| 1039 | /// If the active payload is unknown, it will default back to the most aligned field. This is | ||
| 1040 | /// to make sure that the overal struct has the correct alignment in spir-v. | ||
| 1041 | /// If any of the fields' size is 0, it will be omitted. | ||
| 1042 | /// NOTE: When the active field is set to something other than the most aligned field, the | ||
| 1043 | /// resulting struct will be *underaligned*. | ||
| 1044 | fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvType.Ref { | ||
| 1045 | const target = self.getTarget(); | ||
| 1046 | const layout = ty.unionGetLayout(target); | ||
| 1047 | const union_ty = ty.cast(Type.Payload.Union).?.data; | ||
| 1048 | |||
| 1049 | if (union_ty.layout == .Packed) { | ||
| 1050 | return self.todo("packed union types", .{}); | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect); | ||
| 1054 | if (layout.payload_size == 0) { | ||
| 1055 | // No payload, so represent this as just the tag type. | ||
| 1056 | return tag_ty_ref; | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | var members = std.BoundedArray(SpvType.Payload.Struct.Member, 4){}; | ||
| 1060 | |||
| 1061 | const has_tag = layout.tag_size != 0; | ||
| 1062 | const tag_first = layout.tag_align >= layout.payload_align; | ||
| 1063 | const tag_member = .{ .name = "tag", .ty = tag_ty_ref }; | ||
| 1064 | const u8_ty_ref = try self.intType(.unsigned, 8); // TODO: What if Int8Type is not enabled? | ||
| 1065 | |||
| 1066 | if (has_tag and tag_first) { | ||
| 1067 | members.appendAssumeCapacity(tag_member); | ||
| 1068 | } | ||
| 1069 | |||
| 1070 | const active_field = maybe_active_field orelse layout.most_aligned_field; | ||
| 1071 | const active_field_ty = union_ty.fields.values()[active_field].ty; | ||
| 1072 | |||
| 1073 | const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: { | ||
| 1074 | const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect); | ||
| 1075 | members.appendAssumeCapacity(.{ .name = "payload", .ty = active_payload_ty_ref }); | ||
| 1076 | break :blk active_field_ty.abiSize(target); | ||
| 1077 | } else 0; | ||
| 1078 | |||
| 1079 | const payload_padding_len = layout.payload_size - active_field_size; | ||
| 1080 | if (payload_padding_len != 0) { | ||
| 1081 | const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref); | ||
| 1082 | members.appendAssumeCapacity(.{ .name = "padding_payload", .ty = payload_padding_ty_ref }); | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | if (has_tag and !tag_first) { | ||
| 1086 | members.appendAssumeCapacity(tag_member); | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | if (layout.padding != 0) { | ||
| 1090 | const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref); | ||
| 1091 | members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref }); | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | return try self.spv.simpleStructType(members.slice()); | ||
| 444 | } | 1095 | } |
| 445 | 1096 | ||
| 446 | /// Turn a Zig type into a SPIR-V Type, and return a reference to it. | 1097 | /// Turn a Zig type into a SPIR-V Type, and return a reference to it. |
| 447 | fn resolveType(self: *DeclGen, ty: Type) Error!SpvType.Ref { | 1098 | fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref { |
| 1099 | log.debug("resolveType: ty = {}", .{ty.fmt(self.module)}); | ||
| 448 | const target = self.getTarget(); | 1100 | const target = self.getTarget(); |
| 449 | return switch (ty.zigTypeTag()) { | 1101 | switch (ty.zigTypeTag()) { |
| 450 | .Void => try self.spv.resolveType(SpvType.initTag(.void)), | 1102 | .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)), |
| 451 | .Bool => blk: { | 1103 | .Bool => switch (repr) { |
| 452 | // TODO: SPIR-V booleans are opaque. For local variables this is fine, but for structs | 1104 | .direct => return try self.spv.resolveType(SpvType.initTag(.bool)), |
| 453 | // members we want to use integer types instead. | 1105 | // SPIR-V booleans are opaque, which is fine for operations, but they cant be stored. |
| 454 | break :blk try self.spv.resolveType(SpvType.initTag(.bool)); | 1106 | // This function returns the *stored* type, for values directly we convert this into a bool when |
| 1107 | // it is loaded, and convert it back to this type when stored. | ||
| 1108 | .indirect => return try self.intType(.unsigned, 1), | ||
| 455 | }, | 1109 | }, |
| 456 | .Int => blk: { | 1110 | .Int => { |
| 457 | const int_info = ty.intInfo(target); | 1111 | const int_info = ty.intInfo(target); |
| 458 | const backing_bits = self.backingIntBits(int_info.bits) orelse { | 1112 | return try self.intType(int_info.signedness, int_info.bits); |
| 459 | // TODO: Integers too big for any native type are represented as "composite integers": | 1113 | }, |
| 460 | // An array of largestSupportedIntBits. | 1114 | .Enum => { |
| 461 | return self.todo("Implement composite int type {}", .{ty.fmtDebug()}); | 1115 | var buffer: Type.Payload.Bits = undefined; |
| 462 | }; | 1116 | const tag_ty = ty.intTagType(&buffer); |
| 463 | 1117 | return self.resolveType(tag_ty, repr); | |
| 464 | const payload = try self.spv.arena.create(SpvType.Payload.Int); | ||
| 465 | payload.* = .{ | ||
| 466 | .width = backing_bits, | ||
| 467 | .signedness = int_info.signedness, | ||
| 468 | }; | ||
| 469 | break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); | ||
| 470 | }, | 1118 | }, |
| 471 | .Float => blk: { | 1119 | .Float => { |
| 472 | // We can (and want) not really emulate floating points with other floating point types like with the integer types, | 1120 | // We can (and want) not really emulate floating points with other floating point types like with the integer types, |
| 473 | // so if the float is not supported, just return an error. | 1121 | // so if the float is not supported, just return an error. |
| 474 | const bits = ty.floatBits(target); | 1122 | const bits = ty.floatBits(target); |
| ... | @@ -484,43 +1132,58 @@ pub const DeclGen = struct { | ... | @@ -484,43 +1132,58 @@ pub const DeclGen = struct { |
| 484 | return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits}); | 1132 | return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits}); |
| 485 | } | 1133 | } |
| 486 | 1134 | ||
| 487 | const payload = try self.spv.arena.create(SpvType.Payload.Float); | 1135 | return try self.spv.resolveType(SpvType.float(bits)); |
| 488 | payload.* = .{ | 1136 | }, |
| 489 | .width = bits, | 1137 | .Array => { |
| 1138 | const elem_ty = ty.childType(); | ||
| 1139 | const elem_ty_ref = try self.resolveType(elem_ty, .indirect); | ||
| 1140 | const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse { | ||
| 1141 | return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()}); | ||
| 490 | }; | 1142 | }; |
| 491 | break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); | 1143 | return try self.spv.arrayType(total_len, elem_ty_ref); |
| 492 | }, | 1144 | }, |
| 493 | .Fn => blk: { | 1145 | .Fn => switch (repr) { |
| 494 | // We only support C-calling-convention functions for now, no varargs. | 1146 | .direct => { |
| 495 | if (ty.fnCallingConvention() != .C) | 1147 | // TODO: Put this somewhere in Sema.zig |
| 496 | return self.fail("Unsupported calling convention for SPIR-V", .{}); | 1148 | if (ty.fnIsVarArgs()) |
| 497 | if (ty.fnIsVarArgs()) | 1149 | return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); |
| 498 | return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); | 1150 | |
| 499 | 1151 | // TODO: Parameter passing convention etc. | |
| 500 | const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen()); | 1152 | |
| 501 | for (param_types, 0..) |*param, i| { | 1153 | const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen()); |
| 502 | param.* = try self.resolveType(ty.fnParamType(i)); | 1154 | for (param_types, 0..) |*param, i| { |
| 503 | } | 1155 | param.* = try self.resolveType(ty.fnParamType(i), .direct); |
| 1156 | } | ||
| 504 | 1157 | ||
| 505 | const return_type = try self.resolveType(ty.fnReturnType()); | 1158 | const return_type = try self.resolveType(ty.fnReturnType(), .direct); |
| 506 | 1159 | ||
| 507 | const payload = try self.spv.arena.create(SpvType.Payload.Function); | 1160 | const payload = try self.spv.arena.create(SpvType.Payload.Function); |
| 508 | payload.* = .{ .return_type = return_type, .parameters = param_types }; | 1161 | payload.* = .{ .return_type = return_type, .parameters = param_types }; |
| 509 | break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); | 1162 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); |
| 1163 | }, | ||
| 1164 | .indirect => { | ||
| 1165 | // TODO: Represent function pointers properly. | ||
| 1166 | // For now, just use an usize type. | ||
| 1167 | return try self.sizeType(); | ||
| 1168 | }, | ||
| 510 | }, | 1169 | }, |
| 511 | .Pointer => blk: { | 1170 | .Pointer => { |
| 512 | const payload = try self.spv.arena.create(SpvType.Payload.Pointer); | 1171 | const ptr_info = ty.ptrInfo().data; |
| 513 | payload.* = .{ | 1172 | |
| 514 | .storage_class = spirvStorageClass(ty.ptrAddressSpace()), | 1173 | const storage_class = spvStorageClass(ptr_info.@"addrspace"); |
| 515 | .child_type = try self.resolveType(ty.elemType()), | 1174 | const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect); |
| 516 | .array_stride = 0, | 1175 | const ptr_ty_ref = try self.spv.ptrType(child_ty_ref, storage_class, 0); |
| 517 | // Note: only available in Kernels! | 1176 | |
| 518 | .alignment = null, | 1177 | if (ptr_info.size != .Slice) { |
| 519 | .max_byte_offset = null, | 1178 | return ptr_ty_ref; |
| 520 | }; | 1179 | } |
| 521 | break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); | 1180 | |
| 1181 | return try self.spv.simpleStructType(&.{ | ||
| 1182 | .{ .ty = ptr_ty_ref, .name = "ptr" }, | ||
| 1183 | .{ .ty = try self.sizeType(), .name = "len" }, | ||
| 1184 | }); | ||
| 522 | }, | 1185 | }, |
| 523 | .Vector => blk: { | 1186 | .Vector => { |
| 524 | // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations | 1187 | // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations |
| 525 | // which work on them), so simply use those. | 1188 | // which work on them), so simply use those. |
| 526 | // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way. | 1189 | // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way. |
| ... | @@ -532,10 +1195,112 @@ pub const DeclGen = struct { | ... | @@ -532,10 +1195,112 @@ pub const DeclGen = struct { |
| 532 | 1195 | ||
| 533 | const payload = try self.spv.arena.create(SpvType.Payload.Vector); | 1196 | const payload = try self.spv.arena.create(SpvType.Payload.Vector); |
| 534 | payload.* = .{ | 1197 | payload.* = .{ |
| 535 | .component_type = try self.resolveType(ty.elemType()), | 1198 | .component_type = try self.resolveType(ty.elemType(), repr), |
| 536 | .component_count = @intCast(u32, ty.vectorLen()), | 1199 | .component_count = @intCast(u32, ty.vectorLen()), |
| 537 | }; | 1200 | }; |
| 538 | break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); | 1201 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); |
| 1202 | }, | ||
| 1203 | .Struct => { | ||
| 1204 | if (ty.isSimpleTupleOrAnonStruct()) { | ||
| 1205 | const tuple = ty.tupleFields(); | ||
| 1206 | const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, tuple.types.len); | ||
| 1207 | var member_index: u32 = 0; | ||
| 1208 | for (tuple.types, 0..) |field_ty, i| { | ||
| 1209 | const field_val = tuple.values[i]; | ||
| 1210 | if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBitsIgnoreComptime()) continue; | ||
| 1211 | members[member_index] = .{ | ||
| 1212 | .ty = try self.resolveType(field_ty, .indirect), | ||
| 1213 | }; | ||
| 1214 | member_index += 1; | ||
| 1215 | } | ||
| 1216 | const payload = try self.spv.arena.create(SpvType.Payload.Struct); | ||
| 1217 | payload.* = .{ | ||
| 1218 | .members = members[0..member_index], | ||
| 1219 | }; | ||
| 1220 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | const struct_ty = ty.castTag(.@"struct").?.data; | ||
| 1224 | |||
| 1225 | if (struct_ty.layout == .Packed) { | ||
| 1226 | return try self.resolveType(struct_ty.backing_int_ty, .indirect); | ||
| 1227 | } | ||
| 1228 | |||
| 1229 | const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count()); | ||
| 1230 | var member_index: usize = 0; | ||
| 1231 | for (struct_ty.fields.values(), 0..) |field, i| { | ||
| 1232 | if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; | ||
| 1233 | |||
| 1234 | members[member_index] = .{ | ||
| 1235 | .ty = try self.resolveType(field.ty, .indirect), | ||
| 1236 | .name = struct_ty.fields.keys()[i], | ||
| 1237 | }; | ||
| 1238 | member_index += 1; | ||
| 1239 | } | ||
| 1240 | |||
| 1241 | const name = try struct_ty.getFullyQualifiedName(self.module); | ||
| 1242 | defer self.module.gpa.free(name); | ||
| 1243 | |||
| 1244 | const payload = try self.spv.arena.create(SpvType.Payload.Struct); | ||
| 1245 | payload.* = .{ | ||
| 1246 | .members = members[0..member_index], | ||
| 1247 | .name = try self.spv.arena.dupe(u8, name), | ||
| 1248 | }; | ||
| 1249 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); | ||
| 1250 | }, | ||
| 1251 | .Optional => { | ||
| 1252 | var buf: Type.Payload.ElemType = undefined; | ||
| 1253 | const payload_ty = ty.optionalChild(&buf); | ||
| 1254 | if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { | ||
| 1255 | // Just use a bool. | ||
| 1256 | // Note: Always generate the bool with indirect format, to save on some sanity | ||
| 1257 | // Perform the converison to a direct bool when the field is extracted. | ||
| 1258 | return try self.resolveType(Type.bool, .indirect); | ||
| 1259 | } | ||
| 1260 | |||
| 1261 | const payload_ty_ref = try self.resolveType(payload_ty, .indirect); | ||
| 1262 | if (ty.optionalReprIsPayload()) { | ||
| 1263 | // Optional is actually a pointer. | ||
| 1264 | return payload_ty_ref; | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | const bool_ty_ref = try self.resolveType(Type.bool, .indirect); | ||
| 1268 | |||
| 1269 | // its an actual optional | ||
| 1270 | return try self.spv.simpleStructType(&.{ | ||
| 1271 | .{ .ty = payload_ty_ref, .name = "payload" }, | ||
| 1272 | .{ .ty = bool_ty_ref, .name = "valid" }, | ||
| 1273 | }); | ||
| 1274 | }, | ||
| 1275 | .Union => return try self.resolveUnionType(ty, null), | ||
| 1276 | .ErrorSet => return try self.intType(.unsigned, 16), | ||
| 1277 | .ErrorUnion => { | ||
| 1278 | const payload_ty = ty.errorUnionPayload(); | ||
| 1279 | const error_ty_ref = try self.resolveType(Type.anyerror, .indirect); | ||
| 1280 | if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { | ||
| 1281 | return error_ty_ref; | ||
| 1282 | } | ||
| 1283 | |||
| 1284 | const payload_ty_ref = try self.resolveType(payload_ty, .indirect); | ||
| 1285 | |||
| 1286 | const payload_align = payload_ty.abiAlignment(target); | ||
| 1287 | const error_align = Type.anyerror.abiAlignment(target); | ||
| 1288 | |||
| 1289 | var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){}; | ||
| 1290 | // Similar to unions, we're going to put the most aligned member first. | ||
| 1291 | if (error_align > payload_align) { | ||
| 1292 | // Put the error first | ||
| 1293 | members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" }); | ||
| 1294 | members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" }); | ||
| 1295 | // TODO: ABI padding? | ||
| 1296 | } else { | ||
| 1297 | // Put the payload first. | ||
| 1298 | members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" }); | ||
| 1299 | members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" }); | ||
| 1300 | // TODO: ABI padding? | ||
| 1301 | } | ||
| 1302 | |||
| 1303 | return try self.spv.simpleStructType(members.slice()); | ||
| 539 | }, | 1304 | }, |
| 540 | 1305 | ||
| 541 | .Null, | 1306 | .Null, |
| ... | @@ -547,31 +1312,120 @@ pub const DeclGen = struct { | ... | @@ -547,31 +1312,120 @@ pub const DeclGen = struct { |
| 547 | => unreachable, // Must be comptime. | 1312 | => unreachable, // Must be comptime. |
| 548 | 1313 | ||
| 549 | else => |tag| return self.todo("Implement zig type '{}'", .{tag}), | 1314 | else => |tag| return self.todo("Implement zig type '{}'", .{tag}), |
| 550 | }; | 1315 | } |
| 551 | } | 1316 | } |
| 552 | 1317 | ||
| 553 | fn spirvStorageClass(as: std.builtin.AddressSpace) spec.StorageClass { | 1318 | fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass { |
| 554 | return switch (as) { | 1319 | return switch (as) { |
| 555 | .generic => .Generic, // TODO: Disallow? | 1320 | .generic => .Generic, |
| 556 | .gs, .fs, .ss => unreachable, | ||
| 557 | .shared => .Workgroup, | 1321 | .shared => .Workgroup, |
| 558 | .local => .Private, | 1322 | .local => .Private, |
| 559 | .global, .param, .constant, .flash, .flash1, .flash2, .flash3, .flash4, .flash5 => unreachable, | 1323 | .global => .CrossWorkgroup, |
| 1324 | .constant => .UniformConstant, | ||
| 1325 | .gs, | ||
| 1326 | .fs, | ||
| 1327 | .ss, | ||
| 1328 | .param, | ||
| 1329 | .flash, | ||
| 1330 | .flash1, | ||
| 1331 | .flash2, | ||
| 1332 | .flash3, | ||
| 1333 | .flash4, | ||
| 1334 | .flash5, | ||
| 1335 | => unreachable, | ||
| 560 | }; | 1336 | }; |
| 561 | } | 1337 | } |
| 562 | 1338 | ||
| 1339 | /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure. | ||
| 1340 | /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry- | ||
| 1341 | /// points. The test executor will then be able to invoke these to run the tests. | ||
| 1342 | /// Note that tests are lowered according to std.builtin.TestFn, which is `fn () anyerror!void`. | ||
| 1343 | /// (anyerror!void has the same layout as anyerror). | ||
| 1344 | /// Each test declaration generates a function like. | ||
| 1345 | /// %anyerror = OpTypeInt 0 16 | ||
| 1346 | /// %p_anyerror = OpTypePointer CrossWorkgroup %anyerror | ||
| 1347 | /// %K = OpTypeFunction %void %p_anyerror | ||
| 1348 | /// | ||
| 1349 | /// %test = OpFunction %void %K | ||
| 1350 | /// %p_err = OpFunctionParameter %p_anyerror | ||
| 1351 | /// %lbl = OpLabel | ||
| 1352 | /// %result = OpFunctionCall %anyerror %func | ||
| 1353 | /// OpStore %p_err %result | ||
| 1354 | /// OpFunctionEnd | ||
| 1355 | /// TODO is to also write out the error as a function call parameter, and to somehow fetch | ||
| 1356 | /// the name of an error in the text executor. | ||
| 1357 | fn generateTestEntryPoint(self: *DeclGen, name: []const u8, spv_test_decl_index: SpvModule.Decl.Index) !void { | ||
| 1358 | const anyerror_ty_ref = try self.resolveType(Type.anyerror, .direct); | ||
| 1359 | const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup, 0); | ||
| 1360 | const void_ty_ref = try self.resolveType(Type.void, .direct); | ||
| 1361 | |||
| 1362 | const kernel_proto_ty_ref = blk: { | ||
| 1363 | const proto_payload = try self.spv.arena.create(SpvType.Payload.Function); | ||
| 1364 | proto_payload.* = .{ | ||
| 1365 | .return_type = void_ty_ref, | ||
| 1366 | .parameters = try self.spv.arena.dupe(SpvType.Ref, &.{ptr_anyerror_ty_ref}), | ||
| 1367 | }; | ||
| 1368 | break :blk try self.spv.resolveType(SpvType.initPayload(&proto_payload.base)); | ||
| 1369 | }; | ||
| 1370 | |||
| 1371 | const test_id = self.spv.declPtr(spv_test_decl_index).result_id; | ||
| 1372 | |||
| 1373 | const spv_decl_index = try self.spv.allocDecl(.func); | ||
| 1374 | const kernel_id = self.spv.declPtr(spv_decl_index).result_id; | ||
| 1375 | |||
| 1376 | const error_id = self.spv.allocId(); | ||
| 1377 | const p_error_id = self.spv.allocId(); | ||
| 1378 | |||
| 1379 | const section = &self.spv.sections.functions; | ||
| 1380 | try section.emit(self.spv.gpa, .OpFunction, .{ | ||
| 1381 | .id_result_type = self.typeId(void_ty_ref), | ||
| 1382 | .id_result = kernel_id, | ||
| 1383 | .function_control = .{}, | ||
| 1384 | .function_type = self.typeId(kernel_proto_ty_ref), | ||
| 1385 | }); | ||
| 1386 | try section.emit(self.spv.gpa, .OpFunctionParameter, .{ | ||
| 1387 | .id_result_type = self.typeId(ptr_anyerror_ty_ref), | ||
| 1388 | .id_result = p_error_id, | ||
| 1389 | }); | ||
| 1390 | try section.emit(self.spv.gpa, .OpLabel, .{ | ||
| 1391 | .id_result = self.spv.allocId(), | ||
| 1392 | }); | ||
| 1393 | try section.emit(self.spv.gpa, .OpFunctionCall, .{ | ||
| 1394 | .id_result_type = self.typeId(anyerror_ty_ref), | ||
| 1395 | .id_result = error_id, | ||
| 1396 | .function = test_id, | ||
| 1397 | }); | ||
| 1398 | try section.emit(self.spv.gpa, .OpStore, .{ | ||
| 1399 | .pointer = p_error_id, | ||
| 1400 | .object = error_id, | ||
| 1401 | }); | ||
| 1402 | try section.emit(self.spv.gpa, .OpReturn, {}); | ||
| 1403 | try section.emit(self.spv.gpa, .OpFunctionEnd, {}); | ||
| 1404 | |||
| 1405 | try self.spv.declareDeclDeps(spv_decl_index, &.{spv_test_decl_index}); | ||
| 1406 | |||
| 1407 | // Just generate a quick other name because the intel runtime crashes when the entry- | ||
| 1408 | // point name is the same as a different OpName. | ||
| 1409 | const test_name = try std.fmt.allocPrint(self.gpa, "test {s}", .{name}); | ||
| 1410 | defer self.gpa.free(test_name); | ||
| 1411 | try self.spv.declareEntryPoint(spv_decl_index, test_name); | ||
| 1412 | } | ||
| 1413 | |||
| 563 | fn genDecl(self: *DeclGen) !void { | 1414 | fn genDecl(self: *DeclGen) !void { |
| 564 | const result_id = self.ids.get(self.decl_index).?; | ||
| 565 | const decl = self.module.declPtr(self.decl_index); | 1415 | const decl = self.module.declPtr(self.decl_index); |
| 1416 | const spv_decl_index = try self.resolveDecl(self.decl_index); | ||
| 1417 | |||
| 1418 | const decl_id = self.spv.declPtr(spv_decl_index).result_id; | ||
| 1419 | log.debug("genDecl {s} = {}", .{ decl.name, decl_id }); | ||
| 566 | 1420 | ||
| 567 | if (decl.val.castTag(.function)) |_| { | 1421 | if (decl.val.castTag(.function)) |_| { |
| 568 | assert(decl.ty.zigTypeTag() == .Fn); | 1422 | assert(decl.ty.zigTypeTag() == .Fn); |
| 569 | const prototype_id = try self.resolveTypeId(decl.ty); | 1423 | const prototype_id = try self.resolveTypeId(decl.ty); |
| 570 | try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{ | 1424 | try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{ |
| 571 | .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()), | 1425 | .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()), |
| 572 | .id_result = result_id, | 1426 | .id_result = decl_id, |
| 573 | .function_control = .{}, // TODO: We can set inline here if the type requires it. | 1427 | .function_control = .{}, // TODO: We can set inline here if the type requires it. |
| 574 | .function_type = prototype_id.toRef(), | 1428 | .function_type = prototype_id, |
| 575 | }); | 1429 | }); |
| 576 | 1430 | ||
| 577 | const params = decl.ty.fnParamLen(); | 1431 | const params = decl.ty.fnParamLen(); |
| ... | @@ -585,7 +1439,7 @@ pub const DeclGen = struct { | ... | @@ -585,7 +1439,7 @@ pub const DeclGen = struct { |
| 585 | .id_result_type = param_type_id, | 1439 | .id_result_type = param_type_id, |
| 586 | .id_result = arg_result_id, | 1440 | .id_result = arg_result_id, |
| 587 | }); | 1441 | }); |
| 588 | self.args.appendAssumeCapacity(arg_result_id.toRef()); | 1442 | self.args.appendAssumeCapacity(arg_result_id); |
| 589 | } | 1443 | } |
| 590 | 1444 | ||
| 591 | // TODO: This could probably be done in a better way... | 1445 | // TODO: This could probably be done in a better way... |
| ... | @@ -596,17 +1450,53 @@ pub const DeclGen = struct { | ... | @@ -596,17 +1450,53 @@ pub const DeclGen = struct { |
| 596 | try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{ | 1450 | try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{ |
| 597 | .id_result = root_block_id, | 1451 | .id_result = root_block_id, |
| 598 | }); | 1452 | }); |
| 599 | self.current_block_label_id = root_block_id.toRef(); | 1453 | self.current_block_label_id = root_block_id; |
| 600 | 1454 | ||
| 601 | const main_body = self.air.getMainBody(); | 1455 | const main_body = self.air.getMainBody(); |
| 602 | try self.genBody(main_body); | 1456 | try self.genBody(main_body); |
| 603 | 1457 | ||
| 604 | // Append the actual code into the functions section. | 1458 | // Append the actual code into the functions section. |
| 605 | try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {}); | 1459 | try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {}); |
| 606 | try self.spv.addFunction(self.func); | 1460 | try self.spv.addFunction(spv_decl_index, self.func); |
| 1461 | |||
| 1462 | const fqn = try decl.getFullyQualifiedName(self.module); | ||
| 1463 | defer self.module.gpa.free(fqn); | ||
| 1464 | |||
| 1465 | try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{ | ||
| 1466 | .target = decl_id, | ||
| 1467 | .name = fqn, | ||
| 1468 | }); | ||
| 1469 | |||
| 1470 | // Temporarily generate a test kernel declaration if this is a test function. | ||
| 1471 | if (self.module.test_functions.contains(self.decl_index)) { | ||
| 1472 | try self.generateTestEntryPoint(fqn, spv_decl_index); | ||
| 1473 | } | ||
| 607 | } else { | 1474 | } else { |
| 608 | // TODO | 1475 | const init_val = if (decl.val.castTag(.variable)) |payload| |
| 609 | // return self.todo("generate decl type {}", .{decl.ty.zigTypeTag()}); | 1476 | payload.data.init |
| 1477 | else | ||
| 1478 | decl.val; | ||
| 1479 | |||
| 1480 | if (init_val.tag() == .unreachable_value) { | ||
| 1481 | return self.todo("importing extern variables", .{}); | ||
| 1482 | } | ||
| 1483 | |||
| 1484 | // TODO: integrate with variable(). | ||
| 1485 | |||
| 1486 | const final_storage_class = spvStorageClass(decl.@"addrspace"); | ||
| 1487 | const actual_storage_class = switch (final_storage_class) { | ||
| 1488 | .Generic => .CrossWorkgroup, | ||
| 1489 | else => final_storage_class, | ||
| 1490 | }; | ||
| 1491 | |||
| 1492 | try self.lowerIndirectConstant( | ||
| 1493 | spv_decl_index, | ||
| 1494 | decl.ty, | ||
| 1495 | init_val, | ||
| 1496 | actual_storage_class, | ||
| 1497 | final_storage_class == .Generic, | ||
| 1498 | decl.@"align", | ||
| 1499 | ); | ||
| 610 | } | 1500 | } |
| 611 | } | 1501 | } |
| 612 | 1502 | ||
| ... | @@ -618,11 +1508,25 @@ pub const DeclGen = struct { | ... | @@ -618,11 +1508,25 @@ pub const DeclGen = struct { |
| 618 | 1508 | ||
| 619 | fn genInst(self: *DeclGen, inst: Air.Inst.Index) !void { | 1509 | fn genInst(self: *DeclGen, inst: Air.Inst.Index) !void { |
| 620 | const air_tags = self.air.instructions.items(.tag); | 1510 | const air_tags = self.air.instructions.items(.tag); |
| 621 | const result_id = switch (air_tags[inst]) { | 1511 | const maybe_result_id: ?IdRef = switch (air_tags[inst]) { |
| 622 | // zig fmt: off | 1512 | // zig fmt: off |
| 623 | .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd), | 1513 | .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd, true), |
| 624 | .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub), | 1514 | .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub, true), |
| 625 | .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul), | 1515 | .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul, true), |
| 1516 | |||
| 1517 | .div_float, | ||
| 1518 | .div_float_optimized, | ||
| 1519 | // TODO: Check that this is the right operation. | ||
| 1520 | .div_trunc, | ||
| 1521 | .div_trunc_optimized, | ||
| 1522 | => try self.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv, false), | ||
| 1523 | // TODO: Check if this is the right operation | ||
| 1524 | // TODO: Make airArithOp for rem not emit a mask for the LHS. | ||
| 1525 | .rem, | ||
| 1526 | .rem_optimized, | ||
| 1527 | => try self.airArithOp(inst, .OpFRem, .OpSRem, .OpSRem, false), | ||
| 1528 | |||
| 1529 | .add_with_overflow => try self.airOverflowArithOp(inst), | ||
| 626 | 1530 | ||
| 627 | .shuffle => try self.airShuffle(inst), | 1531 | .shuffle => try self.airShuffle(inst), |
| 628 | 1532 | ||
| ... | @@ -632,7 +1536,24 @@ pub const DeclGen = struct { | ... | @@ -632,7 +1536,24 @@ pub const DeclGen = struct { |
| 632 | .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), | 1536 | .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), |
| 633 | .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), | 1537 | .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), |
| 634 | 1538 | ||
| 635 | .not => try self.airNot(inst), | 1539 | .shl => try self.airShift(inst, .OpShiftLeftLogical), |
| 1540 | |||
| 1541 | .bitcast => try self.airBitcast(inst), | ||
| 1542 | .intcast => try self.airIntcast(inst), | ||
| 1543 | .not => try self.airNot(inst), | ||
| 1544 | |||
| 1545 | .slice_ptr => try self.airSliceField(inst, 0), | ||
| 1546 | .slice_len => try self.airSliceField(inst, 1), | ||
| 1547 | .slice_elem_ptr => try self.airSliceElemPtr(inst), | ||
| 1548 | .slice_elem_val => try self.airSliceElemVal(inst), | ||
| 1549 | .ptr_elem_ptr => try self.airPtrElemPtr(inst), | ||
| 1550 | |||
| 1551 | .struct_field_val => try self.airStructFieldVal(inst), | ||
| 1552 | |||
| 1553 | .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0), | ||
| 1554 | .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1), | ||
| 1555 | .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2), | ||
| 1556 | .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3), | ||
| 636 | 1557 | ||
| 637 | .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual), | 1558 | .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual), |
| 638 | .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual), | 1559 | .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual), |
| ... | @@ -641,21 +1562,33 @@ pub const DeclGen = struct { | ... | @@ -641,21 +1562,33 @@ pub const DeclGen = struct { |
| 641 | .cmp_lt => try self.airCmp(inst, .OpFOrdLessThan, .OpSLessThan, .OpULessThan), | 1562 | .cmp_lt => try self.airCmp(inst, .OpFOrdLessThan, .OpSLessThan, .OpULessThan), |
| 642 | .cmp_lte => try self.airCmp(inst, .OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual), | 1563 | .cmp_lte => try self.airCmp(inst, .OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual), |
| 643 | 1564 | ||
| 644 | .arg => self.airArg(), | 1565 | .arg => self.airArg(), |
| 645 | .alloc => try self.airAlloc(inst), | 1566 | .alloc => try self.airAlloc(inst), |
| 646 | .block => (try self.airBlock(inst)) orelse return, | 1567 | // TODO: We probably need to have a special implementation of this for the C abi. |
| 647 | .load => try self.airLoad(inst), | 1568 | .ret_ptr => try self.airAlloc(inst), |
| 1569 | .block => try self.airBlock(inst), | ||
| 1570 | |||
| 1571 | .load => try self.airLoad(inst), | ||
| 1572 | .store => return self.airStore(inst), | ||
| 648 | 1573 | ||
| 649 | .br => return self.airBr(inst), | 1574 | .br => return self.airBr(inst), |
| 650 | .breakpoint => return, | 1575 | .breakpoint => return, |
| 651 | .cond_br => return self.airCondBr(inst), | 1576 | .cond_br => return self.airCondBr(inst), |
| 652 | .constant => unreachable, | 1577 | .constant => unreachable, |
| 1578 | .const_ty => unreachable, | ||
| 653 | .dbg_stmt => return self.airDbgStmt(inst), | 1579 | .dbg_stmt => return self.airDbgStmt(inst), |
| 654 | .loop => return self.airLoop(inst), | 1580 | .loop => return self.airLoop(inst), |
| 655 | .ret => return self.airRet(inst), | 1581 | .ret => return self.airRet(inst), |
| 656 | .store => return self.airStore(inst), | 1582 | .ret_load => return self.airRetLoad(inst), |
| 1583 | .switch_br => return self.airSwitchBr(inst), | ||
| 657 | .unreach => return self.airUnreach(), | 1584 | .unreach => return self.airUnreach(), |
| 658 | .assembly => (try self.airAssembly(inst)) orelse return, | 1585 | |
| 1586 | .assembly => try self.airAssembly(inst), | ||
| 1587 | |||
| 1588 | .call => try self.airCall(inst, .auto), | ||
| 1589 | .call_always_tail => try self.airCall(inst, .always_tail), | ||
| 1590 | .call_never_tail => try self.airCall(inst, .never_tail), | ||
| 1591 | .call_never_inline => try self.airCall(inst, .never_inline), | ||
| 659 | 1592 | ||
| 660 | .dbg_var_ptr => return, | 1593 | .dbg_var_ptr => return, |
| 661 | .dbg_var_val => return, | 1594 | .dbg_var_val => return, |
| ... | @@ -666,22 +1599,62 @@ pub const DeclGen = struct { | ... | @@ -666,22 +1599,62 @@ pub const DeclGen = struct { |
| 666 | else => |tag| return self.todo("implement AIR tag {s}", .{@tagName(tag)}), | 1599 | else => |tag| return self.todo("implement AIR tag {s}", .{@tagName(tag)}), |
| 667 | }; | 1600 | }; |
| 668 | 1601 | ||
| 1602 | const result_id = maybe_result_id orelse return; | ||
| 669 | try self.inst_results.putNoClobber(self.gpa, inst, result_id); | 1603 | try self.inst_results.putNoClobber(self.gpa, inst, result_id); |
| 670 | } | 1604 | } |
| 671 | 1605 | ||
| 672 | fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !IdRef { | 1606 | fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef { |
| 1607 | if (self.liveness.isUnused(inst)) return null; | ||
| 1608 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | ||
| 1609 | const lhs_id = try self.resolve(bin_op.lhs); | ||
| 1610 | const rhs_id = try self.resolve(bin_op.rhs); | ||
| 1611 | const result_id = self.spv.allocId(); | ||
| 1612 | const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst)); | ||
| 1613 | try self.func.body.emit(self.spv.gpa, opcode, .{ | ||
| 1614 | .id_result_type = result_type_id, | ||
| 1615 | .id_result = result_id, | ||
| 1616 | .operand_1 = lhs_id, | ||
| 1617 | .operand_2 = rhs_id, | ||
| 1618 | }); | ||
| 1619 | return result_id; | ||
| 1620 | } | ||
| 1621 | |||
| 1622 | fn airShift(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef { | ||
| 1623 | if (self.liveness.isUnused(inst)) return null; | ||
| 673 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | 1624 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 674 | const lhs_id = try self.resolve(bin_op.lhs); | 1625 | const lhs_id = try self.resolve(bin_op.lhs); |
| 675 | const rhs_id = try self.resolve(bin_op.rhs); | 1626 | const rhs_id = try self.resolve(bin_op.rhs); |
| 676 | const result_id = self.spv.allocId(); | ||
| 677 | const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst)); | 1627 | const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst)); |
| 1628 | |||
| 1629 | // the shift and the base must be the same type in SPIR-V, but in Zig the shift is a smaller int. | ||
| 1630 | const shift_id = self.spv.allocId(); | ||
| 1631 | try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ | ||
| 1632 | .id_result_type = result_type_id, | ||
| 1633 | .id_result = shift_id, | ||
| 1634 | .unsigned_value = rhs_id, | ||
| 1635 | }); | ||
| 1636 | |||
| 1637 | const result_id = self.spv.allocId(); | ||
| 678 | try self.func.body.emit(self.spv.gpa, opcode, .{ | 1638 | try self.func.body.emit(self.spv.gpa, opcode, .{ |
| 679 | .id_result_type = result_type_id, | 1639 | .id_result_type = result_type_id, |
| 680 | .id_result = result_id, | 1640 | .id_result = result_id, |
| 681 | .operand_1 = lhs_id, | 1641 | .base = lhs_id, |
| 682 | .operand_2 = rhs_id, | 1642 | .shift = shift_id, |
| 1643 | }); | ||
| 1644 | return result_id; | ||
| 1645 | } | ||
| 1646 | |||
| 1647 | fn maskStrangeInt(self: *DeclGen, ty_ref: SpvType.Ref, value_id: IdRef, bits: u16) !IdRef { | ||
| 1648 | const mask_value = if (bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @intCast(u6, bits)) - 1; | ||
| 1649 | const result_id = self.spv.allocId(); | ||
| 1650 | const mask_id = try self.constInt(ty_ref, mask_value); | ||
| 1651 | try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ | ||
| 1652 | .id_result_type = self.typeId(ty_ref), | ||
| 1653 | .id_result = result_id, | ||
| 1654 | .operand_1 = value_id, | ||
| 1655 | .operand_2 = mask_id, | ||
| 683 | }); | 1656 | }); |
| 684 | return result_id.toRef(); | 1657 | return result_id; |
| 685 | } | 1658 | } |
| 686 | 1659 | ||
| 687 | fn airArithOp( | 1660 | fn airArithOp( |
| ... | @@ -690,16 +1663,18 @@ pub const DeclGen = struct { | ... | @@ -690,16 +1663,18 @@ pub const DeclGen = struct { |
| 690 | comptime fop: Opcode, | 1663 | comptime fop: Opcode, |
| 691 | comptime sop: Opcode, | 1664 | comptime sop: Opcode, |
| 692 | comptime uop: Opcode, | 1665 | comptime uop: Opcode, |
| 693 | ) !IdRef { | 1666 | /// true if this operation holds under modular arithmetic. |
| 1667 | comptime modular: bool, | ||
| 1668 | ) !?IdRef { | ||
| 1669 | if (self.liveness.isUnused(inst)) return null; | ||
| 694 | // LHS and RHS are guaranteed to have the same type, and AIR guarantees | 1670 | // LHS and RHS are guaranteed to have the same type, and AIR guarantees |
| 695 | // the result to be the same as the LHS and RHS, which matches SPIR-V. | 1671 | // the result to be the same as the LHS and RHS, which matches SPIR-V. |
| 696 | const ty = self.air.typeOfIndex(inst); | 1672 | const ty = self.air.typeOfIndex(inst); |
| 697 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | 1673 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 698 | const lhs_id = try self.resolve(bin_op.lhs); | 1674 | var lhs_id = try self.resolve(bin_op.lhs); |
| 699 | const rhs_id = try self.resolve(bin_op.rhs); | 1675 | var rhs_id = try self.resolve(bin_op.rhs); |
| 700 | 1676 | ||
| 701 | const result_id = self.spv.allocId(); | 1677 | const result_ty_ref = try self.resolveType(ty, .direct); |
| 702 | const result_type_id = try self.resolveTypeId(ty); | ||
| 703 | 1678 | ||
| 704 | assert(self.air.typeOf(bin_op.lhs).eql(ty, self.module)); | 1679 | assert(self.air.typeOf(bin_op.lhs).eql(ty, self.module)); |
| 705 | assert(self.air.typeOf(bin_op.rhs).eql(ty, self.module)); | 1680 | assert(self.air.typeOf(bin_op.rhs).eql(ty, self.module)); |
| ... | @@ -712,19 +1687,27 @@ pub const DeclGen = struct { | ... | @@ -712,19 +1687,27 @@ pub const DeclGen = struct { |
| 712 | .composite_integer => { | 1687 | .composite_integer => { |
| 713 | return self.todo("binary operations for composite integers", .{}); | 1688 | return self.todo("binary operations for composite integers", .{}); |
| 714 | }, | 1689 | }, |
| 715 | .strange_integer => { | 1690 | .strange_integer => blk: { |
| 716 | return self.todo("binary operations for strange integers", .{}); | 1691 | if (!modular) { |
| 1692 | lhs_id = try self.maskStrangeInt(result_ty_ref, lhs_id, info.bits); | ||
| 1693 | rhs_id = try self.maskStrangeInt(result_ty_ref, rhs_id, info.bits); | ||
| 1694 | } | ||
| 1695 | break :blk switch (info.signedness) { | ||
| 1696 | .signed => @as(usize, 1), | ||
| 1697 | .unsigned => @as(usize, 2), | ||
| 1698 | }; | ||
| 717 | }, | 1699 | }, |
| 718 | .integer => switch (info.signedness) { | 1700 | .integer => switch (info.signedness) { |
| 719 | .signed => @as(usize, 1), | 1701 | .signed => @as(usize, 1), |
| 720 | .unsigned => @as(usize, 2), | 1702 | .unsigned => @as(usize, 2), |
| 721 | }, | 1703 | }, |
| 722 | .float => 0, | 1704 | .float => 0, |
| 723 | else => unreachable, | 1705 | .bool => unreachable, |
| 724 | }; | 1706 | }; |
| 725 | 1707 | ||
| 1708 | const result_id = self.spv.allocId(); | ||
| 726 | const operands = .{ | 1709 | const operands = .{ |
| 727 | .id_result_type = result_type_id, | 1710 | .id_result_type = self.typeId(result_ty_ref), |
| 728 | .id_result = result_id, | 1711 | .id_result = result_id, |
| 729 | .operand_1 = lhs_id, | 1712 | .operand_1 = lhs_id, |
| 730 | .operand_2 = rhs_id, | 1713 | .operand_2 = rhs_id, |
| ... | @@ -739,10 +1722,90 @@ pub const DeclGen = struct { | ... | @@ -739,10 +1722,90 @@ pub const DeclGen = struct { |
| 739 | // TODO: Trap on overflow? Probably going to be annoying. | 1722 | // TODO: Trap on overflow? Probably going to be annoying. |
| 740 | // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap. | 1723 | // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap. |
| 741 | 1724 | ||
| 742 | return result_id.toRef(); | 1725 | return result_id; |
| 1726 | } | ||
| 1727 | |||
| 1728 | fn airOverflowArithOp(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { | ||
| 1729 | if (self.liveness.isUnused(inst)) return null; | ||
| 1730 | |||
| 1731 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; | ||
| 1732 | const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; | ||
| 1733 | const lhs = try self.resolve(extra.lhs); | ||
| 1734 | const rhs = try self.resolve(extra.rhs); | ||
| 1735 | |||
| 1736 | const operand_ty = self.air.typeOf(extra.lhs); | ||
| 1737 | const result_ty = self.air.typeOfIndex(inst); | ||
| 1738 | |||
| 1739 | const info = try self.arithmeticTypeInfo(operand_ty); | ||
| 1740 | switch (info.class) { | ||
| 1741 | .composite_integer => return self.todo("overflow ops for composite integers", .{}), | ||
| 1742 | .strange_integer => return self.todo("overflow ops for strange integers", .{}), | ||
| 1743 | .integer => {}, | ||
| 1744 | .float, .bool => unreachable, | ||
| 1745 | } | ||
| 1746 | |||
| 1747 | const operand_ty_id = try self.resolveTypeId(operand_ty); | ||
| 1748 | const result_type_id = try self.resolveTypeId(result_ty); | ||
| 1749 | |||
| 1750 | const overflow_member_ty = try self.intType(.unsigned, info.bits); | ||
| 1751 | const overflow_member_ty_id = self.typeId(overflow_member_ty); | ||
| 1752 | |||
| 1753 | const op_result_id = blk: { | ||
| 1754 | // Construct the SPIR-V result type. | ||
| 1755 | // It is almost the same as the zig one, except that the fields must be the same type | ||
| 1756 | // and they must be unsigned. | ||
| 1757 | const overflow_result_ty_ref = try self.spv.simpleStructType(&.{ | ||
| 1758 | .{ .ty = overflow_member_ty, .name = "res" }, | ||
| 1759 | .{ .ty = overflow_member_ty, .name = "ov" }, | ||
| 1760 | }); | ||
| 1761 | const result_id = self.spv.allocId(); | ||
| 1762 | try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{ | ||
| 1763 | .id_result_type = self.typeId(overflow_result_ty_ref), | ||
| 1764 | .id_result = result_id, | ||
| 1765 | .operand_1 = lhs, | ||
| 1766 | .operand_2 = rhs, | ||
| 1767 | }); | ||
| 1768 | break :blk result_id; | ||
| 1769 | }; | ||
| 1770 | |||
| 1771 | // Now convert the SPIR-V flavor result into a Zig-flavor result. | ||
| 1772 | // First, extract the two fields. | ||
| 1773 | const unsigned_result = try self.extractField(overflow_member_ty_id, op_result_id, 0); | ||
| 1774 | const overflow = try self.extractField(overflow_member_ty_id, op_result_id, 1); | ||
| 1775 | |||
| 1776 | // We need to convert the results to the types that Zig expects here. | ||
| 1777 | // The `result` is the same type except unsigned, so we can just bitcast that. | ||
| 1778 | const result = try self.bitcast(operand_ty_id, unsigned_result); | ||
| 1779 | |||
| 1780 | // The overflow needs to be converted into whatever is used to represent it in Zig. | ||
| 1781 | const casted_overflow = blk: { | ||
| 1782 | const ov_ty = result_ty.tupleFields().types[1]; | ||
| 1783 | const ov_ty_id = try self.resolveTypeId(ov_ty); | ||
| 1784 | const result_id = self.spv.allocId(); | ||
| 1785 | try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ | ||
| 1786 | .id_result_type = ov_ty_id, | ||
| 1787 | .id_result = result_id, | ||
| 1788 | .unsigned_value = overflow, | ||
| 1789 | }); | ||
| 1790 | break :blk result_id; | ||
| 1791 | }; | ||
| 1792 | |||
| 1793 | // TODO: If copying this function for borrow, make sure to convert -1 to 1 as appropriate. | ||
| 1794 | |||
| 1795 | // Finally, construct the Zig type. | ||
| 1796 | // Layout is result, overflow. | ||
| 1797 | const result_id = self.spv.allocId(); | ||
| 1798 | const constituents = [_]IdRef{ result, casted_overflow }; | ||
| 1799 | try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{ | ||
| 1800 | .id_result_type = result_type_id, | ||
| 1801 | .id_result = result_id, | ||
| 1802 | .constituents = &constituents, | ||
| 1803 | }); | ||
| 1804 | return result_id; | ||
| 743 | } | 1805 | } |
| 744 | 1806 | ||
| 745 | fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !IdRef { | 1807 | fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 1808 | if (self.liveness.isUnused(inst)) return null; | ||
| 746 | const ty = self.air.typeOfIndex(inst); | 1809 | const ty = self.air.typeOfIndex(inst); |
| 747 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; | 1810 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; |
| 748 | const extra = self.air.extraData(Air.Shuffle, ty_pl.payload).data; | 1811 | const extra = self.air.extraData(Air.Shuffle, ty_pl.payload).data; |
| ... | @@ -774,15 +1837,16 @@ pub const DeclGen = struct { | ... | @@ -774,15 +1837,16 @@ pub const DeclGen = struct { |
| 774 | self.func.body.writeOperand(spec.LiteralInteger, unsigned); | 1837 | self.func.body.writeOperand(spec.LiteralInteger, unsigned); |
| 775 | } | 1838 | } |
| 776 | } | 1839 | } |
| 777 | return result_id.toRef(); | 1840 | return result_id; |
| 778 | } | 1841 | } |
| 779 | 1842 | ||
| 780 | fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !IdRef { | 1843 | fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !?IdRef { |
| 1844 | if (self.liveness.isUnused(inst)) return null; | ||
| 781 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | 1845 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 782 | const lhs_id = try self.resolve(bin_op.lhs); | 1846 | var lhs_id = try self.resolve(bin_op.lhs); |
| 783 | const rhs_id = try self.resolve(bin_op.rhs); | 1847 | var rhs_id = try self.resolve(bin_op.rhs); |
| 784 | const result_id = self.spv.allocId(); | 1848 | const result_id = self.spv.allocId(); |
| 785 | const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); | 1849 | const result_type_id = try self.resolveTypeId(Type.bool); |
| 786 | const op_ty = self.air.typeOf(bin_op.lhs); | 1850 | const op_ty = self.air.typeOf(bin_op.lhs); |
| 787 | assert(op_ty.eql(self.air.typeOf(bin_op.rhs), self.module)); | 1851 | assert(op_ty.eql(self.air.typeOf(bin_op.rhs), self.module)); |
| 788 | 1852 | ||
| ... | @@ -794,11 +1858,17 @@ pub const DeclGen = struct { | ... | @@ -794,11 +1858,17 @@ pub const DeclGen = struct { |
| 794 | .composite_integer => { | 1858 | .composite_integer => { |
| 795 | return self.todo("binary operations for composite integers", .{}); | 1859 | return self.todo("binary operations for composite integers", .{}); |
| 796 | }, | 1860 | }, |
| 797 | .strange_integer => { | ||
| 798 | return self.todo("comparison for strange integers", .{}); | ||
| 799 | }, | ||
| 800 | .float => 0, | 1861 | .float => 0, |
| 801 | .bool => 1, | 1862 | .bool => 1, |
| 1863 | .strange_integer => blk: { | ||
| 1864 | const op_ty_ref = try self.resolveType(op_ty, .direct); | ||
| 1865 | lhs_id = try self.maskStrangeInt(op_ty_ref, lhs_id, info.bits); | ||
| 1866 | rhs_id = try self.maskStrangeInt(op_ty_ref, rhs_id, info.bits); | ||
| 1867 | break :blk switch (info.signedness) { | ||
| 1868 | .signed => @as(usize, 1), | ||
| 1869 | .unsigned => @as(usize, 2), | ||
| 1870 | }; | ||
| 1871 | }, | ||
| 802 | .integer => switch (info.signedness) { | 1872 | .integer => switch (info.signedness) { |
| 803 | .signed => @as(usize, 1), | 1873 | .signed => @as(usize, 1), |
| 804 | .unsigned => @as(usize, 2), | 1874 | .unsigned => @as(usize, 2), |
| ... | @@ -819,41 +1889,336 @@ pub const DeclGen = struct { | ... | @@ -819,41 +1889,336 @@ pub const DeclGen = struct { |
| 819 | else => unreachable, | 1889 | else => unreachable, |
| 820 | } | 1890 | } |
| 821 | 1891 | ||
| 822 | return result_id.toRef(); | 1892 | return result_id; |
| 1893 | } | ||
| 1894 | |||
| 1895 | fn bitcast(self: *DeclGen, target_type_id: IdResultType, value_id: IdRef) !IdRef { | ||
| 1896 | const result_id = self.spv.allocId(); | ||
| 1897 | try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ | ||
| 1898 | .id_result_type = target_type_id, | ||
| 1899 | .id_result = result_id, | ||
| 1900 | .operand = value_id, | ||
| 1901 | }); | ||
| 1902 | return result_id; | ||
| 1903 | } | ||
| 1904 | |||
| 1905 | fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { | ||
| 1906 | if (self.liveness.isUnused(inst)) return null; | ||
| 1907 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | ||
| 1908 | const operand_id = try self.resolve(ty_op.operand); | ||
| 1909 | const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst)); | ||
| 1910 | return try self.bitcast(result_type_id, operand_id); | ||
| 1911 | } | ||
| 1912 | |||
| 1913 | fn airIntcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { | ||
| 1914 | if (self.liveness.isUnused(inst)) return null; | ||
| 1915 | |||
| 1916 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | ||
| 1917 | const operand_id = try self.resolve(ty_op.operand); | ||
| 1918 | const dest_ty = self.air.typeOfIndex(inst); | ||
| 1919 | const dest_info = try self.arithmeticTypeInfo(dest_ty); | ||
| 1920 | const dest_ty_id = try self.resolveTypeId(dest_ty); | ||
| 1921 | |||
| 1922 | const result_id = self.spv.allocId(); | ||
| 1923 | switch (dest_info.signedness) { | ||
| 1924 | .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{ | ||
| 1925 | .id_result_type = dest_ty_id, | ||
| 1926 | .id_result = result_id, | ||
| 1927 | .signed_value = operand_id, | ||
| 1928 | }), | ||
| 1929 | .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ | ||
| 1930 | .id_result_type = dest_ty_id, | ||
| 1931 | .id_result = result_id, | ||
| 1932 | .unsigned_value = operand_id, | ||
| 1933 | }), | ||
| 1934 | } | ||
| 1935 | return result_id; | ||
| 823 | } | 1936 | } |
| 824 | 1937 | ||
| 825 | fn airNot(self: *DeclGen, inst: Air.Inst.Index) !IdRef { | 1938 | fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 1939 | if (self.liveness.isUnused(inst)) return null; | ||
| 826 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | 1940 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 827 | const operand_id = try self.resolve(ty_op.operand); | 1941 | const operand_id = try self.resolve(ty_op.operand); |
| 828 | const result_id = self.spv.allocId(); | 1942 | const result_id = self.spv.allocId(); |
| 829 | const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); | 1943 | const result_type_id = try self.resolveTypeId(Type.bool); |
| 830 | try self.func.body.emit(self.spv.gpa, .OpLogicalNot, .{ | 1944 | try self.func.body.emit(self.spv.gpa, .OpLogicalNot, .{ |
| 831 | .id_result_type = result_type_id, | 1945 | .id_result_type = result_type_id, |
| 832 | .id_result = result_id, | 1946 | .id_result = result_id, |
| 833 | .operand = operand_id, | 1947 | .operand = operand_id, |
| 834 | }); | 1948 | }); |
| 835 | return result_id.toRef(); | 1949 | return result_id; |
| 836 | } | 1950 | } |
| 837 | 1951 | ||
| 838 | fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !IdRef { | 1952 | fn extractField(self: *DeclGen, result_ty: IdResultType, object: IdRef, field: u32) !IdRef { |
| 839 | const ty = self.air.typeOfIndex(inst); | ||
| 840 | const result_type_id = try self.resolveTypeId(ty); | ||
| 841 | const result_id = self.spv.allocId(); | 1953 | const result_id = self.spv.allocId(); |
| 1954 | const indexes = [_]u32{field}; | ||
| 1955 | try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ | ||
| 1956 | .id_result_type = result_ty, | ||
| 1957 | .id_result = result_id, | ||
| 1958 | .composite = object, | ||
| 1959 | .indexes = &indexes, | ||
| 1960 | }); | ||
| 1961 | return result_id; | ||
| 1962 | } | ||
| 842 | 1963 | ||
| 843 | // Rather than generating into code here, we're just going to generate directly into the functions section so that | 1964 | fn airSliceField(self: *DeclGen, inst: Air.Inst.Index, field: u32) !?IdRef { |
| 844 | // variable declarations appear in the first block of the function. | 1965 | if (self.liveness.isUnused(inst)) return null; |
| 845 | const storage_class = spirvStorageClass(ty.ptrAddressSpace()); | 1966 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 846 | const section = if (storage_class == .Function) | 1967 | return try self.extractField( |
| 847 | &self.func.prologue | 1968 | try self.resolveTypeId(self.air.typeOfIndex(inst)), |
| 848 | else | 1969 | try self.resolve(ty_op.operand), |
| 849 | &self.spv.sections.types_globals_constants; | 1970 | field, |
| 1971 | ); | ||
| 1972 | } | ||
| 850 | 1973 | ||
| 851 | try section.emit(self.spv.gpa, .OpVariable, .{ | 1974 | fn airSliceElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 1975 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | ||
| 1976 | const slice_ty = self.air.typeOf(bin_op.lhs); | ||
| 1977 | if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; | ||
| 1978 | |||
| 1979 | const slice = try self.resolve(bin_op.lhs); | ||
| 1980 | const index = try self.resolve(bin_op.rhs); | ||
| 1981 | |||
| 1982 | const spv_ptr_ty = try self.resolveTypeId(self.air.typeOfIndex(inst)); | ||
| 1983 | |||
| 1984 | const slice_ptr = blk: { | ||
| 1985 | const result_id = self.spv.allocId(); | ||
| 1986 | try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ | ||
| 1987 | .id_result_type = spv_ptr_ty, | ||
| 1988 | .id_result = result_id, | ||
| 1989 | .composite = slice, | ||
| 1990 | .indexes = &.{0}, | ||
| 1991 | }); | ||
| 1992 | break :blk result_id; | ||
| 1993 | }; | ||
| 1994 | |||
| 1995 | const result_id = self.spv.allocId(); | ||
| 1996 | try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{ | ||
| 1997 | .id_result_type = spv_ptr_ty, | ||
| 1998 | .id_result = result_id, | ||
| 1999 | .base = slice_ptr, | ||
| 2000 | .element = index, | ||
| 2001 | }); | ||
| 2002 | return result_id; | ||
| 2003 | } | ||
| 2004 | |||
| 2005 | fn airSliceElemVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { | ||
| 2006 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | ||
| 2007 | const slice_ty = self.air.typeOf(bin_op.lhs); | ||
| 2008 | if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; | ||
| 2009 | |||
| 2010 | const slice = try self.resolve(bin_op.lhs); | ||
| 2011 | const index = try self.resolve(bin_op.rhs); | ||
| 2012 | |||
| 2013 | var slice_buf: Type.SlicePtrFieldTypeBuffer = undefined; | ||
| 2014 | const ptr_ty_id = try self.resolveTypeId(slice_ty.slicePtrFieldType(&slice_buf)); | ||
| 2015 | |||
| 2016 | const slice_ptr = blk: { | ||
| 2017 | const result_id = self.spv.allocId(); | ||
| 2018 | try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ | ||
| 2019 | .id_result_type = ptr_ty_id, | ||
| 2020 | .id_result = result_id, | ||
| 2021 | .composite = slice, | ||
| 2022 | .indexes = &.{0}, | ||
| 2023 | }); | ||
| 2024 | break :blk result_id; | ||
| 2025 | }; | ||
| 2026 | |||
| 2027 | const elem_ptr = blk: { | ||
| 2028 | const result_id = self.spv.allocId(); | ||
| 2029 | try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{ | ||
| 2030 | .id_result_type = ptr_ty_id, | ||
| 2031 | .id_result = result_id, | ||
| 2032 | .base = slice_ptr, | ||
| 2033 | .element = index, | ||
| 2034 | }); | ||
| 2035 | break :blk result_id; | ||
| 2036 | }; | ||
| 2037 | |||
| 2038 | return try self.load(slice_ty, elem_ptr); | ||
| 2039 | } | ||
| 2040 | |||
| 2041 | fn airPtrElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { | ||
| 2042 | if (self.liveness.isUnused(inst)) return null; | ||
| 2043 | |||
| 2044 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; | ||
| 2045 | const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; | ||
| 2046 | const ptr_ty = self.air.typeOf(bin_op.lhs); | ||
| 2047 | const result_ty = self.air.typeOfIndex(inst); | ||
| 2048 | const elem_ty = ptr_ty.childType(); | ||
| 2049 | // TODO: Make this return a null ptr or something | ||
| 2050 | if (!elem_ty.hasRuntimeBitsIgnoreComptime()) return null; | ||
| 2051 | |||
| 2052 | const result_type_id = try self.resolveTypeId(result_ty); | ||
| 2053 | const base_ptr = try self.resolve(bin_op.lhs); | ||
| 2054 | const rhs = try self.resolve(bin_op.rhs); | ||
| 2055 | |||
| 2056 | const result_id = self.spv.allocId(); | ||
| 2057 | const indexes = [_]IdRef{rhs}; | ||
| 2058 | try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ | ||
| 852 | .id_result_type = result_type_id, | 2059 | .id_result_type = result_type_id, |
| 853 | .id_result = result_id, | 2060 | .id_result = result_id, |
| 854 | .storage_class = storage_class, | 2061 | .base = base_ptr, |
| 2062 | .indexes = &indexes, | ||
| 2063 | }); | ||
| 2064 | return result_id; | ||
| 2065 | } | ||
| 2066 | |||
| 2067 | fn airStructFieldVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { | ||
| 2068 | if (self.liveness.isUnused(inst)) return null; | ||
| 2069 | |||
| 2070 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; | ||
| 2071 | const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; | ||
| 2072 | |||
| 2073 | const struct_ty = self.air.typeOf(struct_field.struct_operand); | ||
| 2074 | const object = try self.resolve(struct_field.struct_operand); | ||
| 2075 | const field_index = struct_field.field_index; | ||
| 2076 | const field_ty = struct_ty.structFieldType(field_index); | ||
| 2077 | const field_ty_id = try self.resolveTypeId(field_ty); | ||
| 2078 | |||
| 2079 | if (!field_ty.hasRuntimeBitsIgnoreComptime()) return null; | ||
| 2080 | |||
| 2081 | assert(struct_ty.zigTypeTag() == .Struct); // Cannot do unions yet. | ||
| 2082 | |||
| 2083 | const result_id = self.spv.allocId(); | ||
| 2084 | const indexes = [_]u32{field_index}; | ||
| 2085 | try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ | ||
| 2086 | .id_result_type = field_ty_id, | ||
| 2087 | .id_result = result_id, | ||
| 2088 | .composite = object, | ||
| 2089 | .indexes = &indexes, | ||
| 855 | }); | 2090 | }); |
| 856 | return result_id.toRef(); | 2091 | return result_id; |
| 2092 | } | ||
| 2093 | |||
| 2094 | fn structFieldPtr( | ||
| 2095 | self: *DeclGen, | ||
| 2096 | result_ptr_ty: Type, | ||
| 2097 | object_ptr_ty: Type, | ||
| 2098 | object_ptr: IdRef, | ||
| 2099 | field_index: u32, | ||
| 2100 | ) !?IdRef { | ||
| 2101 | const object_ty = object_ptr_ty.childType(); | ||
| 2102 | switch (object_ty.zigTypeTag()) { | ||
| 2103 | .Struct => switch (object_ty.containerLayout()) { | ||
| 2104 | .Packed => unreachable, // TODO | ||
| 2105 | else => { | ||
| 2106 | const u32_ty_id = self.typeId(try self.intType(.unsigned, 32)); | ||
| 2107 | const field_index_id = self.spv.allocId(); | ||
| 2108 | try self.spv.emitConstant(u32_ty_id, field_index_id, .{ .uint32 = field_index }); | ||
| 2109 | const result_id = self.spv.allocId(); | ||
| 2110 | const result_type_id = try self.resolveTypeId(result_ptr_ty); | ||
| 2111 | const indexes = [_]IdRef{field_index_id}; | ||
| 2112 | try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ | ||
| 2113 | .id_result_type = result_type_id, | ||
| 2114 | .id_result = result_id, | ||
| 2115 | .base = object_ptr, | ||
| 2116 | .indexes = &indexes, | ||
| 2117 | }); | ||
| 2118 | return result_id; | ||
| 2119 | }, | ||
| 2120 | }, | ||
| 2121 | else => unreachable, // TODO | ||
| 2122 | } | ||
| 2123 | } | ||
| 2124 | |||
| 2125 | fn airStructFieldPtrIndex(self: *DeclGen, inst: Air.Inst.Index, field_index: u32) !?IdRef { | ||
| 2126 | if (self.liveness.isUnused(inst)) return null; | ||
| 2127 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | ||
| 2128 | const struct_ptr = try self.resolve(ty_op.operand); | ||
| 2129 | const struct_ptr_ty = self.air.typeOf(ty_op.operand); | ||
| 2130 | const result_ptr_ty = self.air.typeOfIndex(inst); | ||
| 2131 | return try self.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index); | ||
| 2132 | } | ||
| 2133 | |||
| 2134 | /// We cannot use an OpVariable directly in an OpSpecConstantOp, but we can | ||
| 2135 | /// after we insert a dummy AccessChain... | ||
| 2136 | /// TODO: Get rid of this | ||
| 2137 | fn makePointerConstant( | ||
| 2138 | self: *DeclGen, | ||
| 2139 | section: *SpvSection, | ||
| 2140 | ptr_ty_ref: SpvType.Ref, | ||
| 2141 | ptr_id: IdRef, | ||
| 2142 | ) !IdRef { | ||
| 2143 | const result_id = self.spv.allocId(); | ||
| 2144 | try section.emitSpecConstantOp(self.spv.gpa, .OpInBoundsAccessChain, .{ | ||
| 2145 | .id_result_type = self.typeId(ptr_ty_ref), | ||
| 2146 | .id_result = result_id, | ||
| 2147 | .base = ptr_id, | ||
| 2148 | }); | ||
| 2149 | return result_id; | ||
| 2150 | } | ||
| 2151 | |||
| 2152 | fn variable( | ||
| 2153 | self: *DeclGen, | ||
| 2154 | comptime context: enum { function, global }, | ||
| 2155 | result_id: IdRef, | ||
| 2156 | ptr_ty_ref: SpvType.Ref, | ||
| 2157 | initializer: ?IdRef, | ||
| 2158 | ) !void { | ||
| 2159 | const storage_class = self.spv.typeRefType(ptr_ty_ref).payload(.pointer).storage_class; | ||
| 2160 | const actual_storage_class = switch (storage_class) { | ||
| 2161 | .Generic => switch (context) { | ||
| 2162 | .function => .Function, | ||
| 2163 | .global => .CrossWorkgroup, | ||
| 2164 | }, | ||
| 2165 | else => storage_class, | ||
| 2166 | }; | ||
| 2167 | const actual_ptr_ty_ref = switch (storage_class) { | ||
| 2168 | .Generic => try self.spv.changePtrStorageClass(ptr_ty_ref, actual_storage_class), | ||
| 2169 | else => ptr_ty_ref, | ||
| 2170 | }; | ||
| 2171 | const alloc_result_id = switch (storage_class) { | ||
| 2172 | .Generic => self.spv.allocId(), | ||
| 2173 | else => result_id, | ||
| 2174 | }; | ||
| 2175 | |||
| 2176 | const section = switch (actual_storage_class) { | ||
| 2177 | .Generic => unreachable, | ||
| 2178 | // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to | ||
| 2179 | // directly generate them into func.prologue instead of the body. | ||
| 2180 | .Function => &self.func.prologue, | ||
| 2181 | else => &self.spv.sections.types_globals_constants, | ||
| 2182 | }; | ||
| 2183 | try section.emit(self.spv.gpa, .OpVariable, .{ | ||
| 2184 | .id_result_type = self.typeId(actual_ptr_ty_ref), | ||
| 2185 | .id_result = alloc_result_id, | ||
| 2186 | .storage_class = actual_storage_class, | ||
| 2187 | .initializer = initializer, | ||
| 2188 | }); | ||
| 2189 | |||
| 2190 | if (storage_class != .Generic) { | ||
| 2191 | return; | ||
| 2192 | } | ||
| 2193 | |||
| 2194 | // Now we need to convert the pointer. | ||
| 2195 | // If this is a function local, we need to perform the conversion at runtime. Otherwise, we can do | ||
| 2196 | // it ahead of time using OpSpecConstantOp. | ||
| 2197 | switch (actual_storage_class) { | ||
| 2198 | .Function => try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{ | ||
| 2199 | .id_result_type = self.typeId(ptr_ty_ref), | ||
| 2200 | .id_result = result_id, | ||
| 2201 | .pointer = alloc_result_id, | ||
| 2202 | }), | ||
| 2203 | // TODO: Can we do without this cast or move it to runtime? | ||
| 2204 | else => { | ||
| 2205 | const const_ptr_id = try self.makePointerConstant(section, actual_ptr_ty_ref, alloc_result_id); | ||
| 2206 | try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ | ||
| 2207 | .id_result_type = self.typeId(ptr_ty_ref), | ||
| 2208 | .id_result = result_id, | ||
| 2209 | .pointer = const_ptr_id, | ||
| 2210 | }); | ||
| 2211 | }, | ||
| 2212 | } | ||
| 2213 | } | ||
| 2214 | |||
| 2215 | fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { | ||
| 2216 | if (self.liveness.isUnused(inst)) return null; | ||
| 2217 | const ty = self.air.typeOfIndex(inst); | ||
| 2218 | const result_ty_ref = try self.resolveType(ty, .direct); | ||
| 2219 | const result_id = self.spv.allocId(); | ||
| 2220 | try self.variable(.function, result_id, result_ty_ref, null); | ||
| 2221 | return result_id; | ||
| 857 | } | 2222 | } |
| 858 | 2223 | ||
| 859 | fn airArg(self: *DeclGen) IdRef { | 2224 | fn airArg(self: *DeclGen) IdRef { |
| ... | @@ -873,7 +2238,7 @@ pub const DeclGen = struct { | ... | @@ -873,7 +2238,7 @@ pub const DeclGen = struct { |
| 873 | var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.gpa, 4); | 2238 | var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.gpa, 4); |
| 874 | 2239 | ||
| 875 | try self.blocks.putNoClobber(self.gpa, inst, .{ | 2240 | try self.blocks.putNoClobber(self.gpa, inst, .{ |
| 876 | .label_id = label_id.toRef(), | 2241 | .label_id = label_id, |
| 877 | .incoming_blocks = &incoming_blocks, | 2242 | .incoming_blocks = &incoming_blocks, |
| 878 | }); | 2243 | }); |
| 879 | defer { | 2244 | defer { |
| ... | @@ -890,7 +2255,7 @@ pub const DeclGen = struct { | ... | @@ -890,7 +2255,7 @@ pub const DeclGen = struct { |
| 890 | try self.beginSpvBlock(label_id); | 2255 | try self.beginSpvBlock(label_id); |
| 891 | 2256 | ||
| 892 | // If this block didn't produce a value, simply return here. | 2257 | // If this block didn't produce a value, simply return here. |
| 893 | if (!ty.hasRuntimeBits()) | 2258 | if (!ty.hasRuntimeBitsIgnoreComptime()) |
| 894 | return null; | 2259 | return null; |
| 895 | 2260 | ||
| 896 | // Combine the result from the blocks using the Phi instruction. | 2261 | // Combine the result from the blocks using the Phi instruction. |
| ... | @@ -908,7 +2273,7 @@ pub const DeclGen = struct { | ... | @@ -908,7 +2273,7 @@ pub const DeclGen = struct { |
| 908 | self.func.body.writeOperand(spec.PairIdRefIdRef, .{ incoming.break_value_id, incoming.src_label_id }); | 2273 | self.func.body.writeOperand(spec.PairIdRefIdRef, .{ incoming.break_value_id, incoming.src_label_id }); |
| 909 | } | 2274 | } |
| 910 | 2275 | ||
| 911 | return result_id.toRef(); | 2276 | return result_id; |
| 912 | } | 2277 | } |
| 913 | 2278 | ||
| 914 | fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void { | 2279 | fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void { |
| ... | @@ -941,8 +2306,8 @@ pub const DeclGen = struct { | ... | @@ -941,8 +2306,8 @@ pub const DeclGen = struct { |
| 941 | 2306 | ||
| 942 | try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{ | 2307 | try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{ |
| 943 | .condition = condition_id, | 2308 | .condition = condition_id, |
| 944 | .true_label = then_label_id.toRef(), | 2309 | .true_label = then_label_id, |
| 945 | .false_label = else_label_id.toRef(), | 2310 | .false_label = else_label_id, |
| 946 | }); | 2311 | }); |
| 947 | 2312 | ||
| 948 | try self.beginSpvBlock(then_label_id); | 2313 | try self.beginSpvBlock(then_label_id); |
| ... | @@ -961,26 +2326,80 @@ pub const DeclGen = struct { | ... | @@ -961,26 +2326,80 @@ pub const DeclGen = struct { |
| 961 | }); | 2326 | }); |
| 962 | } | 2327 | } |
| 963 | 2328 | ||
| 964 | fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !IdRef { | 2329 | fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 965 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | 2330 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 966 | const operand_id = try self.resolve(ty_op.operand); | 2331 | const ptr_ty = self.air.typeOf(ty_op.operand); |
| 967 | const ty = self.air.typeOfIndex(inst); | 2332 | const operand = try self.resolve(ty_op.operand); |
| 2333 | if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; | ||
| 968 | 2334 | ||
| 969 | const result_type_id = try self.resolveTypeId(ty); | 2335 | return try self.load(ptr_ty, operand); |
| 970 | const result_id = self.spv.allocId(); | 2336 | } |
| 971 | 2337 | ||
| 2338 | fn load(self: *DeclGen, ptr_ty: Type, ptr: IdRef) !IdRef { | ||
| 2339 | const value_ty = ptr_ty.childType(); | ||
| 2340 | const direct_result_ty_ref = try self.resolveType(value_ty, .direct); | ||
| 2341 | const indirect_result_ty_ref = try self.resolveType(value_ty, .indirect); | ||
| 2342 | const result_id = self.spv.allocId(); | ||
| 972 | const access = spec.MemoryAccess.Extended{ | 2343 | const access = spec.MemoryAccess.Extended{ |
| 973 | .Volatile = ty.isVolatilePtr(), | 2344 | .Volatile = ptr_ty.isVolatilePtr(), |
| 974 | }; | 2345 | }; |
| 975 | |||
| 976 | try self.func.body.emit(self.spv.gpa, .OpLoad, .{ | 2346 | try self.func.body.emit(self.spv.gpa, .OpLoad, .{ |
| 977 | .id_result_type = result_type_id, | 2347 | .id_result_type = self.typeId(indirect_result_ty_ref), |
| 978 | .id_result = result_id, | 2348 | .id_result = result_id, |
| 979 | .pointer = operand_id, | 2349 | .pointer = ptr, |
| 980 | .memory_access = access, | 2350 | .memory_access = access, |
| 981 | }); | 2351 | }); |
| 2352 | if (value_ty.zigTypeTag() == .Bool) { | ||
| 2353 | // Convert indirect bool to direct bool | ||
| 2354 | const zero_id = try self.constInt(indirect_result_ty_ref, 0); | ||
| 2355 | const casted_result_id = self.spv.allocId(); | ||
| 2356 | try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{ | ||
| 2357 | .id_result_type = self.typeId(direct_result_ty_ref), | ||
| 2358 | .id_result = casted_result_id, | ||
| 2359 | .operand_1 = result_id, | ||
| 2360 | .operand_2 = zero_id, | ||
| 2361 | }); | ||
| 2362 | return casted_result_id; | ||
| 2363 | } | ||
| 2364 | return result_id; | ||
| 2365 | } | ||
| 2366 | |||
| 2367 | fn airStore(self: *DeclGen, inst: Air.Inst.Index) !void { | ||
| 2368 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | ||
| 2369 | const ptr_ty = self.air.typeOf(bin_op.lhs); | ||
| 2370 | const ptr = try self.resolve(bin_op.lhs); | ||
| 2371 | const value = try self.resolve(bin_op.rhs); | ||
| 982 | 2372 | ||
| 983 | return result_id.toRef(); | 2373 | try self.store(ptr_ty, ptr, value); |
| 2374 | } | ||
| 2375 | |||
| 2376 | fn store(self: *DeclGen, ptr_ty: Type, ptr: IdRef, value: IdRef) !void { | ||
| 2377 | const value_ty = ptr_ty.childType(); | ||
| 2378 | const converted_value = switch (value_ty.zigTypeTag()) { | ||
| 2379 | .Bool => blk: { | ||
| 2380 | const indirect_bool_ty_ref = try self.resolveType(value_ty, .indirect); | ||
| 2381 | const result_id = self.spv.allocId(); | ||
| 2382 | const zero = try self.constInt(indirect_bool_ty_ref, 0); | ||
| 2383 | const one = try self.constInt(indirect_bool_ty_ref, 1); | ||
| 2384 | try self.func.body.emit(self.spv.gpa, .OpSelect, .{ | ||
| 2385 | .id_result_type = self.typeId(indirect_bool_ty_ref), | ||
| 2386 | .id_result = result_id, | ||
| 2387 | .condition = value, | ||
| 2388 | .object_1 = one, | ||
| 2389 | .object_2 = zero, | ||
| 2390 | }); | ||
| 2391 | break :blk result_id; | ||
| 2392 | }, | ||
| 2393 | else => value, | ||
| 2394 | }; | ||
| 2395 | const access = spec.MemoryAccess.Extended{ | ||
| 2396 | .Volatile = ptr_ty.isVolatilePtr(), | ||
| 2397 | }; | ||
| 2398 | try self.func.body.emit(self.spv.gpa, .OpStore, .{ | ||
| 2399 | .pointer = ptr, | ||
| 2400 | .object = converted_value, | ||
| 2401 | .memory_access = access, | ||
| 2402 | }); | ||
| 984 | } | 2403 | } |
| 985 | 2404 | ||
| 986 | fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void { | 2405 | fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void { |
| ... | @@ -990,13 +2409,13 @@ pub const DeclGen = struct { | ... | @@ -990,13 +2409,13 @@ pub const DeclGen = struct { |
| 990 | const loop_label_id = self.spv.allocId(); | 2409 | const loop_label_id = self.spv.allocId(); |
| 991 | 2410 | ||
| 992 | // Jump to the loop entry point | 2411 | // Jump to the loop entry point |
| 993 | try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() }); | 2412 | try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id }); |
| 994 | 2413 | ||
| 995 | // TODO: Look into OpLoopMerge. | 2414 | // TODO: Look into OpLoopMerge. |
| 996 | try self.beginSpvBlock(loop_label_id); | 2415 | try self.beginSpvBlock(loop_label_id); |
| 997 | try self.genBody(body); | 2416 | try self.genBody(body); |
| 998 | 2417 | ||
| 999 | try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() }); | 2418 | try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id }); |
| 1000 | } | 2419 | } |
| 1001 | 2420 | ||
| 1002 | fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void { | 2421 | fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void { |
| ... | @@ -1010,23 +2429,138 @@ pub const DeclGen = struct { | ... | @@ -1010,23 +2429,138 @@ pub const DeclGen = struct { |
| 1010 | } | 2429 | } |
| 1011 | } | 2430 | } |
| 1012 | 2431 | ||
| 1013 | fn airStore(self: *DeclGen, inst: Air.Inst.Index) !void { | 2432 | fn airRetLoad(self: *DeclGen, inst: Air.Inst.Index) !void { |
| 1014 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | 2433 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| 1015 | const dst_ptr_id = try self.resolve(bin_op.lhs); | 2434 | const ptr_ty = self.air.typeOf(un_op); |
| 1016 | const src_val_id = try self.resolve(bin_op.rhs); | 2435 | const ret_ty = ptr_ty.childType(); |
| 1017 | const lhs_ty = self.air.typeOf(bin_op.lhs); | ||
| 1018 | 2436 | ||
| 1019 | const access = spec.MemoryAccess.Extended{ | 2437 | if (!ret_ty.hasRuntimeBitsIgnoreComptime()) { |
| 1020 | .Volatile = lhs_ty.isVolatilePtr(), | 2438 | try self.func.body.emit(self.spv.gpa, .OpReturn, {}); |
| 1021 | }; | 2439 | return; |
| 2440 | } | ||
| 1022 | 2441 | ||
| 1023 | try self.func.body.emit(self.spv.gpa, .OpStore, .{ | 2442 | const ptr = try self.resolve(un_op); |
| 1024 | .pointer = dst_ptr_id, | 2443 | const value = try self.load(ptr_ty, ptr); |
| 1025 | .object = src_val_id, | 2444 | try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{ |
| 1026 | .memory_access = access, | 2445 | .value = value, |
| 1027 | }); | 2446 | }); |
| 1028 | } | 2447 | } |
| 1029 | 2448 | ||
| 2449 | fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void { | ||
| 2450 | const target = self.getTarget(); | ||
| 2451 | const pl_op = self.air.instructions.items(.data)[inst].pl_op; | ||
| 2452 | const cond = try self.resolve(pl_op.operand); | ||
| 2453 | const cond_ty = self.air.typeOf(pl_op.operand); | ||
| 2454 | const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload); | ||
| 2455 | |||
| 2456 | const cond_words: u32 = switch (cond_ty.zigTypeTag()) { | ||
| 2457 | .Int => blk: { | ||
| 2458 | const bits = cond_ty.intInfo(target).bits; | ||
| 2459 | const backing_bits = self.backingIntBits(bits) orelse { | ||
| 2460 | return self.todo("implement composite int switch", .{}); | ||
| 2461 | }; | ||
| 2462 | break :blk if (backing_bits <= 32) @as(u32, 1) else 2; | ||
| 2463 | }, | ||
| 2464 | .Enum => blk: { | ||
| 2465 | var buffer: Type.Payload.Bits = undefined; | ||
| 2466 | const int_ty = cond_ty.intTagType(&buffer); | ||
| 2467 | const int_info = int_ty.intInfo(target); | ||
| 2468 | const backing_bits = self.backingIntBits(int_info.bits) orelse { | ||
| 2469 | return self.todo("implement composite int switch", .{}); | ||
| 2470 | }; | ||
| 2471 | break :blk if (backing_bits <= 32) @as(u32, 1) else 2; | ||
| 2472 | }, | ||
| 2473 | else => return self.todo("implement switch for type {s}", .{@tagName(cond_ty.zigTypeTag())}), // TODO: Figure out which types apply here, and work around them as we can only do integers. | ||
| 2474 | }; | ||
| 2475 | |||
| 2476 | const num_cases = switch_br.data.cases_len; | ||
| 2477 | |||
| 2478 | // Compute the total number of arms that we need. | ||
| 2479 | // Zig switches are grouped by condition, so we need to loop through all of them | ||
| 2480 | const num_conditions = blk: { | ||
| 2481 | var extra_index: usize = switch_br.end; | ||
| 2482 | var case_i: u32 = 0; | ||
| 2483 | var num_conditions: u32 = 0; | ||
| 2484 | while (case_i < num_cases) : (case_i += 1) { | ||
| 2485 | const case = self.air.extraData(Air.SwitchBr.Case, extra_index); | ||
| 2486 | const case_body = self.air.extra[case.end + case.data.items_len ..][0..case.data.body_len]; | ||
| 2487 | extra_index = case.end + case.data.items_len + case_body.len; | ||
| 2488 | num_conditions += case.data.items_len; | ||
| 2489 | } | ||
| 2490 | break :blk num_conditions; | ||
| 2491 | }; | ||
| 2492 | |||
| 2493 | // First, pre-allocate the labels for the cases. | ||
| 2494 | const first_case_label = self.spv.allocIds(num_cases); | ||
| 2495 | // We always need the default case - if zig has none, we will generate unreachable there. | ||
| 2496 | const default = self.spv.allocId(); | ||
| 2497 | |||
| 2498 | // Emit the instruction before generating the blocks. | ||
| 2499 | try self.func.body.emitRaw(self.spv.gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions); | ||
| 2500 | self.func.body.writeOperand(IdRef, cond); | ||
| 2501 | self.func.body.writeOperand(IdRef, default); | ||
| 2502 | |||
| 2503 | // Emit each of the cases | ||
| 2504 | { | ||
| 2505 | var extra_index: usize = switch_br.end; | ||
| 2506 | var case_i: u32 = 0; | ||
| 2507 | while (case_i < num_cases) : (case_i += 1) { | ||
| 2508 | // SPIR-V needs a literal here, which' width depends on the case condition. | ||
| 2509 | const case = self.air.extraData(Air.SwitchBr.Case, extra_index); | ||
| 2510 | const items = @ptrCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]); | ||
| 2511 | const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len]; | ||
| 2512 | extra_index = case.end + case.data.items_len + case_body.len; | ||
| 2513 | |||
| 2514 | const label = IdRef{ .id = first_case_label.id + case_i }; | ||
| 2515 | |||
| 2516 | for (items) |item| { | ||
| 2517 | const value = self.air.value(item) orelse { | ||
| 2518 | return self.todo("switch on runtime value???", .{}); | ||
| 2519 | }; | ||
| 2520 | const int_val = switch (cond_ty.zigTypeTag()) { | ||
| 2521 | .Int => if (cond_ty.isSignedInt()) @bitCast(u64, value.toSignedInt(target)) else value.toUnsignedInt(target), | ||
| 2522 | .Enum => blk: { | ||
| 2523 | var int_buffer: Value.Payload.U64 = undefined; | ||
| 2524 | // TODO: figure out of cond_ty is correct (something with enum literals) | ||
| 2525 | break :blk value.enumToInt(cond_ty, &int_buffer).toUnsignedInt(target); // TODO: composite integer constants | ||
| 2526 | }, | ||
| 2527 | else => unreachable, | ||
| 2528 | }; | ||
| 2529 | const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) { | ||
| 2530 | 1 => .{ .uint32 = @intCast(u32, int_val) }, | ||
| 2531 | 2 => .{ .uint64 = int_val }, | ||
| 2532 | else => unreachable, | ||
| 2533 | }; | ||
| 2534 | self.func.body.writeOperand(spec.LiteralContextDependentNumber, int_lit); | ||
| 2535 | self.func.body.writeOperand(IdRef, label); | ||
| 2536 | } | ||
| 2537 | } | ||
| 2538 | } | ||
| 2539 | |||
| 2540 | // Now, finally, we can start emitting each of the cases. | ||
| 2541 | var extra_index: usize = switch_br.end; | ||
| 2542 | var case_i: u32 = 0; | ||
| 2543 | while (case_i < num_cases) : (case_i += 1) { | ||
| 2544 | const case = self.air.extraData(Air.SwitchBr.Case, extra_index); | ||
| 2545 | const items = @ptrCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]); | ||
| 2546 | const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len]; | ||
| 2547 | extra_index = case.end + case.data.items_len + case_body.len; | ||
| 2548 | |||
| 2549 | const label = IdResult{ .id = first_case_label.id + case_i }; | ||
| 2550 | |||
| 2551 | try self.beginSpvBlock(label); | ||
| 2552 | try self.genBody(case_body); | ||
| 2553 | } | ||
| 2554 | |||
| 2555 | const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len]; | ||
| 2556 | try self.beginSpvBlock(default); | ||
| 2557 | if (else_body.len != 0) { | ||
| 2558 | try self.genBody(else_body); | ||
| 2559 | } else { | ||
| 2560 | try self.func.body.emit(self.spv.gpa, .OpUnreachable, {}); | ||
| 2561 | } | ||
| 2562 | } | ||
| 2563 | |||
| 1030 | fn airUnreach(self: *DeclGen) !void { | 2564 | fn airUnreach(self: *DeclGen) !void { |
| 1031 | try self.func.body.emit(self.spv.gpa, .OpUnreachable, {}); | 2565 | try self.func.body.emit(self.spv.gpa, .OpUnreachable, {}); |
| 1032 | } | 2566 | } |
| ... | @@ -1158,4 +2692,47 @@ pub const DeclGen = struct { | ... | @@ -1158,4 +2692,47 @@ pub const DeclGen = struct { |
| 1158 | 2692 | ||
| 1159 | return null; | 2693 | return null; |
| 1160 | } | 2694 | } |
| 2695 | |||
| 2696 | fn airCall(self: *DeclGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !?IdRef { | ||
| 2697 | _ = modifier; | ||
| 2698 | |||
| 2699 | const pl_op = self.air.instructions.items(.data)[inst].pl_op; | ||
| 2700 | const extra = self.air.extraData(Air.Call, pl_op.payload); | ||
| 2701 | const args = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]); | ||
| 2702 | const callee_ty = self.air.typeOf(pl_op.operand); | ||
| 2703 | const zig_fn_ty = switch (callee_ty.zigTypeTag()) { | ||
| 2704 | .Fn => callee_ty, | ||
| 2705 | .Pointer => return self.fail("cannot call function pointers", .{}), | ||
| 2706 | else => unreachable, | ||
| 2707 | }; | ||
| 2708 | const fn_info = zig_fn_ty.fnInfo(); | ||
| 2709 | const return_type = fn_info.return_type; | ||
| 2710 | |||
| 2711 | const result_type_id = try self.resolveTypeId(return_type); | ||
| 2712 | const result_id = self.spv.allocId(); | ||
| 2713 | const callee_id = try self.resolve(pl_op.operand); | ||
| 2714 | |||
| 2715 | try self.func.body.emitRaw(self.spv.gpa, .OpFunctionCall, 3 + args.len); | ||
| 2716 | self.func.body.writeOperand(spec.IdResultType, result_type_id); | ||
| 2717 | self.func.body.writeOperand(spec.IdResult, result_id); | ||
| 2718 | self.func.body.writeOperand(spec.IdRef, callee_id); | ||
| 2719 | |||
| 2720 | for (args) |arg| { | ||
| 2721 | const arg_id = try self.resolve(arg); | ||
| 2722 | const arg_ty = self.air.typeOf(arg); | ||
| 2723 | if (!arg_ty.hasRuntimeBitsIgnoreComptime()) continue; | ||
| 2724 | |||
| 2725 | self.func.body.writeOperand(spec.IdRef, arg_id); | ||
| 2726 | } | ||
| 2727 | |||
| 2728 | if (return_type.isNoReturn()) { | ||
| 2729 | try self.func.body.emit(self.spv.gpa, .OpUnreachable, {}); | ||
| 2730 | } | ||
| 2731 | |||
| 2732 | if (self.liveness.isUnused(inst) or !return_type.hasRuntimeBitsIgnoreComptime()) { | ||
| 2733 | return null; | ||
| 2734 | } | ||
| 2735 | |||
| 2736 | return result_id; | ||
| 2737 | } | ||
| 1161 | }; | 2738 | }; |
src/codegen/spirv/Assembler.zig+46-41| ... | @@ -135,7 +135,7 @@ const AsmValue = union(enum) { | ... | @@ -135,7 +135,7 @@ const AsmValue = union(enum) { |
| 135 | return switch (self) { | 135 | return switch (self) { |
| 136 | .just_declared, .unresolved_forward_reference => unreachable, | 136 | .just_declared, .unresolved_forward_reference => unreachable, |
| 137 | .value => |result| result, | 137 | .value => |result| result, |
| 138 | .ty => |ref| spv.typeResultId(ref).toRef(), | 138 | .ty => |ref| spv.typeId(ref), |
| 139 | }; | 139 | }; |
| 140 | } | 140 | } |
| 141 | }; | 141 | }; |
| ... | @@ -239,12 +239,17 @@ fn todo(self: *Assembler, comptime fmt: []const u8, args: anytype) Error { | ... | @@ -239,12 +239,17 @@ fn todo(self: *Assembler, comptime fmt: []const u8, args: anytype) Error { |
| 239 | /// If this function returns `error.AssembleFail`, an explanatory | 239 | /// If this function returns `error.AssembleFail`, an explanatory |
| 240 | /// error message has already been emitted into `self.errors`. | 240 | /// error message has already been emitted into `self.errors`. |
| 241 | fn processInstruction(self: *Assembler) !void { | 241 | fn processInstruction(self: *Assembler) !void { |
| 242 | const result = switch (self.inst.opcode.class()) { | 242 | const result = switch (self.inst.opcode) { |
| 243 | .TypeDeclaration => try self.processTypeInstruction(), | 243 | .OpEntryPoint => { |
| 244 | else => if (try self.processGenericInstruction()) |result| | 244 | return self.fail(0, "cannot export entry points via OpEntryPoint, export the kernel using callconv(.Kernel)", .{}); |
| 245 | result | 245 | }, |
| 246 | else | 246 | else => switch (self.inst.opcode.class()) { |
| 247 | return, | 247 | .TypeDeclaration => try self.processTypeInstruction(), |
| 248 | else => if (try self.processGenericInstruction()) |result| | ||
| 249 | result | ||
| 250 | else | ||
| 251 | return, | ||
| 252 | }, | ||
| 248 | }; | 253 | }; |
| 249 | 254 | ||
| 250 | const result_ref = self.inst.result().?; | 255 | const result_ref = self.inst.result().?; |
| ... | @@ -266,27 +271,28 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { | ... | @@ -266,27 +271,28 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { |
| 266 | .OpTypeVoid => SpvType.initTag(.void), | 271 | .OpTypeVoid => SpvType.initTag(.void), |
| 267 | .OpTypeBool => SpvType.initTag(.bool), | 272 | .OpTypeBool => SpvType.initTag(.bool), |
| 268 | .OpTypeInt => blk: { | 273 | .OpTypeInt => blk: { |
| 269 | const payload = try self.spv.arena.create(SpvType.Payload.Int); | ||
| 270 | const signedness: std.builtin.Signedness = switch (operands[2].literal32) { | 274 | const signedness: std.builtin.Signedness = switch (operands[2].literal32) { |
| 271 | 0 => .unsigned, | 275 | 0 => .unsigned, |
| 272 | 1 => .signed, | 276 | 1 => .signed, |
| 273 | else => { | 277 | else => { |
| 274 | // TODO: Improve source location. | 278 | // TODO: Improve source location. |
| 275 | return self.fail(0, "'{}' is not a valid signedness (expected 0 or 1)", .{operands[2].literal32}); | 279 | return self.fail(0, "{} is not a valid signedness (expected 0 or 1)", .{operands[2].literal32}); |
| 276 | }, | 280 | }, |
| 277 | }; | 281 | }; |
| 278 | payload.* = .{ | 282 | const width = std.math.cast(u16, operands[1].literal32) orelse { |
| 279 | .width = operands[1].literal32, | 283 | return self.fail(0, "int type of {} bits is too large", .{operands[1].literal32}); |
| 280 | .signedness = signedness, | ||
| 281 | }; | 284 | }; |
| 282 | break :blk SpvType.initPayload(&payload.base); | 285 | break :blk try SpvType.int(self.spv.arena, signedness, width); |
| 283 | }, | 286 | }, |
| 284 | .OpTypeFloat => blk: { | 287 | .OpTypeFloat => blk: { |
| 285 | const payload = try self.spv.arena.create(SpvType.Payload.Float); | 288 | const bits = operands[1].literal32; |
| 286 | payload.* = .{ | 289 | switch (bits) { |
| 287 | .width = operands[1].literal32, | 290 | 16, 32, 64 => {}, |
| 288 | }; | 291 | else => { |
| 289 | break :blk SpvType.initPayload(&payload.base); | 292 | return self.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits}); |
| 293 | }, | ||
| 294 | } | ||
| 295 | break :blk SpvType.float(@intCast(u16, bits)); | ||
| 290 | }, | 296 | }, |
| 291 | .OpTypeVector => blk: { | 297 | .OpTypeVector => blk: { |
| 292 | const payload = try self.spv.arena.create(SpvType.Payload.Vector); | 298 | const payload = try self.spv.arena.create(SpvType.Payload.Vector); |
| ... | @@ -382,10 +388,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { | ... | @@ -382,10 +388,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { |
| 382 | payload.* = .{ | 388 | payload.* = .{ |
| 383 | .storage_class = @intToEnum(spec.StorageClass, operands[1].value), | 389 | .storage_class = @intToEnum(spec.StorageClass, operands[1].value), |
| 384 | .child_type = try self.resolveTypeRef(operands[2].ref_id), | 390 | .child_type = try self.resolveTypeRef(operands[2].ref_id), |
| 385 | // TODO: Fetch these values from decorations. | 391 | // TODO: Fetch decorations |
| 386 | .array_stride = 0, | ||
| 387 | .alignment = null, | ||
| 388 | .max_byte_offset = null, | ||
| 389 | }; | 392 | }; |
| 390 | break :blk SpvType.initPayload(&payload.base); | 393 | break :blk SpvType.initPayload(&payload.base); |
| 391 | }, | 394 | }, |
| ... | @@ -434,11 +437,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue { | ... | @@ -434,11 +437,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue { |
| 434 | .Annotation => &self.spv.sections.annotations, | 437 | .Annotation => &self.spv.sections.annotations, |
| 435 | .TypeDeclaration => unreachable, // Handled elsewhere. | 438 | .TypeDeclaration => unreachable, // Handled elsewhere. |
| 436 | else => switch (self.inst.opcode) { | 439 | else => switch (self.inst.opcode) { |
| 437 | .OpEntryPoint => &self.spv.sections.entry_points, | 440 | .OpEntryPoint => unreachable, |
| 438 | .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes, | 441 | .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes, |
| 439 | .OpVariable => switch (@intToEnum(spec.StorageClass, operands[2].value)) { | 442 | .OpVariable => switch (@intToEnum(spec.StorageClass, operands[2].value)) { |
| 440 | .Function => &self.func.prologue, | 443 | .Function => &self.func.prologue, |
| 441 | else => &self.spv.sections.types_globals_constants, | 444 | else => { |
| 445 | // This is currently disabled because global variables are required to be | ||
| 446 | // emitted in the proper order, and this should be honored in inline assembly | ||
| 447 | // as well. | ||
| 448 | return self.todo("global variables", .{}); | ||
| 449 | }, | ||
| 442 | }, | 450 | }, |
| 443 | // Default case - to be worked out further. | 451 | // Default case - to be worked out further. |
| 444 | else => &self.func.body, | 452 | else => &self.func.body, |
| ... | @@ -485,7 +493,7 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue { | ... | @@ -485,7 +493,7 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue { |
| 485 | section.instructions.items[first_word] |= @as(u32, @intCast(u16, actual_word_count)) << 16 | @enumToInt(self.inst.opcode); | 493 | section.instructions.items[first_word] |= @as(u32, @intCast(u16, actual_word_count)) << 16 | @enumToInt(self.inst.opcode); |
| 486 | 494 | ||
| 487 | if (maybe_result_id) |result| { | 495 | if (maybe_result_id) |result| { |
| 488 | return AsmValue{ .value = result.toRef() }; | 496 | return AsmValue{ .value = result }; |
| 489 | } | 497 | } |
| 490 | return null; | 498 | return null; |
| 491 | } | 499 | } |
| ... | @@ -753,22 +761,19 @@ fn parseContextDependentNumber(self: *Assembler) !void { | ... | @@ -753,22 +761,19 @@ fn parseContextDependentNumber(self: *Assembler) !void { |
| 753 | 761 | ||
| 754 | const tok = self.currentToken(); | 762 | const tok = self.currentToken(); |
| 755 | const result_type_ref = try self.resolveTypeRef(self.inst.operands.items[0].ref_id); | 763 | const result_type_ref = try self.resolveTypeRef(self.inst.operands.items[0].ref_id); |
| 756 | const result_type = self.spv.type_cache.keys()[result_type_ref]; | 764 | const result_type = self.spv.type_cache.keys()[@enumToInt(result_type_ref)]; |
| 757 | switch (result_type.tag()) { | 765 | if (result_type.isInt()) { |
| 758 | .int => { | 766 | try self.parseContextDependentInt(result_type.intSignedness(), result_type.intFloatBits()); |
| 759 | const int = result_type.castTag(.int).?; | 767 | } else if (result_type.isFloat()) { |
| 760 | try self.parseContextDependentInt(int.signedness, int.width); | 768 | const width = result_type.intFloatBits(); |
| 761 | }, | 769 | switch (width) { |
| 762 | .float => { | 770 | 16 => try self.parseContextDependentFloat(16), |
| 763 | const width = result_type.castTag(.float).?.width; | 771 | 32 => try self.parseContextDependentFloat(32), |
| 764 | switch (width) { | 772 | 64 => try self.parseContextDependentFloat(64), |
| 765 | 16 => try self.parseContextDependentFloat(16), | 773 | else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}), |
| 766 | 32 => try self.parseContextDependentFloat(32), | 774 | } |
| 767 | 64 => try self.parseContextDependentFloat(64), | 775 | } else { |
| 768 | else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}), | 776 | return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())}); |
| 769 | } | ||
| 770 | }, | ||
| 771 | else => return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())}), | ||
| 772 | } | 777 | } |
| 773 | } | 778 | } |
| 774 | 779 |
src/codegen/spirv/Module.zig+474-76| ... | @@ -39,22 +39,68 @@ pub const Fn = struct { | ... | @@ -39,22 +39,68 @@ pub const Fn = struct { |
| 39 | /// This section should also contain the OpFunctionEnd instruction marking | 39 | /// This section should also contain the OpFunctionEnd instruction marking |
| 40 | /// the end of this function definition. | 40 | /// the end of this function definition. |
| 41 | body: Section = .{}, | 41 | body: Section = .{}, |
| 42 | /// The decl dependencies that this function depends on. | ||
| 43 | decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{}, | ||
| 42 | 44 | ||
| 43 | /// Reset this function without deallocating resources, so that | 45 | /// Reset this function without deallocating resources, so that |
| 44 | /// it may be used to emit code for another function. | 46 | /// it may be used to emit code for another function. |
| 45 | pub fn reset(self: *Fn) void { | 47 | pub fn reset(self: *Fn) void { |
| 46 | self.prologue.reset(); | 48 | self.prologue.reset(); |
| 47 | self.body.reset(); | 49 | self.body.reset(); |
| 50 | self.decl_deps.items.len = 0; | ||
| 48 | } | 51 | } |
| 49 | 52 | ||
| 50 | /// Free the resources owned by this function. | 53 | /// Free the resources owned by this function. |
| 51 | pub fn deinit(self: *Fn, a: Allocator) void { | 54 | pub fn deinit(self: *Fn, a: Allocator) void { |
| 52 | self.prologue.deinit(a); | 55 | self.prologue.deinit(a); |
| 53 | self.body.deinit(a); | 56 | self.body.deinit(a); |
| 57 | self.decl_deps.deinit(a); | ||
| 54 | self.* = undefined; | 58 | self.* = undefined; |
| 55 | } | 59 | } |
| 56 | }; | 60 | }; |
| 57 | 61 | ||
| 62 | /// Declarations, both functions and globals, can have dependencies. These are used for 2 things: | ||
| 63 | /// - Globals must be declared before they are used, also between globals. The compiler processes | ||
| 64 | /// globals unordered, so we must use the dependencies here to figure out how to order the globals | ||
| 65 | /// in the final module. The Globals structure is also used for that. | ||
| 66 | /// - Entry points must declare the complete list of OpVariable instructions that they access. | ||
| 67 | /// For these we use the same dependency structure. | ||
| 68 | /// In this mechanism, globals will only depend on other globals, while functions may depend on | ||
| 69 | /// globals or other functions. | ||
| 70 | pub const Decl = struct { | ||
| 71 | /// Index to refer to a Decl by. | ||
| 72 | pub const Index = enum(u32) { _ }; | ||
| 73 | |||
| 74 | /// The result-id to be used for this declaration. This is the final result-id | ||
| 75 | /// of the decl, which may be an OpFunction, OpVariable, or the result of a sequence | ||
| 76 | /// of OpSpecConstantOp operations. | ||
| 77 | result_id: IdRef, | ||
| 78 | /// The offset of the first dependency of this decl in the `decl_deps` array. | ||
| 79 | begin_dep: u32, | ||
| 80 | /// The past-end offset of the dependencies of this decl in the `decl_deps` array. | ||
| 81 | end_dep: u32, | ||
| 82 | }; | ||
| 83 | |||
| 84 | /// Globals must be kept in order: operations involving globals must be ordered | ||
| 85 | /// so that the global declaration precedes any usage. | ||
| 86 | pub const Global = struct { | ||
| 87 | /// This is the result-id of the OpVariable instruction that declares the global. | ||
| 88 | result_id: IdRef, | ||
| 89 | /// The offset into `self.globals.section` of the first instruction of this global | ||
| 90 | /// declaration. | ||
| 91 | begin_inst: u32, | ||
| 92 | /// The past-end offset into `self.flobals.section`. | ||
| 93 | end_inst: u32, | ||
| 94 | }; | ||
| 95 | |||
| 96 | /// This models a kernel entry point. | ||
| 97 | pub const EntryPoint = struct { | ||
| 98 | /// The declaration that should be exported. | ||
| 99 | decl_index: Decl.Index, | ||
| 100 | /// The name of the kernel to be exported. | ||
| 101 | name: []const u8, | ||
| 102 | }; | ||
| 103 | |||
| 58 | /// A general-purpose allocator which may be used to allocate resources for this module | 104 | /// A general-purpose allocator which may be used to allocate resources for this module |
| 59 | gpa: Allocator, | 105 | gpa: Allocator, |
| 60 | 106 | ||
| ... | @@ -69,13 +115,13 @@ sections: struct { | ... | @@ -69,13 +115,13 @@ sections: struct { |
| 69 | extensions: Section = .{}, | 115 | extensions: Section = .{}, |
| 70 | // OpExtInstImport instructions - skip for now. | 116 | // OpExtInstImport instructions - skip for now. |
| 71 | // memory model defined by target, not required here. | 117 | // memory model defined by target, not required here. |
| 72 | /// OpEntryPoint instructions. | 118 | /// OpEntryPoint instructions - Handled by `self.entry_points`. |
| 73 | entry_points: Section = .{}, | ||
| 74 | /// OpExecutionMode and OpExecutionModeId instructions. | 119 | /// OpExecutionMode and OpExecutionModeId instructions. |
| 75 | execution_modes: Section = .{}, | 120 | execution_modes: Section = .{}, |
| 76 | /// OpString, OpSourcExtension, OpSource, OpSourceContinued. | 121 | /// OpString, OpSourcExtension, OpSource, OpSourceContinued. |
| 77 | debug_strings: Section = .{}, | 122 | debug_strings: Section = .{}, |
| 78 | // OpName, OpMemberName - skip for now. | 123 | // OpName, OpMemberName. |
| 124 | debug_names: Section = .{}, | ||
| 79 | // OpModuleProcessed - skip for now. | 125 | // OpModuleProcessed - skip for now. |
| 80 | /// Annotation instructions (OpDecorate etc). | 126 | /// Annotation instructions (OpDecorate etc). |
| 81 | annotations: Section = .{}, | 127 | annotations: Section = .{}, |
| ... | @@ -101,6 +147,26 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{}, | ... | @@ -101,6 +147,26 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{}, |
| 101 | /// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref). | 147 | /// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref). |
| 102 | type_cache: TypeCache = .{}, | 148 | type_cache: TypeCache = .{}, |
| 103 | 149 | ||
| 150 | /// Set of Decls, referred to by Decl.Index. | ||
| 151 | decls: std.ArrayListUnmanaged(Decl) = .{}, | ||
| 152 | |||
| 153 | /// List of dependencies, per decl. This list holds all the dependencies, sliced by the | ||
| 154 | /// begin_dep and end_dep in `self.decls`. | ||
| 155 | decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{}, | ||
| 156 | |||
| 157 | /// The list of entry points that should be exported from this module. | ||
| 158 | entry_points: std.ArrayListUnmanaged(EntryPoint) = .{}, | ||
| 159 | |||
| 160 | /// The fields in this structure help to maintain the required order for global variables. | ||
| 161 | globals: struct { | ||
| 162 | /// Set of globals, referred to by Decl.Index. | ||
| 163 | globals: std.AutoArrayHashMapUnmanaged(Decl.Index, Global) = .{}, | ||
| 164 | /// This pseudo-section contains the initialization code for all the globals. Instructions from | ||
| 165 | /// here are reordered when flushing the module. Its contents should be part of the | ||
| 166 | /// `types_globals_constants` SPIR-V section. | ||
| 167 | section: Section = .{}, | ||
| 168 | } = .{}, | ||
| 169 | |||
| 104 | pub fn init(gpa: Allocator, arena: Allocator) Module { | 170 | pub fn init(gpa: Allocator, arena: Allocator) Module { |
| 105 | return .{ | 171 | return .{ |
| 106 | .gpa = gpa, | 172 | .gpa = gpa, |
| ... | @@ -112,9 +178,9 @@ pub fn init(gpa: Allocator, arena: Allocator) Module { | ... | @@ -112,9 +178,9 @@ pub fn init(gpa: Allocator, arena: Allocator) Module { |
| 112 | pub fn deinit(self: *Module) void { | 178 | pub fn deinit(self: *Module) void { |
| 113 | self.sections.capabilities.deinit(self.gpa); | 179 | self.sections.capabilities.deinit(self.gpa); |
| 114 | self.sections.extensions.deinit(self.gpa); | 180 | self.sections.extensions.deinit(self.gpa); |
| 115 | self.sections.entry_points.deinit(self.gpa); | ||
| 116 | self.sections.execution_modes.deinit(self.gpa); | 181 | self.sections.execution_modes.deinit(self.gpa); |
| 117 | self.sections.debug_strings.deinit(self.gpa); | 182 | self.sections.debug_strings.deinit(self.gpa); |
| 183 | self.sections.debug_names.deinit(self.gpa); | ||
| 118 | self.sections.annotations.deinit(self.gpa); | 184 | self.sections.annotations.deinit(self.gpa); |
| 119 | self.sections.types_globals_constants.deinit(self.gpa); | 185 | self.sections.types_globals_constants.deinit(self.gpa); |
| 120 | self.sections.functions.deinit(self.gpa); | 186 | self.sections.functions.deinit(self.gpa); |
| ... | @@ -122,6 +188,14 @@ pub fn deinit(self: *Module) void { | ... | @@ -122,6 +188,14 @@ pub fn deinit(self: *Module) void { |
| 122 | self.source_file_names.deinit(self.gpa); | 188 | self.source_file_names.deinit(self.gpa); |
| 123 | self.type_cache.deinit(self.gpa); | 189 | self.type_cache.deinit(self.gpa); |
| 124 | 190 | ||
| 191 | self.decls.deinit(self.gpa); | ||
| 192 | self.decl_deps.deinit(self.gpa); | ||
| 193 | |||
| 194 | self.entry_points.deinit(self.gpa); | ||
| 195 | |||
| 196 | self.globals.globals.deinit(self.gpa); | ||
| 197 | self.globals.section.deinit(self.gpa); | ||
| 198 | |||
| 125 | self.* = undefined; | 199 | self.* = undefined; |
| 126 | } | 200 | } |
| 127 | 201 | ||
| ... | @@ -130,32 +204,138 @@ pub fn allocId(self: *Module) spec.IdResult { | ... | @@ -130,32 +204,138 @@ pub fn allocId(self: *Module) spec.IdResult { |
| 130 | return .{ .id = self.next_result_id }; | 204 | return .{ .id = self.next_result_id }; |
| 131 | } | 205 | } |
| 132 | 206 | ||
| 207 | pub fn allocIds(self: *Module, n: u32) spec.IdResult { | ||
| 208 | defer self.next_result_id += n; | ||
| 209 | return .{ .id = self.next_result_id }; | ||
| 210 | } | ||
| 211 | |||
| 133 | pub fn idBound(self: Module) Word { | 212 | pub fn idBound(self: Module) Word { |
| 134 | return self.next_result_id; | 213 | return self.next_result_id; |
| 135 | } | 214 | } |
| 136 | 215 | ||
| 216 | fn orderGlobalsInto( | ||
| 217 | self: *Module, | ||
| 218 | decl_index: Decl.Index, | ||
| 219 | section: *Section, | ||
| 220 | seen: *std.DynamicBitSetUnmanaged, | ||
| 221 | ) !void { | ||
| 222 | const decl = self.declPtr(decl_index); | ||
| 223 | const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep]; | ||
| 224 | const global = self.globalPtr(decl_index).?; | ||
| 225 | const insts = self.globals.section.instructions.items[global.begin_inst..global.end_inst]; | ||
| 226 | |||
| 227 | seen.set(@enumToInt(decl_index)); | ||
| 228 | |||
| 229 | for (deps) |dep| { | ||
| 230 | if (!seen.isSet(@enumToInt(dep))) { | ||
| 231 | try self.orderGlobalsInto(dep, section, seen); | ||
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | try section.instructions.appendSlice(self.gpa, insts); | ||
| 236 | } | ||
| 237 | |||
| 238 | fn orderGlobals(self: *Module) !Section { | ||
| 239 | const globals = self.globals.globals.keys(); | ||
| 240 | |||
| 241 | var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len); | ||
| 242 | defer seen.deinit(self.gpa); | ||
| 243 | |||
| 244 | var ordered_globals = Section{}; | ||
| 245 | errdefer ordered_globals.deinit(self.gpa); | ||
| 246 | |||
| 247 | for (globals) |decl_index| { | ||
| 248 | if (!seen.isSet(@enumToInt(decl_index))) { | ||
| 249 | try self.orderGlobalsInto(decl_index, &ordered_globals, &seen); | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | return ordered_globals; | ||
| 254 | } | ||
| 255 | |||
| 256 | fn addEntryPointDeps( | ||
| 257 | self: *Module, | ||
| 258 | decl_index: Decl.Index, | ||
| 259 | seen: *std.DynamicBitSetUnmanaged, | ||
| 260 | interface: *std.ArrayList(IdRef), | ||
| 261 | ) !void { | ||
| 262 | const decl = self.declPtr(decl_index); | ||
| 263 | const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep]; | ||
| 264 | |||
| 265 | seen.set(@enumToInt(decl_index)); | ||
| 266 | |||
| 267 | if (self.globalPtr(decl_index)) |global| { | ||
| 268 | try interface.append(global.result_id); | ||
| 269 | } | ||
| 270 | |||
| 271 | for (deps) |dep| { | ||
| 272 | if (!seen.isSet(@enumToInt(dep))) { | ||
| 273 | try self.addEntryPointDeps(dep, seen, interface); | ||
| 274 | } | ||
| 275 | } | ||
| 276 | } | ||
| 277 | |||
| 278 | fn entryPoints(self: *Module) !Section { | ||
| 279 | var entry_points = Section{}; | ||
| 280 | errdefer entry_points.deinit(self.gpa); | ||
| 281 | |||
| 282 | var interface = std.ArrayList(IdRef).init(self.gpa); | ||
| 283 | defer interface.deinit(); | ||
| 284 | |||
| 285 | var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len); | ||
| 286 | defer seen.deinit(self.gpa); | ||
| 287 | |||
| 288 | for (self.entry_points.items) |entry_point| { | ||
| 289 | interface.items.len = 0; | ||
| 290 | seen.setRangeValue(.{ .start = 0, .end = self.decls.items.len }, false); | ||
| 291 | |||
| 292 | try self.addEntryPointDeps(entry_point.decl_index, &seen, &interface); | ||
| 293 | |||
| 294 | const entry_point_id = self.declPtr(entry_point.decl_index).result_id; | ||
| 295 | try entry_points.emit(self.gpa, .OpEntryPoint, .{ | ||
| 296 | .execution_model = .Kernel, | ||
| 297 | .entry_point = entry_point_id, | ||
| 298 | .name = entry_point.name, | ||
| 299 | .interface = interface.items, | ||
| 300 | }); | ||
| 301 | } | ||
| 302 | |||
| 303 | return entry_points; | ||
| 304 | } | ||
| 305 | |||
| 137 | /// Emit this module as a spir-v binary. | 306 | /// Emit this module as a spir-v binary. |
| 138 | pub fn flush(self: Module, file: std.fs.File) !void { | 307 | pub fn flush(self: *Module, file: std.fs.File) !void { |
| 139 | // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction" | 308 | // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction" |
| 140 | 309 | ||
| 141 | const header = [_]Word{ | 310 | const header = [_]Word{ |
| 142 | spec.magic_number, | 311 | spec.magic_number, |
| 143 | (1 << 16) | (5 << 8), | 312 | // TODO: From cpu features |
| 313 | // Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports. | ||
| 314 | (1 << 16) | (4 << 8), | ||
| 144 | 0, // TODO: Register Zig compiler magic number. | 315 | 0, // TODO: Register Zig compiler magic number. |
| 145 | self.idBound(), | 316 | self.idBound(), |
| 146 | 0, // Schema (currently reserved for future use) | 317 | 0, // Schema (currently reserved for future use) |
| 147 | }; | 318 | }; |
| 148 | 319 | ||
| 320 | // TODO: Perform topological sort on the globals. | ||
| 321 | var globals = try self.orderGlobals(); | ||
| 322 | defer globals.deinit(self.gpa); | ||
| 323 | |||
| 324 | var entry_points = try self.entryPoints(); | ||
| 325 | defer entry_points.deinit(self.gpa); | ||
| 326 | |||
| 149 | // Note: needs to be kept in order according to section 2.3! | 327 | // Note: needs to be kept in order according to section 2.3! |
| 150 | const buffers = &[_][]const Word{ | 328 | const buffers = &[_][]const Word{ |
| 151 | &header, | 329 | &header, |
| 152 | self.sections.capabilities.toWords(), | 330 | self.sections.capabilities.toWords(), |
| 153 | self.sections.extensions.toWords(), | 331 | self.sections.extensions.toWords(), |
| 154 | self.sections.entry_points.toWords(), | 332 | entry_points.toWords(), |
| 155 | self.sections.execution_modes.toWords(), | 333 | self.sections.execution_modes.toWords(), |
| 156 | self.sections.debug_strings.toWords(), | 334 | self.sections.debug_strings.toWords(), |
| 335 | self.sections.debug_names.toWords(), | ||
| 157 | self.sections.annotations.toWords(), | 336 | self.sections.annotations.toWords(), |
| 158 | self.sections.types_globals_constants.toWords(), | 337 | self.sections.types_globals_constants.toWords(), |
| 338 | globals.toWords(), | ||
| 159 | self.sections.functions.toWords(), | 339 | self.sections.functions.toWords(), |
| 160 | }; | 340 | }; |
| 161 | 341 | ||
| ... | @@ -175,9 +355,10 @@ pub fn flush(self: Module, file: std.fs.File) !void { | ... | @@ -175,9 +355,10 @@ pub fn flush(self: Module, file: std.fs.File) !void { |
| 175 | } | 355 | } |
| 176 | 356 | ||
| 177 | /// Merge the sections making up a function declaration into this module. | 357 | /// Merge the sections making up a function declaration into this module. |
| 178 | pub fn addFunction(self: *Module, func: Fn) !void { | 358 | pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void { |
| 179 | try self.sections.functions.append(self.gpa, func.prologue); | 359 | try self.sections.functions.append(self.gpa, func.prologue); |
| 180 | try self.sections.functions.append(self.gpa, func.body); | 360 | try self.sections.functions.append(self.gpa, func.body); |
| 361 | try self.declareDeclDeps(decl_index, func.decl_deps.items); | ||
| 181 | } | 362 | } |
| 182 | 363 | ||
| 183 | /// Fetch the result-id of an OpString instruction that encodes the path of the source | 364 | /// Fetch the result-id of an OpString instruction that encodes the path of the source |
| ... | @@ -188,7 +369,7 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef { | ... | @@ -188,7 +369,7 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef { |
| 188 | const result = try self.source_file_names.getOrPut(self.gpa, path); | 369 | const result = try self.source_file_names.getOrPut(self.gpa, path); |
| 189 | if (!result.found_existing) { | 370 | if (!result.found_existing) { |
| 190 | const file_result_id = self.allocId(); | 371 | const file_result_id = self.allocId(); |
| 191 | result.value_ptr.* = file_result_id.toRef(); | 372 | result.value_ptr.* = file_result_id; |
| 192 | try self.sections.debug_strings.emit(self.gpa, .OpString, .{ | 373 | try self.sections.debug_strings.emit(self.gpa, .OpString, .{ |
| 193 | .id_result = file_result_id, | 374 | .id_result = file_result_id, |
| 194 | .string = path, | 375 | .string = path, |
| ... | @@ -197,7 +378,7 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef { | ... | @@ -197,7 +378,7 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef { |
| 197 | try self.sections.debug_strings.emit(self.gpa, .OpSource, .{ | 378 | try self.sections.debug_strings.emit(self.gpa, .OpSource, .{ |
| 198 | .source_language = .Unknown, // TODO: Register Zig source language. | 379 | .source_language = .Unknown, // TODO: Register Zig source language. |
| 199 | .version = 0, // TODO: Zig version as u32? | 380 | .version = 0, // TODO: Zig version as u32? |
| 200 | .file = file_result_id.toRef(), | 381 | .file = file_result_id, |
| 201 | .source = null, // TODO: Store actual source also? | 382 | .source = null, // TODO: Store actual source also? |
| 202 | }); | 383 | }); |
| 203 | } | 384 | } |
| ... | @@ -216,22 +397,21 @@ pub fn resolveType(self: *Module, ty: Type) !Type.Ref { | ... | @@ -216,22 +397,21 @@ pub fn resolveType(self: *Module, ty: Type) !Type.Ref { |
| 216 | result.value_ptr.* = try self.emitType(ty); | 397 | result.value_ptr.* = try self.emitType(ty); |
| 217 | } | 398 | } |
| 218 | 399 | ||
| 219 | return result.index; | 400 | return @intToEnum(Type.Ref, result.index); |
| 220 | } | 401 | } |
| 221 | 402 | ||
| 222 | pub fn resolveTypeId(self: *Module, ty: Type) !IdRef { | 403 | pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType { |
| 223 | const type_ref = try self.resolveType(ty); | 404 | const ty_ref = try self.resolveType(ty); |
| 224 | return self.typeResultId(type_ref); | 405 | return self.typeId(ty_ref); |
| 225 | } | 406 | } |
| 226 | 407 | ||
| 227 | /// Get the result-id of a particular type, by reference. Asserts type_ref is valid. | 408 | pub fn typeRefType(self: Module, ty_ref: Type.Ref) Type { |
| 228 | pub fn typeResultId(self: Module, type_ref: Type.Ref) IdResultType { | 409 | return self.type_cache.keys()[@enumToInt(ty_ref)]; |
| 229 | return self.type_cache.values()[type_ref]; | ||
| 230 | } | 410 | } |
| 231 | 411 | ||
| 232 | /// Get the result-id of a particular type as IdRef, by Type.Ref. Asserts type_ref is valid. | 412 | /// Get the result-id of a particular type, by reference. Asserts type_ref is valid. |
| 233 | pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { | 413 | pub fn typeId(self: Module, ty_ref: Type.Ref) IdResultType { |
| 234 | return self.type_cache.values()[type_ref].toRef(); | 414 | return self.type_cache.values()[@enumToInt(ty_ref)]; |
| 235 | } | 415 | } |
| 236 | 416 | ||
| 237 | /// Unconditionally emit a spir-v type into the appropriate section. | 417 | /// Unconditionally emit a spir-v type into the appropriate section. |
| ... | @@ -240,47 +420,94 @@ pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { | ... | @@ -240,47 +420,94 @@ pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { |
| 240 | /// Note: This function does not attempt to perform any validation on the type. | 420 | /// Note: This function does not attempt to perform any validation on the type. |
| 241 | /// The type is emitted in a shallow fashion; any child types should already | 421 | /// The type is emitted in a shallow fashion; any child types should already |
| 242 | /// be emitted at this point. | 422 | /// be emitted at this point. |
| 243 | pub fn emitType(self: *Module, ty: Type) !IdResultType { | 423 | pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { |
| 244 | const result_id = self.allocId(); | 424 | const result_id = self.allocId(); |
| 245 | const ref_id = result_id.toRef(); | 425 | const ref_id = result_id; |
| 246 | const types = &self.sections.types_globals_constants; | 426 | const types = &self.sections.types_globals_constants; |
| 247 | const annotations = &self.sections.annotations; | 427 | const debug_names = &self.sections.debug_names; |
| 248 | const result_id_operand = .{ .id_result = result_id }; | 428 | const result_id_operand = .{ .id_result = result_id }; |
| 249 | 429 | ||
| 250 | switch (ty.tag()) { | 430 | switch (ty.tag()) { |
| 251 | .void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand), | 431 | .void => { |
| 252 | .bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand), | 432 | try types.emit(self.gpa, .OpTypeVoid, result_id_operand); |
| 253 | .int => { | 433 | try debug_names.emit(self.gpa, .OpName, .{ |
| 254 | const signedness: spec.LiteralInteger = switch (ty.payload(.int).signedness) { | 434 | .target = result_id, |
| 435 | .name = "void", | ||
| 436 | }); | ||
| 437 | }, | ||
| 438 | .bool => { | ||
| 439 | try types.emit(self.gpa, .OpTypeBool, result_id_operand); | ||
| 440 | try debug_names.emit(self.gpa, .OpName, .{ | ||
| 441 | .target = result_id, | ||
| 442 | .name = "bool", | ||
| 443 | }); | ||
| 444 | }, | ||
| 445 | .u8, | ||
| 446 | .u16, | ||
| 447 | .u32, | ||
| 448 | .u64, | ||
| 449 | .i8, | ||
| 450 | .i16, | ||
| 451 | .i32, | ||
| 452 | .i64, | ||
| 453 | .int, | ||
| 454 | => { | ||
| 455 | // TODO: Kernels do not support OpTypeInt that is signed. We can probably | ||
| 456 | // can get rid of the signedness all together, in Shaders also. | ||
| 457 | const bits = ty.intFloatBits(); | ||
| 458 | const signedness: spec.LiteralInteger = switch (ty.intSignedness()) { | ||
| 255 | .unsigned => 0, | 459 | .unsigned => 0, |
| 256 | .signed => 1, | 460 | .signed => 1, |
| 257 | }; | 461 | }; |
| 258 | 462 | ||
| 259 | try types.emit(self.gpa, .OpTypeInt, .{ | 463 | try types.emit(self.gpa, .OpTypeInt, .{ |
| 260 | .id_result = result_id, | 464 | .id_result = result_id, |
| 261 | .width = ty.payload(.int).width, | 465 | .width = bits, |
| 262 | .signedness = signedness, | 466 | .signedness = signedness, |
| 263 | }); | 467 | }); |
| 468 | |||
| 469 | const ui: []const u8 = switch (signedness) { | ||
| 470 | 0 => "u", | ||
| 471 | 1 => "i", | ||
| 472 | else => unreachable, | ||
| 473 | }; | ||
| 474 | const name = try std.fmt.allocPrint(self.gpa, "{s}{}", .{ ui, bits }); | ||
| 475 | defer self.gpa.free(name); | ||
| 476 | |||
| 477 | try debug_names.emit(self.gpa, .OpName, .{ | ||
| 478 | .target = result_id, | ||
| 479 | .name = name, | ||
| 480 | }); | ||
| 481 | }, | ||
| 482 | .f16, .f32, .f64 => { | ||
| 483 | const bits = ty.intFloatBits(); | ||
| 484 | try types.emit(self.gpa, .OpTypeFloat, .{ | ||
| 485 | .id_result = result_id, | ||
| 486 | .width = bits, | ||
| 487 | }); | ||
| 488 | |||
| 489 | const name = try std.fmt.allocPrint(self.gpa, "f{}", .{bits}); | ||
| 490 | defer self.gpa.free(name); | ||
| 491 | try debug_names.emit(self.gpa, .OpName, .{ | ||
| 492 | .target = result_id, | ||
| 493 | .name = name, | ||
| 494 | }); | ||
| 264 | }, | 495 | }, |
| 265 | .float => try types.emit(self.gpa, .OpTypeFloat, .{ | ||
| 266 | .id_result = result_id, | ||
| 267 | .width = ty.payload(.float).width, | ||
| 268 | }), | ||
| 269 | .vector => try types.emit(self.gpa, .OpTypeVector, .{ | 496 | .vector => try types.emit(self.gpa, .OpTypeVector, .{ |
| 270 | .id_result = result_id, | 497 | .id_result = result_id, |
| 271 | .component_type = self.typeResultId(ty.childType()).toRef(), | 498 | .component_type = self.typeId(ty.childType()), |
| 272 | .component_count = ty.payload(.vector).component_count, | 499 | .component_count = ty.payload(.vector).component_count, |
| 273 | }), | 500 | }), |
| 274 | .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{ | 501 | .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{ |
| 275 | .id_result = result_id, | 502 | .id_result = result_id, |
| 276 | .column_type = self.typeResultId(ty.childType()).toRef(), | 503 | .column_type = self.typeId(ty.childType()), |
| 277 | .column_count = ty.payload(.matrix).column_count, | 504 | .column_count = ty.payload(.matrix).column_count, |
| 278 | }), | 505 | }), |
| 279 | .image => { | 506 | .image => { |
| 280 | const info = ty.payload(.image); | 507 | const info = ty.payload(.image); |
| 281 | try types.emit(self.gpa, .OpTypeImage, .{ | 508 | try types.emit(self.gpa, .OpTypeImage, .{ |
| 282 | .id_result = result_id, | 509 | .id_result = result_id, |
| 283 | .sampled_type = self.typeResultId(ty.childType()).toRef(), | 510 | .sampled_type = self.typeId(ty.childType()), |
| 284 | .dim = info.dim, | 511 | .dim = info.dim, |
| 285 | .depth = @enumToInt(info.depth), | 512 | .depth = @enumToInt(info.depth), |
| 286 | .arrayed = @boolToInt(info.arrayed), | 513 | .arrayed = @boolToInt(info.arrayed), |
| ... | @@ -293,28 +520,34 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { | ... | @@ -293,28 +520,34 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { |
| 293 | .sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand), | 520 | .sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand), |
| 294 | .sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{ | 521 | .sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{ |
| 295 | .id_result = result_id, | 522 | .id_result = result_id, |
| 296 | .image_type = self.typeResultId(ty.childType()).toRef(), | 523 | .image_type = self.typeId(ty.childType()), |
| 297 | }), | 524 | }), |
| 298 | .array => { | 525 | .array => { |
| 299 | const info = ty.payload(.array); | 526 | const info = ty.payload(.array); |
| 300 | assert(info.length != 0); | 527 | assert(info.length != 0); |
| 528 | |||
| 529 | const size_type = Type.initTag(.u32); | ||
| 530 | const size_type_id = try self.resolveTypeId(size_type); | ||
| 531 | const length_id = self.allocId(); | ||
| 532 | try self.emitConstant(size_type_id, length_id, .{ .uint32 = info.length }); | ||
| 533 | |||
| 301 | try types.emit(self.gpa, .OpTypeArray, .{ | 534 | try types.emit(self.gpa, .OpTypeArray, .{ |
| 302 | .id_result = result_id, | 535 | .id_result = result_id, |
| 303 | .element_type = self.typeResultId(ty.childType()).toRef(), | 536 | .element_type = self.typeId(ty.childType()), |
| 304 | .length = .{ .id = 0 }, // TODO: info.length must be emitted as constant! | 537 | .length = length_id, |
| 305 | }); | 538 | }); |
| 306 | if (info.array_stride != 0) { | 539 | if (info.array_stride != 0) { |
| 307 | try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); | 540 | try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); |
| 308 | } | 541 | } |
| 309 | }, | 542 | }, |
| 310 | .runtime_array => { | 543 | .runtime_array => { |
| 311 | const info = ty.payload(.runtime_array); | 544 | const info = ty.payload(.runtime_array); |
| 312 | try types.emit(self.gpa, .OpTypeRuntimeArray, .{ | 545 | try types.emit(self.gpa, .OpTypeRuntimeArray, .{ |
| 313 | .id_result = result_id, | 546 | .id_result = result_id, |
| 314 | .element_type = self.typeResultId(ty.childType()).toRef(), | 547 | .element_type = self.typeId(ty.childType()), |
| 315 | }); | 548 | }); |
| 316 | if (info.array_stride != 0) { | 549 | if (info.array_stride != 0) { |
| 317 | try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); | 550 | try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); |
| 318 | } | 551 | } |
| 319 | }, | 552 | }, |
| 320 | .@"struct" => { | 553 | .@"struct" => { |
| ... | @@ -322,7 +555,7 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { | ... | @@ -322,7 +555,7 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { |
| 322 | try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len); | 555 | try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len); |
| 323 | types.writeOperand(IdResult, result_id); | 556 | types.writeOperand(IdResult, result_id); |
| 324 | for (info.members) |member| { | 557 | for (info.members) |member| { |
| 325 | types.writeOperand(IdRef, self.typeResultId(member.ty).toRef()); | 558 | types.writeOperand(IdRef, self.typeId(member.ty)); |
| 326 | } | 559 | } |
| 327 | try self.decorateStruct(ref_id, info); | 560 | try self.decorateStruct(ref_id, info); |
| 328 | }, | 561 | }, |
| ... | @@ -335,25 +568,25 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { | ... | @@ -335,25 +568,25 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { |
| 335 | try types.emit(self.gpa, .OpTypePointer, .{ | 568 | try types.emit(self.gpa, .OpTypePointer, .{ |
| 336 | .id_result = result_id, | 569 | .id_result = result_id, |
| 337 | .storage_class = info.storage_class, | 570 | .storage_class = info.storage_class, |
| 338 | .type = self.typeResultId(ty.childType()).toRef(), | 571 | .type = self.typeId(ty.childType()), |
| 339 | }); | 572 | }); |
| 340 | if (info.array_stride != 0) { | 573 | if (info.array_stride != 0) { |
| 341 | try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); | 574 | try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); |
| 342 | } | 575 | } |
| 343 | if (info.alignment) |alignment| { | 576 | if (info.alignment != 0) { |
| 344 | try annotations.decorate(self.gpa, ref_id, .{ .Alignment = .{ .alignment = alignment } }); | 577 | try self.decorate(ref_id, .{ .Alignment = .{ .alignment = info.alignment } }); |
| 345 | } | 578 | } |
| 346 | if (info.max_byte_offset) |max_byte_offset| { | 579 | if (info.max_byte_offset) |max_byte_offset| { |
| 347 | try annotations.decorate(self.gpa, ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } }); | 580 | try self.decorate(ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } }); |
| 348 | } | 581 | } |
| 349 | }, | 582 | }, |
| 350 | .function => { | 583 | .function => { |
| 351 | const info = ty.payload(.function); | 584 | const info = ty.payload(.function); |
| 352 | try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len); | 585 | try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len); |
| 353 | types.writeOperand(IdResult, result_id); | 586 | types.writeOperand(IdResult, result_id); |
| 354 | types.writeOperand(IdRef, self.typeResultId(info.return_type).toRef()); | 587 | types.writeOperand(IdRef, self.typeId(info.return_type)); |
| 355 | for (info.parameters) |parameter_type| { | 588 | for (info.parameters) |parameter_type| { |
| 356 | types.writeOperand(IdRef, self.typeResultId(parameter_type).toRef()); | 589 | types.writeOperand(IdRef, self.typeId(parameter_type)); |
| 357 | } | 590 | } |
| 358 | }, | 591 | }, |
| 359 | .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand), | 592 | .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand), |
| ... | @@ -368,23 +601,30 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { | ... | @@ -368,23 +601,30 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { |
| 368 | .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand), | 601 | .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand), |
| 369 | } | 602 | } |
| 370 | 603 | ||
| 371 | return result_id.toResultType(); | 604 | return result_id; |
| 372 | } | 605 | } |
| 373 | 606 | ||
| 374 | fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void { | 607 | fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void { |
| 375 | const annotations = &self.sections.annotations; | 608 | const debug_names = &self.sections.debug_names; |
| 609 | |||
| 610 | if (info.name.len != 0) { | ||
| 611 | try debug_names.emit(self.gpa, .OpName, .{ | ||
| 612 | .target = target, | ||
| 613 | .name = info.name, | ||
| 614 | }); | ||
| 615 | } | ||
| 376 | 616 | ||
| 377 | // Decorations for the struct type itself. | 617 | // Decorations for the struct type itself. |
| 378 | if (info.decorations.block) | 618 | if (info.decorations.block) |
| 379 | try annotations.decorate(self.gpa, target, .Block); | 619 | try self.decorate(target, .Block); |
| 380 | if (info.decorations.buffer_block) | 620 | if (info.decorations.buffer_block) |
| 381 | try annotations.decorate(self.gpa, target, .BufferBlock); | 621 | try self.decorate(target, .BufferBlock); |
| 382 | if (info.decorations.glsl_shared) | 622 | if (info.decorations.glsl_shared) |
| 383 | try annotations.decorate(self.gpa, target, .GLSLShared); | 623 | try self.decorate(target, .GLSLShared); |
| 384 | if (info.decorations.glsl_packed) | 624 | if (info.decorations.glsl_packed) |
| 385 | try annotations.decorate(self.gpa, target, .GLSLPacked); | 625 | try self.decorate(target, .GLSLPacked); |
| 386 | if (info.decorations.c_packed) | 626 | if (info.decorations.c_packed) |
| 387 | try annotations.decorate(self.gpa, target, .CPacked); | 627 | try self.decorate(target, .CPacked); |
| 388 | 628 | ||
| 389 | // Decorations for the struct members. | 629 | // Decorations for the struct members. |
| 390 | const extra = info.member_decoration_extra; | 630 | const extra = info.member_decoration_extra; |
| ... | @@ -392,71 +632,89 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct | ... | @@ -392,71 +632,89 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct |
| 392 | for (info.members, 0..) |member, i| { | 632 | for (info.members, 0..) |member, i| { |
| 393 | const d = member.decorations; | 633 | const d = member.decorations; |
| 394 | const index = @intCast(Word, i); | 634 | const index = @intCast(Word, i); |
| 635 | |||
| 636 | if (member.name.len != 0) { | ||
| 637 | try debug_names.emit(self.gpa, .OpMemberName, .{ | ||
| 638 | .type = target, | ||
| 639 | .member = index, | ||
| 640 | .name = member.name, | ||
| 641 | }); | ||
| 642 | } | ||
| 643 | |||
| 644 | switch (member.offset) { | ||
| 645 | .none => {}, | ||
| 646 | else => try self.decorateMember( | ||
| 647 | target, | ||
| 648 | index, | ||
| 649 | .{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } }, | ||
| 650 | ), | ||
| 651 | } | ||
| 652 | |||
| 395 | switch (d.matrix_layout) { | 653 | switch (d.matrix_layout) { |
| 396 | .row_major => try annotations.decorateMember(self.gpa, target, index, .RowMajor), | 654 | .row_major => try self.decorateMember(target, index, .RowMajor), |
| 397 | .col_major => try annotations.decorateMember(self.gpa, target, index, .ColMajor), | 655 | .col_major => try self.decorateMember(target, index, .ColMajor), |
| 398 | .none => {}, | 656 | .none => {}, |
| 399 | } | 657 | } |
| 400 | if (d.matrix_layout != .none) { | 658 | if (d.matrix_layout != .none) { |
| 401 | try annotations.decorateMember(self.gpa, target, index, .{ | 659 | try self.decorateMember(target, index, .{ |
| 402 | .MatrixStride = .{ .matrix_stride = extra[extra_i] }, | 660 | .MatrixStride = .{ .matrix_stride = extra[extra_i] }, |
| 403 | }); | 661 | }); |
| 404 | extra_i += 1; | 662 | extra_i += 1; |
| 405 | } | 663 | } |
| 406 | 664 | ||
| 407 | if (d.no_perspective) | 665 | if (d.no_perspective) |
| 408 | try annotations.decorateMember(self.gpa, target, index, .NoPerspective); | 666 | try self.decorateMember(target, index, .NoPerspective); |
| 409 | if (d.flat) | 667 | if (d.flat) |
| 410 | try annotations.decorateMember(self.gpa, target, index, .Flat); | 668 | try self.decorateMember(target, index, .Flat); |
| 411 | if (d.patch) | 669 | if (d.patch) |
| 412 | try annotations.decorateMember(self.gpa, target, index, .Patch); | 670 | try self.decorateMember(target, index, .Patch); |
| 413 | if (d.centroid) | 671 | if (d.centroid) |
| 414 | try annotations.decorateMember(self.gpa, target, index, .Centroid); | 672 | try self.decorateMember(target, index, .Centroid); |
| 415 | if (d.sample) | 673 | if (d.sample) |
| 416 | try annotations.decorateMember(self.gpa, target, index, .Sample); | 674 | try self.decorateMember(target, index, .Sample); |
| 417 | if (d.invariant) | 675 | if (d.invariant) |
| 418 | try annotations.decorateMember(self.gpa, target, index, .Invariant); | 676 | try self.decorateMember(target, index, .Invariant); |
| 419 | if (d.@"volatile") | 677 | if (d.@"volatile") |
| 420 | try annotations.decorateMember(self.gpa, target, index, .Volatile); | 678 | try self.decorateMember(target, index, .Volatile); |
| 421 | if (d.coherent) | 679 | if (d.coherent) |
| 422 | try annotations.decorateMember(self.gpa, target, index, .Coherent); | 680 | try self.decorateMember(target, index, .Coherent); |
| 423 | if (d.non_writable) | 681 | if (d.non_writable) |
| 424 | try annotations.decorateMember(self.gpa, target, index, .NonWritable); | 682 | try self.decorateMember(target, index, .NonWritable); |
| 425 | if (d.non_readable) | 683 | if (d.non_readable) |
| 426 | try annotations.decorateMember(self.gpa, target, index, .NonReadable); | 684 | try self.decorateMember(target, index, .NonReadable); |
| 427 | 685 | ||
| 428 | if (d.builtin) { | 686 | if (d.builtin) { |
| 429 | try annotations.decorateMember(self.gpa, target, index, .{ | 687 | try self.decorateMember(target, index, .{ |
| 430 | .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) }, | 688 | .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) }, |
| 431 | }); | 689 | }); |
| 432 | extra_i += 1; | 690 | extra_i += 1; |
| 433 | } | 691 | } |
| 434 | if (d.stream) { | 692 | if (d.stream) { |
| 435 | try annotations.decorateMember(self.gpa, target, index, .{ | 693 | try self.decorateMember(target, index, .{ |
| 436 | .Stream = .{ .stream_number = extra[extra_i] }, | 694 | .Stream = .{ .stream_number = extra[extra_i] }, |
| 437 | }); | 695 | }); |
| 438 | extra_i += 1; | 696 | extra_i += 1; |
| 439 | } | 697 | } |
| 440 | if (d.location) { | 698 | if (d.location) { |
| 441 | try annotations.decorateMember(self.gpa, target, index, .{ | 699 | try self.decorateMember(target, index, .{ |
| 442 | .Location = .{ .location = extra[extra_i] }, | 700 | .Location = .{ .location = extra[extra_i] }, |
| 443 | }); | 701 | }); |
| 444 | extra_i += 1; | 702 | extra_i += 1; |
| 445 | } | 703 | } |
| 446 | if (d.component) { | 704 | if (d.component) { |
| 447 | try annotations.decorateMember(self.gpa, target, index, .{ | 705 | try self.decorateMember(target, index, .{ |
| 448 | .Component = .{ .component = extra[extra_i] }, | 706 | .Component = .{ .component = extra[extra_i] }, |
| 449 | }); | 707 | }); |
| 450 | extra_i += 1; | 708 | extra_i += 1; |
| 451 | } | 709 | } |
| 452 | if (d.xfb_buffer) { | 710 | if (d.xfb_buffer) { |
| 453 | try annotations.decorateMember(self.gpa, target, index, .{ | 711 | try self.decorateMember(target, index, .{ |
| 454 | .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] }, | 712 | .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] }, |
| 455 | }); | 713 | }); |
| 456 | extra_i += 1; | 714 | extra_i += 1; |
| 457 | } | 715 | } |
| 458 | if (d.xfb_stride) { | 716 | if (d.xfb_stride) { |
| 459 | try annotations.decorateMember(self.gpa, target, index, .{ | 717 | try self.decorateMember(target, index, .{ |
| 460 | .XfbStride = .{ .xfb_stride = extra[extra_i] }, | 718 | .XfbStride = .{ .xfb_stride = extra[extra_i] }, |
| 461 | }); | 719 | }); |
| 462 | extra_i += 1; | 720 | extra_i += 1; |
| ... | @@ -465,10 +723,150 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct | ... | @@ -465,10 +723,150 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct |
| 465 | const len = extra[extra_i]; | 723 | const len = extra[extra_i]; |
| 466 | extra_i += 1; | 724 | extra_i += 1; |
| 467 | const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len]; | 725 | const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len]; |
| 468 | try annotations.decorateMember(self.gpa, target, index, .{ | 726 | try self.decorateMember(target, index, .{ |
| 469 | .UserSemantic = .{ .semantic = semantic }, | 727 | .UserSemantic = .{ .semantic = semantic }, |
| 470 | }); | 728 | }); |
| 471 | extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable; | 729 | extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable; |
| 472 | } | 730 | } |
| 473 | } | 731 | } |
| 474 | } | 732 | } |
| 733 | |||
| 734 | pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Member) !Type.Ref { | ||
| 735 | const payload = try self.arena.create(Type.Payload.Struct); | ||
| 736 | payload.* = .{ | ||
| 737 | .members = try self.arena.dupe(Type.Payload.Struct.Member, members), | ||
| 738 | .decorations = .{}, | ||
| 739 | }; | ||
| 740 | return try self.resolveType(Type.initPayload(&payload.base)); | ||
| 741 | } | ||
| 742 | |||
| 743 | pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref { | ||
| 744 | const payload = try self.arena.create(Type.Payload.Array); | ||
| 745 | payload.* = .{ | ||
| 746 | .element_type = ty, | ||
| 747 | .length = len, | ||
| 748 | }; | ||
| 749 | return try self.resolveType(Type.initPayload(&payload.base)); | ||
| 750 | } | ||
| 751 | |||
| 752 | pub fn ptrType( | ||
| 753 | self: *Module, | ||
| 754 | child: Type.Ref, | ||
| 755 | storage_class: spec.StorageClass, | ||
| 756 | alignment: u32, | ||
| 757 | ) !Type.Ref { | ||
| 758 | const ptr_payload = try self.arena.create(Type.Payload.Pointer); | ||
| 759 | ptr_payload.* = .{ | ||
| 760 | .storage_class = storage_class, | ||
| 761 | .child_type = child, | ||
| 762 | .alignment = alignment, | ||
| 763 | }; | ||
| 764 | return try self.resolveType(Type.initPayload(&ptr_payload.base)); | ||
| 765 | } | ||
| 766 | |||
| 767 | pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref { | ||
| 768 | const payload = try self.arena.create(Type.Payload.Pointer); | ||
| 769 | payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*; | ||
| 770 | payload.storage_class = new_storage_class; | ||
| 771 | return try self.resolveType(Type.initPayload(&payload.base)); | ||
| 772 | } | ||
| 773 | |||
| 774 | pub fn emitConstant( | ||
| 775 | self: *Module, | ||
| 776 | ty_id: IdRef, | ||
| 777 | result_id: IdRef, | ||
| 778 | value: spec.LiteralContextDependentNumber, | ||
| 779 | ) !void { | ||
| 780 | try self.sections.types_globals_constants.emit(self.gpa, .OpConstant, .{ | ||
| 781 | .id_result_type = ty_id, | ||
| 782 | .id_result = result_id, | ||
| 783 | .value = value, | ||
| 784 | }); | ||
| 785 | } | ||
| 786 | |||
| 787 | /// Decorate a result-id. | ||
| 788 | pub fn decorate( | ||
| 789 | self: *Module, | ||
| 790 | target: IdRef, | ||
| 791 | decoration: spec.Decoration.Extended, | ||
| 792 | ) !void { | ||
| 793 | try self.sections.annotations.emit(self.gpa, .OpDecorate, .{ | ||
| 794 | .target = target, | ||
| 795 | .decoration = decoration, | ||
| 796 | }); | ||
| 797 | } | ||
| 798 | |||
| 799 | /// Decorate a result-id which is a member of some struct. | ||
| 800 | pub fn decorateMember( | ||
| 801 | self: *Module, | ||
| 802 | structure_type: IdRef, | ||
| 803 | member: u32, | ||
| 804 | decoration: spec.Decoration.Extended, | ||
| 805 | ) !void { | ||
| 806 | try self.sections.annotations.emit(self.gpa, .OpMemberDecorate, .{ | ||
| 807 | .structure_type = structure_type, | ||
| 808 | .member = member, | ||
| 809 | .decoration = decoration, | ||
| 810 | }); | ||
| 811 | } | ||
| 812 | |||
| 813 | pub const DeclKind = enum { | ||
| 814 | func, | ||
| 815 | global, | ||
| 816 | }; | ||
| 817 | |||
| 818 | pub fn allocDecl(self: *Module, kind: DeclKind) !Decl.Index { | ||
| 819 | try self.decls.append(self.gpa, .{ | ||
| 820 | .result_id = self.allocId(), | ||
| 821 | .begin_dep = undefined, | ||
| 822 | .end_dep = undefined, | ||
| 823 | }); | ||
| 824 | const index = @intToEnum(Decl.Index, @intCast(u32, self.decls.items.len - 1)); | ||
| 825 | switch (kind) { | ||
| 826 | .func => {}, | ||
| 827 | // If the decl represents a global, also allocate a global node. | ||
| 828 | .global => try self.globals.globals.putNoClobber(self.gpa, index, .{ | ||
| 829 | .result_id = undefined, | ||
| 830 | .begin_inst = undefined, | ||
| 831 | .end_inst = undefined, | ||
| 832 | }), | ||
| 833 | } | ||
| 834 | |||
| 835 | return index; | ||
| 836 | } | ||
| 837 | |||
| 838 | pub fn declPtr(self: *Module, index: Decl.Index) *Decl { | ||
| 839 | return &self.decls.items[@enumToInt(index)]; | ||
| 840 | } | ||
| 841 | |||
| 842 | pub fn globalPtr(self: *Module, index: Decl.Index) ?*Global { | ||
| 843 | return self.globals.globals.getPtr(index); | ||
| 844 | } | ||
| 845 | |||
| 846 | /// Declare ALL dependencies for a decl. | ||
| 847 | pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl.Index) !void { | ||
| 848 | const begin_dep = @intCast(u32, self.decl_deps.items.len); | ||
| 849 | try self.decl_deps.appendSlice(self.gpa, deps); | ||
| 850 | const end_dep = @intCast(u32, self.decl_deps.items.len); | ||
| 851 | |||
| 852 | const decl = self.declPtr(decl_index); | ||
| 853 | decl.begin_dep = begin_dep; | ||
| 854 | decl.end_dep = end_dep; | ||
| 855 | } | ||
| 856 | |||
| 857 | pub fn beginGlobal(self: *Module) u32 { | ||
| 858 | return @intCast(u32, self.globals.section.instructions.items.len); | ||
| 859 | } | ||
| 860 | |||
| 861 | pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32) void { | ||
| 862 | const global = self.globalPtr(global_index).?; | ||
| 863 | global.begin_inst = begin_inst; | ||
| 864 | global.end_inst = @intCast(u32, self.globals.section.instructions.items.len); | ||
| 865 | } | ||
| 866 | |||
| 867 | pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8) !void { | ||
| 868 | try self.entry_points.append(self.gpa, .{ | ||
| 869 | .decl_index = decl_index, | ||
| 870 | .name = try self.arena.dupe(u8, name), | ||
| 871 | }); | ||
| 872 | } |
src/codegen/spirv/Section.zig+18-27| ... | @@ -65,32 +65,23 @@ pub fn emit( | ... | @@ -65,32 +65,23 @@ pub fn emit( |
| 65 | section.writeOperands(opcode.Operands(), operands); | 65 | section.writeOperands(opcode.Operands(), operands); |
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | /// Decorate a result-id. | 68 | pub fn emitSpecConstantOp( |
| 69 | pub fn decorate( | ||
| 70 | section: *Section, | 69 | section: *Section, |
| 71 | allocator: Allocator, | 70 | allocator: Allocator, |
| 72 | target: spec.IdRef, | 71 | comptime opcode: spec.Opcode, |
| 73 | decoration: spec.Decoration.Extended, | 72 | operands: opcode.Operands(), |
| 74 | ) !void { | ||
| 75 | try section.emit(allocator, .OpDecorate, .{ | ||
| 76 | .target = target, | ||
| 77 | .decoration = decoration, | ||
| 78 | }); | ||
| 79 | } | ||
| 80 | |||
| 81 | /// Decorate a result-id which is a member of some struct. | ||
| 82 | pub fn decorateMember( | ||
| 83 | section: *Section, | ||
| 84 | allocator: Allocator, | ||
| 85 | structure_type: spec.IdRef, | ||
| 86 | member: u32, | ||
| 87 | decoration: spec.Decoration.Extended, | ||
| 88 | ) !void { | 73 | ) !void { |
| 89 | try section.emit(allocator, .OpMemberDecorate, .{ | 74 | const word_count = operandsSize(opcode.Operands(), operands); |
| 90 | .structure_type = structure_type, | 75 | try section.emitRaw(allocator, .OpSpecConstantOp, 1 + word_count); |
| 91 | .member = member, | 76 | section.writeOperand(spec.IdRef, operands.id_result_type); |
| 92 | .decoration = decoration, | 77 | section.writeOperand(spec.IdRef, operands.id_result); |
| 93 | }); | 78 | section.writeOperand(Opcode, opcode); |
| 79 | |||
| 80 | const fields = @typeInfo(opcode.Operands()).Struct.fields; | ||
| 81 | // First 2 fields are always id_result_type and id_result. | ||
| 82 | inline for (fields[2..]) |field| { | ||
| 83 | section.writeOperand(field.type, @field(operands, field.name)); | ||
| 84 | } | ||
| 94 | } | 85 | } |
| 95 | 86 | ||
| 96 | pub fn writeWord(section: *Section, word: Word) void { | 87 | pub fn writeWord(section: *Section, word: Word) void { |
| ... | @@ -122,7 +113,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) | ... | @@ -122,7 +113,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) |
| 122 | 113 | ||
| 123 | pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void { | 114 | pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void { |
| 124 | switch (Operand) { | 115 | switch (Operand) { |
| 125 | spec.IdResultType, spec.IdResult, spec.IdRef => section.writeWord(operand.id), | 116 | spec.IdResult => section.writeWord(operand.id), |
| 126 | 117 | ||
| 127 | spec.LiteralInteger => section.writeWord(operand), | 118 | spec.LiteralInteger => section.writeWord(operand), |
| 128 | 119 | ||
| ... | @@ -258,9 +249,7 @@ fn operandsSize(comptime Operands: type, operands: Operands) usize { | ... | @@ -258,9 +249,7 @@ fn operandsSize(comptime Operands: type, operands: Operands) usize { |
| 258 | 249 | ||
| 259 | fn operandSize(comptime Operand: type, operand: Operand) usize { | 250 | fn operandSize(comptime Operand: type, operand: Operand) usize { |
| 260 | return switch (Operand) { | 251 | return switch (Operand) { |
| 261 | spec.IdResultType, | ||
| 262 | spec.IdResult, | 252 | spec.IdResult, |
| 263 | spec.IdRef, | ||
| 264 | spec.LiteralInteger, | 253 | spec.LiteralInteger, |
| 265 | spec.LiteralExtInstInteger, | 254 | spec.LiteralExtInstInteger, |
| 266 | => 1, | 255 | => 1, |
| ... | @@ -382,7 +371,9 @@ test "SPIR-V Section emit() - string" { | ... | @@ -382,7 +371,9 @@ test "SPIR-V Section emit() - string" { |
| 382 | }, section.instructions.items); | 371 | }, section.instructions.items); |
| 383 | } | 372 | } |
| 384 | 373 | ||
| 385 | test "SPIR-V Section emit()- extended mask" { | 374 | test "SPIR-V Section emit() - extended mask" { |
| 375 | if (@import("builtin").zig_backend == .stage1) return error.SkipZigTest; | ||
| 376 | |||
| 386 | var section = Section{}; | 377 | var section = Section{}; |
| 387 | defer section.deinit(std.testing.allocator); | 378 | defer section.deinit(std.testing.allocator); |
| 388 | 379 |
src/codegen/spirv/spec.zig+2-13| ... | @@ -3,22 +3,11 @@ | ... | @@ -3,22 +3,11 @@ |
| 3 | const Version = @import("std").builtin.Version; | 3 | const Version = @import("std").builtin.Version; |
| 4 | 4 | ||
| 5 | pub const Word = u32; | 5 | pub const Word = u32; |
| 6 | pub const IdResultType = struct { | ||
| 7 | id: Word, | ||
| 8 | pub fn toRef(self: IdResultType) IdRef { | ||
| 9 | return .{ .id = self.id }; | ||
| 10 | } | ||
| 11 | }; | ||
| 12 | pub const IdResult = struct { | 6 | pub const IdResult = struct { |
| 13 | id: Word, | 7 | id: Word, |
| 14 | pub fn toRef(self: IdResult) IdRef { | ||
| 15 | return .{ .id = self.id }; | ||
| 16 | } | ||
| 17 | pub fn toResultType(self: IdResult) IdResultType { | ||
| 18 | return .{ .id = self.id }; | ||
| 19 | } | ||
| 20 | }; | 8 | }; |
| 21 | pub const IdRef = struct { id: Word }; | 9 | pub const IdResultType = IdResult; |
| 10 | pub const IdRef = IdResult; | ||
| 22 | 11 | ||
| 23 | pub const IdMemorySemantics = IdRef; | 12 | pub const IdMemorySemantics = IdRef; |
| 24 | pub const IdScope = IdRef; | 13 | pub const IdScope = IdRef; |
src/codegen/spirv/type.zig+179-46| ... | @@ -3,6 +3,8 @@ | ... | @@ -3,6 +3,8 @@ |
| 3 | 3 | ||
| 4 | const std = @import("std"); | 4 | const std = @import("std"); |
| 5 | const assert = std.debug.assert; | 5 | const assert = std.debug.assert; |
| 6 | const Signedness = std.builtin.Signedness; | ||
| 7 | const Allocator = std.mem.Allocator; | ||
| 6 | 8 | ||
| 7 | const spec = @import("spec.zig"); | 9 | const spec = @import("spec.zig"); |
| 8 | 10 | ||
| ... | @@ -11,7 +13,7 @@ pub const Type = extern union { | ... | @@ -11,7 +13,7 @@ pub const Type = extern union { |
| 11 | ptr_otherwise: *Payload, | 13 | ptr_otherwise: *Payload, |
| 12 | 14 | ||
| 13 | /// A reference to another SPIR-V type. | 15 | /// A reference to another SPIR-V type. |
| 14 | pub const Ref = usize; | 16 | pub const Ref = enum(u32) { _ }; |
| 15 | 17 | ||
| 16 | pub fn initTag(comptime small_tag: Tag) Type { | 18 | pub fn initTag(comptime small_tag: Tag) Type { |
| 17 | comptime assert(@enumToInt(small_tag) < Tag.no_payload_count); | 19 | comptime assert(@enumToInt(small_tag) < Tag.no_payload_count); |
| ... | @@ -23,6 +25,41 @@ pub const Type = extern union { | ... | @@ -23,6 +25,41 @@ pub const Type = extern union { |
| 23 | return .{ .ptr_otherwise = pl }; | 25 | return .{ .ptr_otherwise = pl }; |
| 24 | } | 26 | } |
| 25 | 27 | ||
| 28 | pub fn int(arena: Allocator, signedness: Signedness, bits: u16) !Type { | ||
| 29 | const bits_and_signedness = switch (signedness) { | ||
| 30 | .signed => -@as(i32, bits), | ||
| 31 | .unsigned => @as(i32, bits), | ||
| 32 | }; | ||
| 33 | |||
| 34 | return switch (bits_and_signedness) { | ||
| 35 | 8 => initTag(.u8), | ||
| 36 | 16 => initTag(.u16), | ||
| 37 | 32 => initTag(.u32), | ||
| 38 | 64 => initTag(.u64), | ||
| 39 | -8 => initTag(.i8), | ||
| 40 | -16 => initTag(.i16), | ||
| 41 | -32 => initTag(.i32), | ||
| 42 | -64 => initTag(.i64), | ||
| 43 | else => { | ||
| 44 | const int_payload = try arena.create(Payload.Int); | ||
| 45 | int_payload.* = .{ | ||
| 46 | .width = bits, | ||
| 47 | .signedness = signedness, | ||
| 48 | }; | ||
| 49 | return initPayload(&int_payload.base); | ||
| 50 | }, | ||
| 51 | }; | ||
| 52 | } | ||
| 53 | |||
| 54 | pub fn float(bits: u16) Type { | ||
| 55 | return switch (bits) { | ||
| 56 | 16 => initTag(.f16), | ||
| 57 | 32 => initTag(.f32), | ||
| 58 | 64 => initTag(.f64), | ||
| 59 | else => unreachable, // Enable more types if required. | ||
| 60 | }; | ||
| 61 | } | ||
| 62 | |||
| 26 | pub fn tag(self: Type) Tag { | 63 | pub fn tag(self: Type) Tag { |
| 27 | if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) { | 64 | if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) { |
| 28 | return self.tag_if_small_enough; | 65 | return self.tag_if_small_enough; |
| ... | @@ -80,9 +117,19 @@ pub const Type = extern union { | ... | @@ -80,9 +117,19 @@ pub const Type = extern union { |
| 80 | .queue, | 117 | .queue, |
| 81 | .pipe_storage, | 118 | .pipe_storage, |
| 82 | .named_barrier, | 119 | .named_barrier, |
| 120 | .u8, | ||
| 121 | .u16, | ||
| 122 | .u32, | ||
| 123 | .u64, | ||
| 124 | .i8, | ||
| 125 | .i16, | ||
| 126 | .i32, | ||
| 127 | .i64, | ||
| 128 | .f16, | ||
| 129 | .f32, | ||
| 130 | .f64, | ||
| 83 | => return true, | 131 | => return true, |
| 84 | .int, | 132 | .int, |
| 85 | .float, | ||
| 86 | .vector, | 133 | .vector, |
| 87 | .matrix, | 134 | .matrix, |
| 88 | .sampled_image, | 135 | .sampled_image, |
| ... | @@ -132,6 +179,17 @@ pub const Type = extern union { | ... | @@ -132,6 +179,17 @@ pub const Type = extern union { |
| 132 | .queue, | 179 | .queue, |
| 133 | .pipe_storage, | 180 | .pipe_storage, |
| 134 | .named_barrier, | 181 | .named_barrier, |
| 182 | .u8, | ||
| 183 | .u16, | ||
| 184 | .u32, | ||
| 185 | .u64, | ||
| 186 | .i8, | ||
| 187 | .i16, | ||
| 188 | .i32, | ||
| 189 | .i64, | ||
| 190 | .f16, | ||
| 191 | .f32, | ||
| 192 | .f64, | ||
| 135 | => {}, | 193 | => {}, |
| 136 | else => self.hashPayload(@field(Tag, field.name), &hasher), | 194 | else => self.hashPayload(@field(Tag, field.name), &hasher), |
| 137 | } | 195 | } |
| ... | @@ -185,6 +243,53 @@ pub const Type = extern union { | ... | @@ -185,6 +243,53 @@ pub const Type = extern union { |
| 185 | }; | 243 | }; |
| 186 | } | 244 | } |
| 187 | 245 | ||
| 246 | pub fn isInt(self: Type) bool { | ||
| 247 | return switch (self.tag()) { | ||
| 248 | .u8, | ||
| 249 | .u16, | ||
| 250 | .u32, | ||
| 251 | .u64, | ||
| 252 | .i8, | ||
| 253 | .i16, | ||
| 254 | .i32, | ||
| 255 | .i64, | ||
| 256 | .int, | ||
| 257 | => true, | ||
| 258 | else => false, | ||
| 259 | }; | ||
| 260 | } | ||
| 261 | |||
| 262 | pub fn isFloat(self: Type) bool { | ||
| 263 | return switch (self.tag()) { | ||
| 264 | .f16, .f32, .f64 => true, | ||
| 265 | else => false, | ||
| 266 | }; | ||
| 267 | } | ||
| 268 | |||
| 269 | /// Returns the number of bits that make up an int or float type. | ||
| 270 | /// Asserts type is either int or float. | ||
| 271 | pub fn intFloatBits(self: Type) u16 { | ||
| 272 | return switch (self.tag()) { | ||
| 273 | .u8, .i8 => 8, | ||
| 274 | .u16, .i16, .f16 => 16, | ||
| 275 | .u32, .i32, .f32 => 32, | ||
| 276 | .u64, .i64, .f64 => 64, | ||
| 277 | .int => self.payload(.int).width, | ||
| 278 | else => unreachable, | ||
| 279 | }; | ||
| 280 | } | ||
| 281 | |||
| 282 | /// Returns the signedness of an integer type. | ||
| 283 | /// Asserts that the type is an int. | ||
| 284 | pub fn intSignedness(self: Type) Signedness { | ||
| 285 | return switch (self.tag()) { | ||
| 286 | .u8, .u16, .u32, .u64 => .unsigned, | ||
| 287 | .i8, .i16, .i32, .i64 => .signed, | ||
| 288 | .int => self.payload(.int).signedness, | ||
| 289 | else => unreachable, | ||
| 290 | }; | ||
| 291 | } | ||
| 292 | |||
| 188 | pub const Tag = enum(usize) { | 293 | pub const Tag = enum(usize) { |
| 189 | void, | 294 | void, |
| 190 | bool, | 295 | bool, |
| ... | @@ -195,10 +300,20 @@ pub const Type = extern union { | ... | @@ -195,10 +300,20 @@ pub const Type = extern union { |
| 195 | queue, | 300 | queue, |
| 196 | pipe_storage, | 301 | pipe_storage, |
| 197 | named_barrier, | 302 | named_barrier, |
| 303 | u8, | ||
| 304 | u16, | ||
| 305 | u32, | ||
| 306 | u64, | ||
| 307 | i8, | ||
| 308 | i16, | ||
| 309 | i32, | ||
| 310 | i64, | ||
| 311 | f16, | ||
| 312 | f32, | ||
| 313 | f64, | ||
| 198 | 314 | ||
| 199 | // After this, the tag requires a payload. | 315 | // After this, the tag requires a payload. |
| 200 | int, | 316 | int, |
| 201 | float, | ||
| 202 | vector, | 317 | vector, |
| 203 | matrix, | 318 | matrix, |
| 204 | image, | 319 | image, |
| ... | @@ -211,14 +326,33 @@ pub const Type = extern union { | ... | @@ -211,14 +326,33 @@ pub const Type = extern union { |
| 211 | function, | 326 | function, |
| 212 | pipe, | 327 | pipe, |
| 213 | 328 | ||
| 214 | pub const last_no_payload_tag = Tag.named_barrier; | 329 | pub const last_no_payload_tag = Tag.f64; |
| 215 | pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; | 330 | pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; |
| 216 | 331 | ||
| 217 | pub fn Type(comptime t: Tag) type { | 332 | pub fn Type(comptime t: Tag) type { |
| 218 | return switch (t) { | 333 | return switch (t) { |
| 219 | .void, .bool, .sampler, .event, .device_event, .reserve_id, .queue, .pipe_storage, .named_barrier => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), | 334 | .void, |
| 335 | .bool, | ||
| 336 | .sampler, | ||
| 337 | .event, | ||
| 338 | .device_event, | ||
| 339 | .reserve_id, | ||
| 340 | .queue, | ||
| 341 | .pipe_storage, | ||
| 342 | .named_barrier, | ||
| 343 | .u8, | ||
| 344 | .u16, | ||
| 345 | .u32, | ||
| 346 | .u64, | ||
| 347 | .i8, | ||
| 348 | .i16, | ||
| 349 | .i32, | ||
| 350 | .i64, | ||
| 351 | .f16, | ||
| 352 | .f32, | ||
| 353 | .f64, | ||
| 354 | => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), | ||
| 220 | .int => Payload.Int, | 355 | .int => Payload.Int, |
| 221 | .float => Payload.Float, | ||
| 222 | .vector => Payload.Vector, | 356 | .vector => Payload.Vector, |
| 223 | .matrix => Payload.Matrix, | 357 | .matrix => Payload.Matrix, |
| 224 | .image => Payload.Image, | 358 | .image => Payload.Image, |
| ... | @@ -239,13 +373,8 @@ pub const Type = extern union { | ... | @@ -239,13 +373,8 @@ pub const Type = extern union { |
| 239 | 373 | ||
| 240 | pub const Int = struct { | 374 | pub const Int = struct { |
| 241 | base: Payload = .{ .tag = .int }, | 375 | base: Payload = .{ .tag = .int }, |
| 242 | width: u32, | 376 | width: u16, |
| 243 | signedness: std.builtin.Signedness, | 377 | signedness: Signedness, |
| 244 | }; | ||
| 245 | |||
| 246 | pub const Float = struct { | ||
| 247 | base: Payload = .{ .tag = .float }, | ||
| 248 | width: u32, | ||
| 249 | }; | 378 | }; |
| 250 | 379 | ||
| 251 | pub const Vector = struct { | 380 | pub const Vector = struct { |
| ... | @@ -292,7 +421,7 @@ pub const Type = extern union { | ... | @@ -292,7 +421,7 @@ pub const Type = extern union { |
| 292 | length: u32, | 421 | length: u32, |
| 293 | /// Type has the 'ArrayStride' decoration. | 422 | /// Type has the 'ArrayStride' decoration. |
| 294 | /// If zero, no stride is present. | 423 | /// If zero, no stride is present. |
| 295 | array_stride: u32, | 424 | array_stride: u32 = 0, |
| 296 | }; | 425 | }; |
| 297 | 426 | ||
| 298 | pub const RuntimeArray = struct { | 427 | pub const RuntimeArray = struct { |
| ... | @@ -300,35 +429,39 @@ pub const Type = extern union { | ... | @@ -300,35 +429,39 @@ pub const Type = extern union { |
| 300 | element_type: Ref, | 429 | element_type: Ref, |
| 301 | /// Type has the 'ArrayStride' decoration. | 430 | /// Type has the 'ArrayStride' decoration. |
| 302 | /// If zero, no stride is present. | 431 | /// If zero, no stride is present. |
| 303 | array_stride: u32, | 432 | array_stride: u32 = 0, |
| 304 | }; | 433 | }; |
| 305 | 434 | ||
| 306 | pub const Struct = struct { | 435 | pub const Struct = struct { |
| 307 | base: Payload = .{ .tag = .@"struct" }, | 436 | base: Payload = .{ .tag = .@"struct" }, |
| 308 | members: []Member, | 437 | members: []Member, |
| 309 | decorations: StructDecorations, | 438 | name: []const u8 = "", |
| 439 | decorations: StructDecorations = .{}, | ||
| 310 | 440 | ||
| 311 | /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by | 441 | /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by |
| 312 | /// order of the `members` slice and `MemberDecorations` struct. | 442 | /// order of the `members` slice and `MemberDecorations` struct. |
| 313 | member_decoration_extra: []u32, | 443 | member_decoration_extra: []u32 = &.{}, |
| 314 | 444 | ||
| 315 | pub const Member = struct { | 445 | pub const Member = struct { |
| 316 | ty: Ref, | 446 | ty: Ref, |
| 317 | offset: u32, | 447 | name: []const u8 = "", |
| 318 | decorations: MemberDecorations, | 448 | offset: MemberOffset = .none, |
| 449 | decorations: MemberDecorations = .{}, | ||
| 319 | }; | 450 | }; |
| 320 | 451 | ||
| 452 | pub const MemberOffset = enum(u32) { none = 0xFFFF_FFFF, _ }; | ||
| 453 | |||
| 321 | pub const StructDecorations = packed struct { | 454 | pub const StructDecorations = packed struct { |
| 322 | /// Type has the 'Block' decoration. | 455 | /// Type has the 'Block' decoration. |
| 323 | block: bool, | 456 | block: bool = false, |
| 324 | /// Type has the 'BufferBlock' decoration. | 457 | /// Type has the 'BufferBlock' decoration. |
| 325 | buffer_block: bool, | 458 | buffer_block: bool = false, |
| 326 | /// Type has the 'GLSLShared' decoration. | 459 | /// Type has the 'GLSLShared' decoration. |
| 327 | glsl_shared: bool, | 460 | glsl_shared: bool = false, |
| 328 | /// Type has the 'GLSLPacked' decoration. | 461 | /// Type has the 'GLSLPacked' decoration. |
| 329 | glsl_packed: bool, | 462 | glsl_packed: bool = false, |
| 330 | /// Type has the 'CPacked' decoration. | 463 | /// Type has the 'CPacked' decoration. |
| 331 | c_packed: bool, | 464 | c_packed: bool = false, |
| 332 | }; | 465 | }; |
| 333 | 466 | ||
| 334 | pub const MemberDecorations = packed struct { | 467 | pub const MemberDecorations = packed struct { |
| ... | @@ -344,31 +477,31 @@ pub const Type = extern union { | ... | @@ -344,31 +477,31 @@ pub const Type = extern union { |
| 344 | col_major, | 477 | col_major, |
| 345 | /// Member is not a matrix or array of matrices. | 478 | /// Member is not a matrix or array of matrices. |
| 346 | none, | 479 | none, |
| 347 | }, | 480 | } = .none, |
| 348 | 481 | ||
| 349 | // Regular decorations, these do not imply extra fields. | 482 | // Regular decorations, these do not imply extra fields. |
| 350 | 483 | ||
| 351 | /// Member has the 'NoPerspective' decoration. | 484 | /// Member has the 'NoPerspective' decoration. |
| 352 | no_perspective: bool, | 485 | no_perspective: bool = false, |
| 353 | /// Member has the 'Flat' decoration. | 486 | /// Member has the 'Flat' decoration. |
| 354 | flat: bool, | 487 | flat: bool = false, |
| 355 | /// Member has the 'Patch' decoration. | 488 | /// Member has the 'Patch' decoration. |
| 356 | patch: bool, | 489 | patch: bool = false, |
| 357 | /// Member has the 'Centroid' decoration. | 490 | /// Member has the 'Centroid' decoration. |
| 358 | centroid: bool, | 491 | centroid: bool = false, |
| 359 | /// Member has the 'Sample' decoration. | 492 | /// Member has the 'Sample' decoration. |
| 360 | sample: bool, | 493 | sample: bool = false, |
| 361 | /// Member has the 'Invariant' decoration. | 494 | /// Member has the 'Invariant' decoration. |
| 362 | /// Note: requires parent struct to have 'Block'. | 495 | /// Note: requires parent struct to have 'Block'. |
| 363 | invariant: bool, | 496 | invariant: bool = false, |
| 364 | /// Member has the 'Volatile' decoration. | 497 | /// Member has the 'Volatile' decoration. |
| 365 | @"volatile": bool, | 498 | @"volatile": bool = false, |
| 366 | /// Member has the 'Coherent' decoration. | 499 | /// Member has the 'Coherent' decoration. |
| 367 | coherent: bool, | 500 | coherent: bool = false, |
| 368 | /// Member has the 'NonWritable' decoration. | 501 | /// Member has the 'NonWritable' decoration. |
| 369 | non_writable: bool, | 502 | non_writable: bool = false, |
| 370 | /// Member has the 'NonReadable' decoration. | 503 | /// Member has the 'NonReadable' decoration. |
| 371 | non_readable: bool, | 504 | non_readable: bool = false, |
| 372 | 505 | ||
| 373 | // The following decorations all imply extra field(s). | 506 | // The following decorations all imply extra field(s). |
| 374 | 507 | ||
| ... | @@ -377,27 +510,27 @@ pub const Type = extern union { | ... | @@ -377,27 +510,27 @@ pub const Type = extern union { |
| 377 | /// Note: If any member of a struct has the BuiltIn decoration, all members must have one. | 510 | /// Note: If any member of a struct has the BuiltIn decoration, all members must have one. |
| 378 | /// Note: Each builtin may only be reachable once for a particular entry point. | 511 | /// Note: Each builtin may only be reachable once for a particular entry point. |
| 379 | /// Note: The member type may be constrained by a particular built-in, defined in the client API specification. | 512 | /// Note: The member type may be constrained by a particular built-in, defined in the client API specification. |
| 380 | builtin: bool, | 513 | builtin: bool = false, |
| 381 | /// Member has the 'Stream' decoration. | 514 | /// Member has the 'Stream' decoration. |
| 382 | /// This member has an extra field of type `u32`. | 515 | /// This member has an extra field of type `u32`. |
| 383 | stream: bool, | 516 | stream: bool = false, |
| 384 | /// Member has the 'Location' decoration. | 517 | /// Member has the 'Location' decoration. |
| 385 | /// This member has an extra field of type `u32`. | 518 | /// This member has an extra field of type `u32`. |
| 386 | location: bool, | 519 | location: bool = false, |
| 387 | /// Member has the 'Component' decoration. | 520 | /// Member has the 'Component' decoration. |
| 388 | /// This member has an extra field of type `u32`. | 521 | /// This member has an extra field of type `u32`. |
| 389 | component: bool, | 522 | component: bool = false, |
| 390 | /// Member has the 'XfbBuffer' decoration. | 523 | /// Member has the 'XfbBuffer' decoration. |
| 391 | /// This member has an extra field of type `u32`. | 524 | /// This member has an extra field of type `u32`. |
| 392 | xfb_buffer: bool, | 525 | xfb_buffer: bool = false, |
| 393 | /// Member has the 'XfbStride' decoration. | 526 | /// Member has the 'XfbStride' decoration. |
| 394 | /// This member has an extra field of type `u32`. | 527 | /// This member has an extra field of type `u32`. |
| 395 | xfb_stride: bool, | 528 | xfb_stride: bool = false, |
| 396 | /// Member has the 'UserSemantic' decoration. | 529 | /// Member has the 'UserSemantic' decoration. |
| 397 | /// This member has an extra field of type `[]u8`, which is encoded | 530 | /// This member has an extra field of type `[]u8`, which is encoded |
| 398 | /// by an `u32` containing the number of chars exactly, and then the string padded to | 531 | /// by an `u32` containing the number of chars exactly, and then the string padded to |
| 399 | /// a multiple of 4 bytes with zeroes. | 532 | /// a multiple of 4 bytes with zeroes. |
| 400 | user_semantic: bool, | 533 | user_semantic: bool = false, |
| 401 | }; | 534 | }; |
| 402 | }; | 535 | }; |
| 403 | 536 | ||
| ... | @@ -413,11 +546,11 @@ pub const Type = extern union { | ... | @@ -413,11 +546,11 @@ pub const Type = extern union { |
| 413 | /// Type has the 'ArrayStride' decoration. | 546 | /// Type has the 'ArrayStride' decoration. |
| 414 | /// This is valid for pointers to elements of an array. | 547 | /// This is valid for pointers to elements of an array. |
| 415 | /// If zero, no stride is present. | 548 | /// If zero, no stride is present. |
| 416 | array_stride: u32, | 549 | array_stride: u32 = 0, |
| 417 | /// Type has the 'Alignment' decoration. | 550 | /// If nonzero, type has the 'Alignment' decoration. |
| 418 | alignment: ?u32, | 551 | alignment: u32 = 0, |
| 419 | /// Type has the 'MaxByteOffset' decoration. | 552 | /// Type has the 'MaxByteOffset' decoration. |
| 420 | max_byte_offset: ?u32, | 553 | max_byte_offset: ?u32 = null, |
| 421 | }; | 554 | }; |
| 422 | 555 | ||
| 423 | pub const Function = struct { | 556 | pub const Function = struct { |
src/link/NvPtx.zig+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | //! NVidia PTX (Paralle Thread Execution) | 1 | //! NVidia PTX (Paralle Thread Execution) |
| 2 | //! https://docs.nvidia.com/cuda/parallel-thread-execution/index.html | 2 | //! https://docs.nvidia.com/cuda/parallel-thread-execution/index.html |
| 3 | //! For this we rely on the nvptx backend of LLVM | 3 | //! For this we rely on the nvptx backend of LLVM |
| 4 | //! Kernel functions need to be marked both as "export" and "callconv(.PtxKernel)" | 4 | //! Kernel functions need to be marked both as "export" and "callconv(.Kernel)" |
| 5 | 5 | ||
| 6 | const NvPtx = @This(); | 6 | const NvPtx = @This(); |
| 7 | 7 |
src/link/SpirV.zig+65-141| ... | @@ -44,34 +44,25 @@ const IdResult = spec.IdResult; | ... | @@ -44,34 +44,25 @@ const IdResult = spec.IdResult; |
| 44 | 44 | ||
| 45 | base: link.File, | 45 | base: link.File, |
| 46 | 46 | ||
| 47 | /// This linker backend does not try to incrementally link output SPIR-V code. | 47 | spv: SpvModule, |
| 48 | /// Instead, it tracks all declarations in this table, and iterates over it | 48 | spv_arena: ArenaAllocator, |
| 49 | /// in the flush function. | 49 | decl_link: codegen.DeclLinkMap, |
| 50 | decl_table: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, DeclGenContext) = .{}, | ||
| 51 | |||
| 52 | const DeclGenContext = struct { | ||
| 53 | air: Air, | ||
| 54 | air_arena: ArenaAllocator.State, | ||
| 55 | liveness: Liveness, | ||
| 56 | |||
| 57 | fn deinit(self: *DeclGenContext, gpa: Allocator) void { | ||
| 58 | self.air.deinit(gpa); | ||
| 59 | self.liveness.deinit(gpa); | ||
| 60 | self.air_arena.promote(gpa).deinit(); | ||
| 61 | self.* = undefined; | ||
| 62 | } | ||
| 63 | }; | ||
| 64 | 50 | ||
| 65 | pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV { | 51 | pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV { |
| 66 | const spirv = try gpa.create(SpirV); | 52 | const self = try gpa.create(SpirV); |
| 67 | spirv.* = .{ | 53 | self.* = .{ |
| 68 | .base = .{ | 54 | .base = .{ |
| 69 | .tag = .spirv, | 55 | .tag = .spirv, |
| 70 | .options = options, | 56 | .options = options, |
| 71 | .file = null, | 57 | .file = null, |
| 72 | .allocator = gpa, | 58 | .allocator = gpa, |
| 73 | }, | 59 | }, |
| 60 | .spv = undefined, | ||
| 61 | .spv_arena = ArenaAllocator.init(gpa), | ||
| 62 | .decl_link = codegen.DeclLinkMap.init(self.base.allocator), | ||
| 74 | }; | 63 | }; |
| 64 | self.spv = SpvModule.init(gpa, self.spv_arena.allocator()); | ||
| 65 | errdefer self.deinit(); | ||
| 75 | 66 | ||
| 76 | // TODO: Figure out where to put all of these | 67 | // TODO: Figure out where to put all of these |
| 77 | switch (options.target.cpu.arch) { | 68 | switch (options.target.cpu.arch) { |
| ... | @@ -88,7 +79,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV { | ... | @@ -88,7 +79,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV { |
| 88 | return error.TODOAbiNotSupported; | 79 | return error.TODOAbiNotSupported; |
| 89 | } | 80 | } |
| 90 | 81 | ||
| 91 | return spirv; | 82 | return self; |
| 92 | } | 83 | } |
| 93 | 84 | ||
| 94 | pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*SpirV { | 85 | pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*SpirV { |
| ... | @@ -107,44 +98,35 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option | ... | @@ -107,44 +98,35 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option |
| 107 | } | 98 | } |
| 108 | 99 | ||
| 109 | pub fn deinit(self: *SpirV) void { | 100 | pub fn deinit(self: *SpirV) void { |
| 110 | self.decl_table.deinit(self.base.allocator); | 101 | self.spv.deinit(); |
| 102 | self.spv_arena.deinit(); | ||
| 103 | self.decl_link.deinit(); | ||
| 111 | } | 104 | } |
| 112 | 105 | ||
| 113 | pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void { | 106 | pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void { |
| 114 | if (build_options.skip_non_native) { | 107 | if (build_options.skip_non_native) { |
| 115 | @panic("Attempted to compile for architecture that was disabled by build configuration"); | 108 | @panic("Attempted to compile for architecture that was disabled by build configuration"); |
| 116 | } | 109 | } |
| 117 | _ = module; | ||
| 118 | |||
| 119 | // Keep track of all decls so we can iterate over them on flush(). | ||
| 120 | const result = try self.decl_table.getOrPut(self.base.allocator, func.owner_decl); | ||
| 121 | if (result.found_existing) { | ||
| 122 | result.value_ptr.deinit(self.base.allocator); | ||
| 123 | } | ||
| 124 | 110 | ||
| 125 | var arena = ArenaAllocator.init(self.base.allocator); | 111 | var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link); |
| 126 | errdefer arena.deinit(); | 112 | defer decl_gen.deinit(); |
| 127 | |||
| 128 | var new_air = try cloneAir(air, self.base.allocator, arena.allocator()); | ||
| 129 | errdefer new_air.deinit(self.base.allocator); | ||
| 130 | |||
| 131 | var new_liveness = try cloneLiveness(liveness, self.base.allocator); | ||
| 132 | errdefer new_liveness.deinit(self.base.allocator); | ||
| 133 | 113 | ||
| 134 | result.value_ptr.* = .{ | 114 | if (try decl_gen.gen(func.owner_decl, air, liveness)) |msg| { |
| 135 | .air = new_air, | 115 | try module.failed_decls.put(module.gpa, func.owner_decl, msg); |
| 136 | .air_arena = arena.state, | 116 | } |
| 137 | .liveness = new_liveness, | ||
| 138 | }; | ||
| 139 | } | 117 | } |
| 140 | 118 | ||
| 141 | pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) !void { | 119 | pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) !void { |
| 142 | if (build_options.skip_non_native) { | 120 | if (build_options.skip_non_native) { |
| 143 | @panic("Attempted to compile for architecture that was disabled by build configuration"); | 121 | @panic("Attempted to compile for architecture that was disabled by build configuration"); |
| 144 | } | 122 | } |
| 145 | _ = module; | 123 | |
| 146 | // Keep track of all decls so we can iterate over them on flush(). | 124 | var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link); |
| 147 | _ = try self.decl_table.getOrPut(self.base.allocator, decl_index); | 125 | defer decl_gen.deinit(); |
| 126 | |||
| 127 | if (try decl_gen.gen(decl_index, undefined, undefined)) |msg| { | ||
| 128 | try module.failed_decls.put(module.gpa, decl_index, msg); | ||
| 129 | } | ||
| 148 | } | 130 | } |
| 149 | 131 | ||
| 150 | pub fn updateDeclExports( | 132 | pub fn updateDeclExports( |
| ... | @@ -153,20 +135,26 @@ pub fn updateDeclExports( | ... | @@ -153,20 +135,26 @@ pub fn updateDeclExports( |
| 153 | decl_index: Module.Decl.Index, | 135 | decl_index: Module.Decl.Index, |
| 154 | exports: []const *Module.Export, | 136 | exports: []const *Module.Export, |
| 155 | ) !void { | 137 | ) !void { |
| 156 | _ = self; | 138 | const decl = module.declPtr(decl_index); |
| 157 | _ = module; | 139 | if (decl.val.tag() == .function and decl.ty.fnCallingConvention() == .Kernel) { |
| 158 | _ = decl_index; | 140 | // TODO: Unify with resolveDecl in spirv.zig. |
| 159 | _ = exports; | 141 | const entry = try self.decl_link.getOrPut(decl_index); |
| 160 | } | 142 | if (!entry.found_existing) { |
| 143 | entry.value_ptr.* = try self.spv.allocDecl(.func); | ||
| 144 | } | ||
| 145 | const spv_decl_index = entry.value_ptr.*; | ||
| 161 | 146 | ||
| 162 | pub fn freeDecl(self: *SpirV, decl_index: Module.Decl.Index) void { | 147 | for (exports) |exp| { |
| 163 | if (self.decl_table.getIndex(decl_index)) |index| { | 148 | try self.spv.declareEntryPoint(spv_decl_index, exp.options.name); |
| 164 | const module = self.base.options.module.?; | ||
| 165 | const decl = module.declPtr(decl_index); | ||
| 166 | if (decl.val.tag() == .function) { | ||
| 167 | self.decl_table.values()[index].deinit(self.base.allocator); | ||
| 168 | } | 149 | } |
| 169 | } | 150 | } |
| 151 | |||
| 152 | // TODO: Export regular functions, variables, etc using Linkage attributes. | ||
| 153 | } | ||
| 154 | |||
| 155 | pub fn freeDecl(self: *SpirV, decl_index: Module.Decl.Index) void { | ||
| 156 | _ = self; | ||
| 157 | _ = decl_index; | ||
| 170 | } | 158 | } |
| 171 | 159 | ||
| 172 | pub fn flush(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void { | 160 | pub fn flush(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void { |
| ... | @@ -189,60 +177,38 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No | ... | @@ -189,60 +177,38 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No |
| 189 | sub_prog_node.activate(); | 177 | sub_prog_node.activate(); |
| 190 | defer sub_prog_node.end(); | 178 | defer sub_prog_node.end(); |
| 191 | 179 | ||
| 192 | const module = self.base.options.module.?; | ||
| 193 | const target = comp.getTarget(); | 180 | const target = comp.getTarget(); |
| 181 | try writeCapabilities(&self.spv, target); | ||
| 182 | try writeMemoryModel(&self.spv, target); | ||
| 194 | 183 | ||
| 195 | var arena = std.heap.ArenaAllocator.init(self.base.allocator); | 184 | // We need to export the list of error names somewhere so that we can pretty-print them in the |
| 196 | defer arena.deinit(); | 185 | // executor. This is not really an important thing though, so we can just dump it in any old |
| 197 | 186 | // nonsemantic instruction. For now, just put it in OpSourceExtension with a special name. | |
| 198 | var spv = SpvModule.init(self.base.allocator, arena.allocator()); | ||
| 199 | defer spv.deinit(); | ||
| 200 | |||
| 201 | // Allocate an ID for every declaration before generating code, | ||
| 202 | // so that we can access them before processing them. | ||
| 203 | // TODO: We're allocating an ID unconditionally now, are there | ||
| 204 | // declarations which don't generate a result? | ||
| 205 | var ids = std.AutoHashMap(Module.Decl.Index, IdResult).init(self.base.allocator); | ||
| 206 | defer ids.deinit(); | ||
| 207 | try ids.ensureTotalCapacity(@intCast(u32, self.decl_table.count())); | ||
| 208 | 187 | ||
| 209 | for (self.decl_table.keys()) |decl_index| { | 188 | var error_info = std.ArrayList(u8).init(self.spv.arena); |
| 210 | const decl = module.declPtr(decl_index); | 189 | try error_info.appendSlice("zig_errors"); |
| 211 | if (decl.has_tv) { | 190 | const module = self.base.options.module.?; |
| 212 | ids.putAssumeCapacityNoClobber(decl_index, spv.allocId()); | 191 | for (module.error_name_list.items) |name| { |
| 213 | } | 192 | // Errors can contain pretty much any character - to encode them in a string we must escape |
| 214 | } | 193 | // them somehow. Easiest here is to use some established scheme, one which also preseves the |
| 215 | 194 | // name if it contains no strange characters is nice for debugging. URI encoding fits the bill. | |
| 216 | // Now, actually generate the code for all declarations. | 195 | // We're using : as separator, which is a reserved character. |
| 217 | var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &spv, &ids); | 196 | |
| 218 | defer decl_gen.deinit(); | 197 | const escaped_name = try std.Uri.escapeString(self.base.allocator, name); |
| 219 | 198 | defer self.base.allocator.free(escaped_name); | |
| 220 | var it = self.decl_table.iterator(); | 199 | try error_info.writer().print(":{s}", .{escaped_name}); |
| 221 | while (it.next()) |entry| { | ||
| 222 | const decl_index = entry.key_ptr.*; | ||
| 223 | const decl = module.declPtr(decl_index); | ||
| 224 | if (!decl.has_tv) continue; | ||
| 225 | |||
| 226 | const air = entry.value_ptr.air; | ||
| 227 | const liveness = entry.value_ptr.liveness; | ||
| 228 | |||
| 229 | // Note, if `decl` is not a function, air/liveness may be undefined. | ||
| 230 | if (try decl_gen.gen(decl_index, air, liveness)) |msg| { | ||
| 231 | try module.failed_decls.put(module.gpa, decl_index, msg); | ||
| 232 | return; // TODO: Attempt to generate more decls? | ||
| 233 | } | ||
| 234 | } | 200 | } |
| 201 | try self.spv.sections.debug_strings.emit(self.spv.gpa, .OpSourceExtension, .{ | ||
| 202 | .extension = error_info.items, | ||
| 203 | }); | ||
| 235 | 204 | ||
| 236 | try writeCapabilities(&spv, target); | 205 | try self.spv.flush(self.base.file.?); |
| 237 | try writeMemoryModel(&spv, target); | ||
| 238 | |||
| 239 | try spv.flush(self.base.file.?); | ||
| 240 | } | 206 | } |
| 241 | 207 | ||
| 242 | fn writeCapabilities(spv: *SpvModule, target: std.Target) !void { | 208 | fn writeCapabilities(spv: *SpvModule, target: std.Target) !void { |
| 243 | // TODO: Integrate with a hypothetical feature system | 209 | // TODO: Integrate with a hypothetical feature system |
| 244 | const caps: []const spec.Capability = switch (target.os.tag) { | 210 | const caps: []const spec.Capability = switch (target.os.tag) { |
| 245 | .opencl => &.{.Kernel}, | 211 | .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .GenericPointer }, |
| 246 | .glsl450 => &.{.Shader}, | 212 | .glsl450 => &.{.Shader}, |
| 247 | .vulkan => &.{.Shader}, | 213 | .vulkan => &.{.Shader}, |
| 248 | else => unreachable, // TODO | 214 | else => unreachable, // TODO |
| ... | @@ -279,45 +245,3 @@ fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void { | ... | @@ -279,45 +245,3 @@ fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void { |
| 279 | .memory_model = memory_model, | 245 | .memory_model = memory_model, |
| 280 | }); | 246 | }); |
| 281 | } | 247 | } |
| 282 | |||
| 283 | fn cloneLiveness(l: Liveness, gpa: Allocator) !Liveness { | ||
| 284 | const tomb_bits = try gpa.dupe(usize, l.tomb_bits); | ||
| 285 | errdefer gpa.free(tomb_bits); | ||
| 286 | |||
| 287 | const extra = try gpa.dupe(u32, l.extra); | ||
| 288 | errdefer gpa.free(extra); | ||
| 289 | |||
| 290 | return Liveness{ | ||
| 291 | .tomb_bits = tomb_bits, | ||
| 292 | .extra = extra, | ||
| 293 | .special = try l.special.clone(gpa), | ||
| 294 | }; | ||
| 295 | } | ||
| 296 | |||
| 297 | fn cloneAir(air: Air, gpa: Allocator, air_arena: Allocator) !Air { | ||
| 298 | const values = try gpa.alloc(Value, air.values.len); | ||
| 299 | errdefer gpa.free(values); | ||
| 300 | |||
| 301 | for (values, 0..) |*value, i| { | ||
| 302 | value.* = try air.values[i].copy(air_arena); | ||
| 303 | } | ||
| 304 | |||
| 305 | var instructions = try air.instructions.toMultiArrayList().clone(gpa); | ||
| 306 | errdefer instructions.deinit(gpa); | ||
| 307 | |||
| 308 | const air_tags = instructions.items(.tag); | ||
| 309 | const air_datas = instructions.items(.data); | ||
| 310 | |||
| 311 | for (air_tags, 0..) |tag, i| { | ||
| 312 | switch (tag) { | ||
| 313 | .alloc, .ret_ptr, .const_ty => air_datas[i].ty = try air_datas[i].ty.copy(air_arena), | ||
| 314 | else => {}, | ||
| 315 | } | ||
| 316 | } | ||
| 317 | |||
| 318 | return Air{ | ||
| 319 | .instructions = instructions.slice(), | ||
| 320 | .extra = try gpa.dupe(u32, air.extra), | ||
| 321 | .values = values, | ||
| 322 | }; | ||
| 323 | } |
src/target.zig+4-4| ... | @@ -163,7 +163,7 @@ pub fn canBuildLibC(target: std.Target) bool { | ... | @@ -163,7 +163,7 @@ pub fn canBuildLibC(target: std.Target) bool { |
| 163 | pub fn cannotDynamicLink(target: std.Target) bool { | 163 | pub fn cannotDynamicLink(target: std.Target) bool { |
| 164 | return switch (target.os.tag) { | 164 | return switch (target.os.tag) { |
| 165 | .freestanding, .other => true, | 165 | .freestanding, .other => true, |
| 166 | else => false, | 166 | else => target.isSpirV(), |
| 167 | }; | 167 | }; |
| 168 | } | 168 | } |
| 169 | 169 | ||
| ... | @@ -331,18 +331,18 @@ pub fn supportsStackProbing(target: std.Target) bool { | ... | @@ -331,18 +331,18 @@ pub fn supportsStackProbing(target: std.Target) bool { |
| 331 | } | 331 | } |
| 332 | 332 | ||
| 333 | pub fn supportsStackProtector(target: std.Target) bool { | 333 | pub fn supportsStackProtector(target: std.Target) bool { |
| 334 | _ = target; | 334 | return !target.isSpirV(); |
| 335 | return true; | ||
| 336 | } | 335 | } |
| 337 | 336 | ||
| 338 | pub fn libcProvidesStackProtector(target: std.Target) bool { | 337 | pub fn libcProvidesStackProtector(target: std.Target) bool { |
| 339 | return !target.isMinGW() and target.os.tag != .wasi; | 338 | return !target.isMinGW() and target.os.tag != .wasi and !target.isSpirV(); |
| 340 | } | 339 | } |
| 341 | 340 | ||
| 342 | pub fn supportsReturnAddress(target: std.Target) bool { | 341 | pub fn supportsReturnAddress(target: std.Target) bool { |
| 343 | return switch (target.cpu.arch) { | 342 | return switch (target.cpu.arch) { |
| 344 | .wasm32, .wasm64 => target.os.tag == .emscripten, | 343 | .wasm32, .wasm64 => target.os.tag == .emscripten, |
| 345 | .bpfel, .bpfeb => false, | 344 | .bpfel, .bpfeb => false, |
| 345 | .spirv32, .spirv64 => false, | ||
| 346 | else => true, | 346 | else => true, |
| 347 | }; | 347 | }; |
| 348 | } | 348 | } |
src/type.zig+5-2| ... | @@ -4796,9 +4796,12 @@ pub const Type = extern union { | ... | @@ -4796,9 +4796,12 @@ pub const Type = extern union { |
| 4796 | } | 4796 | } |
| 4797 | 4797 | ||
| 4798 | /// Asserts the type is a function. | 4798 | /// Asserts the type is a function. |
| 4799 | pub fn fnCallingConventionAllowsZigTypes(cc: std.builtin.CallingConvention) bool { | 4799 | pub fn fnCallingConventionAllowsZigTypes(target: Target, cc: std.builtin.CallingConvention) bool { |
| 4800 | return switch (cc) { | 4800 | return switch (cc) { |
| 4801 | .Unspecified, .Async, .Inline, .PtxKernel => true, | 4801 | .Unspecified, .Async, .Inline => true, |
| 4802 | // For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI. | ||
| 4803 | // The goal is to experiment with more integrated CPU/GPU code. | ||
| 4804 | .Kernel => target.cpu.arch == .nvptx or target.cpu.arch == .nvptx64, | ||
| 4802 | else => false, | 4805 | else => false, |
| 4803 | }; | 4806 | }; |
| 4804 | } | 4807 | } |
test/nvptx.zig+4-4| ... | @@ -10,7 +10,7 @@ pub fn addCases(ctx: *Cases) !void { | ... | @@ -10,7 +10,7 @@ pub fn addCases(ctx: *Cases) !void { |
| 10 | \\ return a + b; | 10 | \\ return a + b; |
| 11 | \\} | 11 | \\} |
| 12 | \\ | 12 | \\ |
| 13 | \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.PtxKernel) void { | 13 | \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.Kernel) void { |
| 14 | \\ const x = add(a, 7); | 14 | \\ const x = add(a, 7); |
| 15 | \\ var y = add(2, 0); | 15 | \\ var y = add(2, 0); |
| 16 | \\ y -= x; | 16 | \\ y -= x; |
| ... | @@ -29,7 +29,7 @@ pub fn addCases(ctx: *Cases) !void { | ... | @@ -29,7 +29,7 @@ pub fn addCases(ctx: *Cases) !void { |
| 29 | \\ ); | 29 | \\ ); |
| 30 | \\} | 30 | \\} |
| 31 | \\ | 31 | \\ |
| 32 | \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.PtxKernel) void { | 32 | \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.Kernel) void { |
| 33 | \\ const i = threadIdX(); | 33 | \\ const i = threadIdX(); |
| 34 | \\ out[i] = a[i] + 7; | 34 | \\ out[i] = a[i] + 7; |
| 35 | \\} | 35 | \\} |
| ... | @@ -42,7 +42,7 @@ pub fn addCases(ctx: *Cases) !void { | ... | @@ -42,7 +42,7 @@ pub fn addCases(ctx: *Cases) !void { |
| 42 | case.addCompile( | 42 | case.addCompile( |
| 43 | \\var x: i32 addrspace(.global) = 0; | 43 | \\var x: i32 addrspace(.global) = 0; |
| 44 | \\ | 44 | \\ |
| 45 | \\pub export fn increment(out: *i32) callconv(.PtxKernel) void { | 45 | \\pub export fn increment(out: *i32) callconv(.Kernel) void { |
| 46 | \\ x += 1; | 46 | \\ x += 1; |
| 47 | \\ out.* = x; | 47 | \\ out.* = x; |
| 48 | \\} | 48 | \\} |
| ... | @@ -59,7 +59,7 @@ pub fn addCases(ctx: *Cases) !void { | ... | @@ -59,7 +59,7 @@ pub fn addCases(ctx: *Cases) !void { |
| 59 | \\} | 59 | \\} |
| 60 | \\ | 60 | \\ |
| 61 | \\ var _sdata: [1024]f32 addrspace(.shared) = undefined; | 61 | \\ var _sdata: [1024]f32 addrspace(.shared) = undefined; |
| 62 | \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.PtxKernel) void { | 62 | \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.Kernel) void { |
| 63 | \\ var sdata = @addrSpaceCast(.generic, &_sdata); | 63 | \\ var sdata = @addrSpaceCast(.generic, &_sdata); |
| 64 | \\ const tid: u32 = threadIdX(); | 64 | \\ const tid: u32 = threadIdX(); |
| 65 | \\ var sum = d_x[tid]; | 65 | \\ var sum = d_x[tid]; |
tools/gen_spirv_spec.zig+2-13| ... | @@ -80,22 +80,11 @@ fn render(writer: anytype, allocator: Allocator, registry: g.CoreRegistry) !void | ... | @@ -80,22 +80,11 @@ fn render(writer: anytype, allocator: Allocator, registry: g.CoreRegistry) !void |
| 80 | \\const Version = @import("std").builtin.Version; | 80 | \\const Version = @import("std").builtin.Version; |
| 81 | \\ | 81 | \\ |
| 82 | \\pub const Word = u32; | 82 | \\pub const Word = u32; |
| 83 | \\pub const IdResultType = struct{ | ||
| 84 | \\ id: Word, | ||
| 85 | \\ pub fn toRef(self: IdResultType) IdRef { | ||
| 86 | \\ return .{.id = self.id}; | ||
| 87 | \\ } | ||
| 88 | \\}; | ||
| 89 | \\pub const IdResult = struct{ | 83 | \\pub const IdResult = struct{ |
| 90 | \\ id: Word, | 84 | \\ id: Word, |
| 91 | \\ pub fn toRef(self: IdResult) IdRef { | ||
| 92 | \\ return .{.id = self.id}; | ||
| 93 | \\ } | ||
| 94 | \\ pub fn toResultType(self: IdResult) IdResultType { | ||
| 95 | \\ return .{.id = self.id}; | ||
| 96 | \\ } | ||
| 97 | \\}; | 85 | \\}; |
| 98 | \\pub const IdRef = struct{ id: Word }; | 86 | \\pub const IdResultType = IdResult; |
| 87 | \\pub const IdRef = IdResult; | ||
| 99 | \\ | 88 | \\ |
| 100 | \\pub const IdMemorySemantics = IdRef; | 89 | \\pub const IdMemorySemantics = IdRef; |
| 101 | \\pub const IdScope = IdRef; | 90 | \\pub const IdScope = IdRef; |