| author | |
| committer | |
| log | c6d178f93de5922163b51cedbf05023623558c6b |
| tree | 60c75b3ed435e687efc1a58da47a53feafe2e045 |
| parent | cfeab9258739f44b73542952b3ca62c1187669e1 |
The SPIR-V backend previously ran codegen single-threaded in the linker thread.
Now each codegen job creates an `Mir` like other self-hosted backends.
Other changes:
- Bring back `dedup_types.zig` and `prune_unused.zig` **ISel**s which were originaly
removed because codegen was single-threaded at that time and therefor had no use
- Clean up `BinaryModule.zig`
- Remove `checkLogicalPtrOperation` from `elemPtrOneLayerOnly` in `Sema.zig`.
Element access uses `OpAccessChain`, which works on all logical address spaces
without `VariablePointers`. the check is only needed for pointer arithmetic.20 files changed, 1512 insertions(+), 387 deletions(-)
src/Sema.zig+9-21| ... | ... | @@ -27094,11 +27094,9 @@ fn elemPtrOneLayerOnly( |
| 27094 | 27094 | return .fromValue(try ptr_val.ptrElem(index, pt)); |
| 27095 | 27095 | } |
| 27096 | 27096 | |
| 27097 | try sema.checkLogicalPtrOperation(block, src, indexable_ty); | |
| 27098 | ||
| 27099 | 27097 | const result_ty = try indexable_ty.elemPtrType(maybe_index, pt); |
| 27100 | 27098 | |
| 27101 | try sema.validateRuntimeElemAccess(block, elem_index_src, result_ty, indexable_ty, indexable_src); | |
| 27099 | try sema.validateRuntimeElemAccess(block, elem_index_src, result_ty, indexable_src); | |
| 27102 | 27100 | try sema.validateRuntimeValue(block, indexable_src, indexable); |
| 27103 | 27101 | |
| 27104 | 27102 | if (child_ty.abiSize(zcu) == 0) { |
| ... | ... | @@ -27161,7 +27159,7 @@ fn elemVal( |
| 27161 | 27159 | return sema.analyzeLoad(block, src, .fromValue(elem_ptr_val), indexable_src); |
| 27162 | 27160 | } |
| 27163 | 27161 | |
| 27164 | try sema.validateRuntimeElemAccess(block, elem_index_src, child_ty, indexable_ty, src); | |
| 27162 | try sema.validateRuntimeElemAccess(block, elem_index_src, child_ty, src); | |
| 27165 | 27163 | switch (child_ty.classify(zcu)) { |
| 27166 | 27164 | .runtime => {}, |
| 27167 | 27165 | .one_possible_value => return .fromValue((try child_ty.onePossibleValue(pt)).?), |
| ... | ... | @@ -27210,11 +27208,9 @@ fn validateRuntimeElemAccess( |
| 27210 | 27208 | block: *Block, |
| 27211 | 27209 | elem_index_src: LazySrcLoc, |
| 27212 | 27210 | elem_ty: Type, |
| 27213 | parent_ty: Type, | |
| 27214 | 27211 | parent_src: LazySrcLoc, |
| 27215 | 27212 | ) CompileError!void { |
| 27216 | const pt = sema.pt; | |
| 27217 | const zcu = pt.zcu; | |
| 27213 | const zcu = sema.pt.zcu; | |
| 27218 | 27214 | |
| 27219 | 27215 | if (elem_ty.comptimeOnly(zcu)) { |
| 27220 | 27216 | const msg = msg: { |
| ... | ... | @@ -27231,14 +27227,6 @@ fn validateRuntimeElemAccess( |
| 27231 | 27227 | }; |
| 27232 | 27228 | return sema.failWithOwnedErrorMsg(block, msg); |
| 27233 | 27229 | } |
| 27234 | ||
| 27235 | if (zcu.intern_pool.indexToKey(parent_ty.toIntern()) == .ptr_type) { | |
| 27236 | const target = zcu.getTarget(); | |
| 27237 | const as = parent_ty.ptrAddressSpace(zcu); | |
| 27238 | if (target_util.shouldBlockPointerOps(target, as)) { | |
| 27239 | return sema.fail(block, elem_index_src, "cannot access element of logical pointer '{f}'", .{parent_ty.fmt(pt)}); | |
| 27240 | } | |
| 27241 | } | |
| 27242 | 27230 | } |
| 27243 | 27231 | |
| 27244 | 27232 | /// Validates `elem_index`, and returns a pointer to that field using `structFieldPtrByIndex`. |
| ... | ... | @@ -27309,7 +27297,7 @@ fn tupleField( |
| 27309 | 27297 | return Air.internedToRef((try tuple_val.fieldValue(pt, field_index)).toIntern()); |
| 27310 | 27298 | } |
| 27311 | 27299 | |
| 27312 | try sema.validateRuntimeElemAccess(block, field_index_src, field_ty, tuple_ty, tuple_src); | |
| 27300 | try sema.validateRuntimeElemAccess(block, field_index_src, field_ty, tuple_src); | |
| 27313 | 27301 | |
| 27314 | 27302 | return block.addStructFieldVal(tuple, field_index, field_ty); |
| 27315 | 27303 | } |
| ... | ... | @@ -27364,7 +27352,7 @@ fn elemValArray( |
| 27364 | 27352 | if (try elem_ty.onePossibleValue(pt)) |opv| return .fromValue(opv); |
| 27365 | 27353 | } |
| 27366 | 27354 | |
| 27367 | try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, array_ty, array_src); | |
| 27355 | try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, array_src); | |
| 27368 | 27356 | try sema.validateRuntimeValue(block, array_src, array); |
| 27369 | 27357 | |
| 27370 | 27358 | if (oob_safety and block.wantSafety()) { |
| ... | ... | @@ -27467,7 +27455,7 @@ fn elemPtrVector( |
| 27467 | 27455 | }; |
| 27468 | 27456 | |
| 27469 | 27457 | if (!init) { |
| 27470 | try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, vector_ty, vector_ptr_src); | |
| 27458 | try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, vector_ptr_src); | |
| 27471 | 27459 | try sema.validateRuntimeValue(block, vector_ptr_src, vector_ptr); |
| 27472 | 27460 | } |
| 27473 | 27461 | |
| ... | ... | @@ -27540,7 +27528,7 @@ fn elemPtrArray( |
| 27540 | 27528 | } |
| 27541 | 27529 | |
| 27542 | 27530 | if (!init) { |
| 27543 | try sema.validateRuntimeElemAccess(block, elem_index_src, array_ty.childType(zcu), array_ty, array_ptr_src); | |
| 27531 | try sema.validateRuntimeElemAccess(block, elem_index_src, array_ty.childType(zcu), array_ptr_src); | |
| 27544 | 27532 | try sema.validateRuntimeValue(block, array_ptr_src, array_ptr); |
| 27545 | 27533 | } |
| 27546 | 27534 | |
| ... | ... | @@ -27603,7 +27591,7 @@ fn elemValSlice( |
| 27603 | 27591 | |
| 27604 | 27592 | if (try elem_ty.onePossibleValue(pt)) |opv| return .fromValue(opv); |
| 27605 | 27593 | |
| 27606 | try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, slice_ty, slice_src); | |
| 27594 | try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, slice_src); | |
| 27607 | 27595 | try sema.validateRuntimeValue(block, slice_src, slice); |
| 27608 | 27596 | |
| 27609 | 27597 | if (oob_safety and block.wantSafety()) { |
| ... | ... | @@ -27663,7 +27651,7 @@ fn elemPtrSlice( |
| 27663 | 27651 | } |
| 27664 | 27652 | } |
| 27665 | 27653 | |
| 27666 | try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ptr_ty, slice_ty, slice_src); | |
| 27654 | try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ptr_ty, slice_src); | |
| 27667 | 27655 | try sema.validateRuntimeValue(block, slice_src, slice); |
| 27668 | 27656 | |
| 27669 | 27657 | if (oob_safety and block.wantSafety()) { |
src/Zcu/PerThread.zig-13| ... | ... | @@ -4513,7 +4513,6 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru |
| 4513 | 4513 | comp.config.use_llvm, |
| 4514 | 4514 | )) { |
| 4515 | 4515 | else => unreachable, // assertion failure |
| 4516 | .stage2_spirv, | |
| 4517 | 4516 | .stage2_llvm, |
| 4518 | 4517 | => {}, |
| 4519 | 4518 | }, |
| ... | ... | @@ -4594,18 +4593,6 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e |
| 4594 | 4593 | |
| 4595 | 4594 | const lf = comp.bin_file orelse return error.NoLinkFile; |
| 4596 | 4595 | |
| 4597 | // Just like LLVM, the SPIR-V backend can't multi-threaded due to SPIR-V design limitations. | |
| 4598 | if (lf.cast(.spirv)) |spirv_file| { | |
| 4599 | assert(zcu.pending_codegen_jobs.load(.monotonic) == 2); // only one codegen at a time (but the value is 2 because 1 is the base) | |
| 4600 | spirv_file.updateFunc(pt, func_index, air, &liveness) catch |err| { | |
| 4601 | switch (err) { | |
| 4602 | error.OutOfMemory => comp.link_diags.setAllocFailure(), | |
| 4603 | } | |
| 4604 | return error.AlreadyReported; | |
| 4605 | }; | |
| 4606 | return error.BackendDoesNotProduceMir; | |
| 4607 | } | |
| 4608 | ||
| 4609 | 4596 | return codegen.generateFunction(lf, pt, func_index, air, &liveness); |
| 4610 | 4597 | } |
| 4611 | 4598 |
src/codegen.zig+4| ... | ... | @@ -101,6 +101,7 @@ pub const AnyMir = union { |
| 101 | 101 | x86_64: if (dev.env.supports(.x86_64_backend)) @import("codegen/x86_64/Mir.zig") else noreturn, |
| 102 | 102 | wasm: if (dev.env.supports(.wasm_backend)) @import("codegen/wasm/Mir.zig") else noreturn, |
| 103 | 103 | c: if (dev.env.supports(.c_backend)) @import("codegen/c.zig").Mir else noreturn, |
| 104 | spirv: if (dev.env.supports(.spirv_backend)) @import("codegen/spirv/Mir.zig") else noreturn, | |
| 104 | 105 | |
| 105 | 106 | pub inline fn tag(comptime backend: std.lang.CompilerBackend) []const u8 { |
| 106 | 107 | return switch (backend) { |
| ... | ... | @@ -110,6 +111,7 @@ pub const AnyMir = union { |
| 110 | 111 | .stage2_x86_64 => "x86_64", |
| 111 | 112 | .stage2_wasm => "wasm", |
| 112 | 113 | .stage2_c => "c", |
| 114 | .stage2_spirv => "spirv", | |
| 113 | 115 | else => unreachable, |
| 114 | 116 | }; |
| 115 | 117 | } |
| ... | ... | @@ -125,6 +127,7 @@ pub const AnyMir = union { |
| 125 | 127 | .stage2_x86_64, |
| 126 | 128 | .stage2_wasm, |
| 127 | 129 | .stage2_c, |
| 130 | .stage2_spirv, | |
| 128 | 131 | => |backend_ct| @field(mir, tag(backend_ct)).deinit(gpa), |
| 129 | 132 | } |
| 130 | 133 | } |
| ... | ... | @@ -153,6 +156,7 @@ pub fn generateFunction( |
| 153 | 156 | .stage2_x86_64, |
| 154 | 157 | .stage2_wasm, |
| 155 | 158 | .stage2_c, |
| 159 | .stage2_spirv, | |
| 156 | 160 | => |backend| { |
| 157 | 161 | dev.check(devFeatureForBackend(backend)); |
| 158 | 162 | const CodeGen = importBackend(backend); |
src/codegen/spirv/CodeGen.zig+194| ... | ... | @@ -5,6 +5,8 @@ const Signedness = std.lang.Signedness; |
| 5 | 5 | const assert = std.debug.assert; |
| 6 | 6 | const log = std.log.scoped(.codegen); |
| 7 | 7 | |
| 8 | const link = @import("../../link.zig"); | |
| 9 | const codegen = @import("../../codegen.zig"); | |
| 8 | 10 | const Zcu = @import("../../Zcu.zig"); |
| 9 | 11 | const Type = @import("../../Type.zig"); |
| 10 | 12 | const Value = @import("../../Value.zig"); |
| ... | ... | @@ -12,6 +14,7 @@ const Air = @import("../../Air.zig"); |
| 12 | 14 | const InternPool = @import("../../InternPool.zig"); |
| 13 | 15 | const Section = @import("Section.zig"); |
| 14 | 16 | const Assembler = @import("Assembler.zig"); |
| 17 | const Mir = @import("Mir.zig"); | |
| 15 | 18 | |
| 16 | 19 | const spec = @import("spec.zig"); |
| 17 | 20 | const Opcode = spec.Opcode; |
| ... | ... | @@ -169,6 +172,197 @@ pub fn deinit(cg: *CodeGen) void { |
| 169 | 172 | cg.body.deinit(gpa); |
| 170 | 173 | } |
| 171 | 174 | |
| 175 | pub fn generate( | |
| 176 | _: *link.File, | |
| 177 | pt: Zcu.PerThread, | |
| 178 | func_index: InternPool.Index, | |
| 179 | air: *const Air, | |
| 180 | liveness: *const ?Air.Liveness, | |
| 181 | ) codegen.Error!Mir { | |
| 182 | const zcu = pt.zcu; | |
| 183 | const gpa = zcu.gpa; | |
| 184 | const nav = zcu.funcInfo(func_index).owner_nav; | |
| 185 | const structured_cfg = zcu.navFileScope(nav).mod.?.structured_cfg; | |
| 186 | ||
| 187 | var arena = std.heap.ArenaAllocator.init(gpa); | |
| 188 | defer arena.deinit(); | |
| 189 | var module: Module = .{ | |
| 190 | .gpa = gpa, | |
| 191 | .arena = arena.allocator(), | |
| 192 | .zcu = zcu, | |
| 193 | }; | |
| 194 | defer module.deinit(); | |
| 195 | ||
| 196 | var cg: CodeGen = .{ | |
| 197 | .pt = pt, | |
| 198 | .air = air.*, | |
| 199 | .liveness = liveness.*.?, | |
| 200 | .owner_nav = nav, | |
| 201 | .module = &module, | |
| 202 | .control_flow = switch (structured_cfg) { | |
| 203 | true => .{ .structured = .{} }, | |
| 204 | false => .{ .unstructured = .{} }, | |
| 205 | }, | |
| 206 | .base_line = zcu.navSrcLine(nav), | |
| 207 | }; | |
| 208 | defer cg.deinit(); | |
| 209 | ||
| 210 | cg.genNav(true) catch |err| switch (err) { | |
| 211 | error.AlreadyReported => return error.AlreadyReported, | |
| 212 | error.OutOfMemory => return error.OutOfMemory, | |
| 213 | }; | |
| 214 | ||
| 215 | return cg.serializeToMir(gpa); | |
| 216 | } | |
| 217 | ||
| 218 | pub fn generateNav( | |
| 219 | pt: Zcu.PerThread, | |
| 220 | nav_index: InternPool.Nav.Index, | |
| 221 | ) codegen.Error!Mir { | |
| 222 | const zcu = pt.zcu; | |
| 223 | const gpa = zcu.gpa; | |
| 224 | const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg; | |
| 225 | ||
| 226 | var arena = std.heap.ArenaAllocator.init(gpa); | |
| 227 | defer arena.deinit(); | |
| 228 | var module: Module = .{ | |
| 229 | .gpa = gpa, | |
| 230 | .arena = arena.allocator(), | |
| 231 | .zcu = zcu, | |
| 232 | }; | |
| 233 | defer module.deinit(); | |
| 234 | ||
| 235 | var cg: CodeGen = .{ | |
| 236 | .pt = pt, | |
| 237 | .air = undefined, | |
| 238 | .liveness = undefined, | |
| 239 | .owner_nav = nav_index, | |
| 240 | .module = &module, | |
| 241 | .control_flow = switch (structured_cfg) { | |
| 242 | true => .{ .structured = .{} }, | |
| 243 | false => .{ .unstructured = .{} }, | |
| 244 | }, | |
| 245 | .base_line = zcu.navSrcLine(nav_index), | |
| 246 | }; | |
| 247 | defer cg.deinit(); | |
| 248 | ||
| 249 | cg.genNav(false) catch |err| switch (err) { | |
| 250 | error.AlreadyReported => return error.AlreadyReported, | |
| 251 | error.OutOfMemory => return error.OutOfMemory, | |
| 252 | }; | |
| 253 | ||
| 254 | return cg.serializeToMir(gpa); | |
| 255 | } | |
| 256 | ||
| 257 | fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir { | |
| 258 | const module = cg.module; | |
| 259 | ||
| 260 | const owner_entry = module.nav_link.get(cg.owner_nav); | |
| 261 | const owner_decl_index = owner_entry orelse return .{ | |
| 262 | .id_bound = module.next_result_id, | |
| 263 | .owner_nav = cg.owner_nav, | |
| 264 | .kind = .func, | |
| 265 | .decl_result_id = .none, | |
| 266 | .extended_instruction_set = &.{}, | |
| 267 | .globals = &.{}, | |
| 268 | .functions = &.{}, | |
| 269 | .annotations = &.{}, | |
| 270 | .debug_names = &.{}, | |
| 271 | .debug_strings = &.{}, | |
| 272 | .execution_modes = &.{}, | |
| 273 | .nav_refs = &.{}, | |
| 274 | .uav_refs = &.{}, | |
| 275 | .decl_deps = &.{}, | |
| 276 | .internal_globals = &.{}, | |
| 277 | .entry_points = &.{}, | |
| 278 | }; | |
| 279 | ||
| 280 | const owner_decl = module.declPtr(owner_decl_index); | |
| 281 | ||
| 282 | var nav_refs: std.ArrayList(Mir.NavRef) = .empty; | |
| 283 | defer nav_refs.deinit(gpa); | |
| 284 | var nav_it = module.nav_link.iterator(); | |
| 285 | while (nav_it.next()) |entry| { | |
| 286 | if (entry.key_ptr.* == cg.owner_nav) continue; | |
| 287 | const decl = module.declPtr(entry.value_ptr.*); | |
| 288 | try nav_refs.append(gpa, .{ | |
| 289 | .local_id = decl.result_id, | |
| 290 | .nav = entry.key_ptr.*, | |
| 291 | .kind = decl.kind, | |
| 292 | }); | |
| 293 | } | |
| 294 | ||
| 295 | var uav_refs: std.ArrayList(Mir.UavRef) = .empty; | |
| 296 | defer uav_refs.deinit(gpa); | |
| 297 | var uav_it = module.uav_link.iterator(); | |
| 298 | while (uav_it.next()) |entry| { | |
| 299 | const decl = module.declPtr(entry.value_ptr.*); | |
| 300 | try uav_refs.append(gpa, .{ | |
| 301 | .local_id = decl.result_id, | |
| 302 | .val = entry.key_ptr.*[0], | |
| 303 | .storage_class = entry.key_ptr.*[1], | |
| 304 | .kind = decl.kind, | |
| 305 | }); | |
| 306 | } | |
| 307 | ||
| 308 | var decl_deps: std.ArrayList(Mir.DeclDep) = .empty; | |
| 309 | defer decl_deps.deinit(gpa); | |
| 310 | var internal_globals: std.ArrayList(Id) = .empty; | |
| 311 | defer internal_globals.deinit(gpa); | |
| 312 | ||
| 313 | const deps = module.decl_deps.items[owner_decl.begin_dep..owner_decl.end_dep]; | |
| 314 | for (deps) |dep_index| { | |
| 315 | const dep_decl = module.declPtr(dep_index); | |
| 316 | var found = false; | |
| 317 | nav_it.index = 0; | |
| 318 | while (nav_it.next()) |entry| { | |
| 319 | if (entry.value_ptr.* == dep_index) { | |
| 320 | try decl_deps.append(gpa, .{ | |
| 321 | .kind = dep_decl.kind, | |
| 322 | .nav = entry.key_ptr.*, | |
| 323 | }); | |
| 324 | found = true; | |
| 325 | break; | |
| 326 | } | |
| 327 | } | |
| 328 | if (!found and dep_decl.kind == .global) { | |
| 329 | try internal_globals.append(gpa, dep_decl.result_id); | |
| 330 | } | |
| 331 | } | |
| 332 | ||
| 333 | var ep_list: std.ArrayList(Mir.EntryPoint) = .empty; | |
| 334 | defer ep_list.deinit(gpa); | |
| 335 | var ep_it = module.entry_points.iterator(); | |
| 336 | while (ep_it.next()) |entry| { | |
| 337 | const ep = entry.value_ptr; | |
| 338 | const ep_decl = module.declPtr(ep.decl_index); | |
| 339 | try ep_list.append(gpa, .{ | |
| 340 | .local_id = ep_decl.result_id, | |
| 341 | .name = try gpa.dupe(u8, ep.name), | |
| 342 | .cc = ep.cc, | |
| 343 | }); | |
| 344 | } | |
| 345 | ||
| 346 | return .{ | |
| 347 | .id_bound = module.next_result_id, | |
| 348 | .owner_nav = cg.owner_nav, | |
| 349 | .kind = owner_decl.kind, | |
| 350 | .decl_result_id = owner_decl.result_id, | |
| 351 | .extended_instruction_set = try module.sections.extended_instruction_set.instructions.toOwnedSlice(gpa), | |
| 352 | .globals = try module.sections.globals.instructions.toOwnedSlice(gpa), | |
| 353 | .functions = try module.sections.functions.instructions.toOwnedSlice(gpa), | |
| 354 | .annotations = try module.sections.annotations.instructions.toOwnedSlice(gpa), | |
| 355 | .debug_names = try module.sections.debug_names.instructions.toOwnedSlice(gpa), | |
| 356 | .debug_strings = try module.sections.debug_strings.instructions.toOwnedSlice(gpa), | |
| 357 | .execution_modes = try module.sections.execution_modes.instructions.toOwnedSlice(gpa), | |
| 358 | .nav_refs = try nav_refs.toOwnedSlice(gpa), | |
| 359 | .uav_refs = try uav_refs.toOwnedSlice(gpa), | |
| 360 | .decl_deps = try decl_deps.toOwnedSlice(gpa), | |
| 361 | .internal_globals = try internal_globals.toOwnedSlice(gpa), | |
| 362 | .entry_points = try ep_list.toOwnedSlice(gpa), | |
| 363 | }; | |
| 364 | } | |
| 365 | ||
| 172 | 366 | const Error = error{ AlreadyReported, OutOfMemory }; |
| 173 | 367 | |
| 174 | 368 | pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { |
src/codegen/spirv/Mir.zig created+71| ... | ... | @@ -0,0 +1,71 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Allocator = std.mem.Allocator; | |
| 3 | ||
| 4 | const spec = @import("spec.zig"); | |
| 5 | const Word = spec.Word; | |
| 6 | const Id = spec.Id; | |
| 7 | ||
| 8 | const InternPool = @import("../../InternPool.zig"); | |
| 9 | const Module = @import("Module.zig"); | |
| 10 | ||
| 11 | const Mir = @This(); | |
| 12 | ||
| 13 | id_bound: Word, | |
| 14 | owner_nav: InternPool.Nav.Index, | |
| 15 | kind: Module.Decl.Kind, | |
| 16 | decl_result_id: Id, | |
| 17 | extended_instruction_set: []const Word, | |
| 18 | globals: []const Word, | |
| 19 | functions: []const Word, | |
| 20 | annotations: []const Word, | |
| 21 | debug_names: []const Word, | |
| 22 | debug_strings: []const Word, | |
| 23 | execution_modes: []const Word, | |
| 24 | nav_refs: []const NavRef, | |
| 25 | uav_refs: []const UavRef, | |
| 26 | decl_deps: []const DeclDep, | |
| 27 | internal_globals: []const Id, | |
| 28 | entry_points: []const EntryPoint, | |
| 29 | ||
| 30 | pub const NavRef = struct { | |
| 31 | local_id: Id, | |
| 32 | nav: InternPool.Nav.Index, | |
| 33 | kind: Module.Decl.Kind, | |
| 34 | }; | |
| 35 | ||
| 36 | pub const UavRef = struct { | |
| 37 | local_id: Id, | |
| 38 | val: InternPool.Index, | |
| 39 | storage_class: spec.StorageClass, | |
| 40 | kind: Module.Decl.Kind, | |
| 41 | }; | |
| 42 | ||
| 43 | pub const DeclDep = struct { | |
| 44 | kind: Module.Decl.Kind, | |
| 45 | nav: InternPool.Nav.Index, | |
| 46 | }; | |
| 47 | ||
| 48 | pub const EntryPoint = struct { | |
| 49 | local_id: Id, | |
| 50 | name: []const u8, | |
| 51 | cc: std.builtin.CallingConvention, | |
| 52 | }; | |
| 53 | ||
| 54 | pub fn deinit(mir: *Mir, gpa: Allocator) void { | |
| 55 | gpa.free(mir.extended_instruction_set); | |
| 56 | gpa.free(mir.globals); | |
| 57 | gpa.free(mir.functions); | |
| 58 | gpa.free(mir.annotations); | |
| 59 | gpa.free(mir.debug_names); | |
| 60 | gpa.free(mir.debug_strings); | |
| 61 | gpa.free(mir.execution_modes); | |
| 62 | gpa.free(mir.nav_refs); | |
| 63 | gpa.free(mir.uav_refs); | |
| 64 | gpa.free(mir.decl_deps); | |
| 65 | gpa.free(mir.internal_globals); | |
| 66 | for (mir.entry_points) |ep| { | |
| 67 | gpa.free(ep.name); | |
| 68 | } | |
| 69 | gpa.free(mir.entry_points); | |
| 70 | mir.* = undefined; | |
| 71 | } |
src/link.zig-1| ... | ... | @@ -841,7 +841,6 @@ pub const File = struct { |
| 841 | 841 | assert(base.comp.zcu.?.llvm_object == null); |
| 842 | 842 | switch (base.tag) { |
| 843 | 843 | .lld => unreachable, |
| 844 | .spirv => unreachable, // see corresponding special case in `Zcu.PerThread.runCodegenInner` | |
| 845 | 844 | .plan9 => unreachable, |
| 846 | 845 | inline else => |tag| { |
| 847 | 846 | dev.check(tag.devFeature()); |
src/link/SpirV.zig+619-108| ... | ... | @@ -10,21 +10,33 @@ const Compilation = @import("../Compilation.zig"); |
| 10 | 10 | const link = @import("../link.zig"); |
| 11 | 11 | const Air = @import("../Air.zig"); |
| 12 | 12 | const Type = @import("../Type.zig"); |
| 13 | const codegen = @import("../codegen.zig"); | |
| 13 | 14 | const CodeGen = @import("../codegen/spirv/CodeGen.zig"); |
| 14 | 15 | const Module = @import("../codegen/spirv/Module.zig"); |
| 15 | 16 | const trace = @import("../tracy.zig").trace; |
| 16 | 17 | const BinaryModule = @import("SpirV/BinaryModule.zig"); |
| 17 | 18 | const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig"); |
| 19 | const dedup_types = @import("SpirV/dedup_types.zig"); | |
| 20 | const prune_unused = @import("SpirV/prune_unused.zig"); | |
| 18 | 21 | |
| 19 | 22 | const spec = @import("../codegen/spirv/spec.zig"); |
| 23 | const Section = @import("../codegen/spirv/Section.zig"); | |
| 20 | 24 | const Id = spec.Id; |
| 21 | 25 | const Word = spec.Word; |
| 26 | const Mir = @import("../codegen/spirv/Mir.zig"); | |
| 22 | 27 | |
| 23 | 28 | const Linker = @This(); |
| 24 | 29 | |
| 25 | 30 | base: link.File, |
| 26 | module: Module, | |
| 27 | cg: CodeGen, | |
| 31 | fragments: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, Mir) = .empty, | |
| 32 | pending_navs: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty, | |
| 33 | entry_points: std.ArrayListUnmanaged(EntryPointDecl) = .empty, | |
| 34 | ||
| 35 | const EntryPointDecl = struct { | |
| 36 | nav: InternPool.Nav.Index, | |
| 37 | name: []const u8, | |
| 38 | cc: std.builtin.CallingConvention, | |
| 39 | }; | |
| 28 | 40 | |
| 29 | 41 | pub fn createEmpty( |
| 30 | 42 | arena: Allocator, |
| ... | ... | @@ -32,7 +44,6 @@ pub fn createEmpty( |
| 32 | 44 | emit: Path, |
| 33 | 45 | options: link.File.OpenOptions, |
| 34 | 46 | ) !*Linker { |
| 35 | const gpa = comp.gpa; | |
| 36 | 47 | const io = comp.io; |
| 37 | 48 | const target = &comp.root_mod.resolved_target.result; |
| 38 | 49 | |
| ... | ... | @@ -61,21 +72,6 @@ pub fn createEmpty( |
| 61 | 72 | .file = null, |
| 62 | 73 | .build_id = options.build_id, |
| 63 | 74 | }, |
| 64 | .module = .{ | |
| 65 | .gpa = gpa, | |
| 66 | .arena = arena, | |
| 67 | .zcu = comp.zcu.?, | |
| 68 | }, | |
| 69 | .cg = .{ | |
| 70 | // These fields are populated in generate() | |
| 71 | .pt = undefined, | |
| 72 | .air = undefined, | |
| 73 | .liveness = undefined, | |
| 74 | .owner_nav = undefined, | |
| 75 | .module = undefined, | |
| 76 | .control_flow = .{ .structured = .{} }, | |
| 77 | .base_line = undefined, | |
| 78 | }, | |
| 79 | 75 | }; |
| 80 | 76 | errdefer linker.deinit(); |
| 81 | 77 | |
| ... | ... | @@ -97,70 +93,55 @@ pub fn open( |
| 97 | 93 | } |
| 98 | 94 | |
| 99 | 95 | pub fn deinit(linker: *Linker) void { |
| 100 | linker.cg.deinit(); | |
| 101 | linker.module.deinit(); | |
| 102 | } | |
| 103 | ||
| 104 | fn generate( | |
| 105 | linker: *Linker, | |
| 106 | pt: Zcu.PerThread, | |
| 107 | nav_index: InternPool.Nav.Index, | |
| 108 | air: Air, | |
| 109 | liveness: Air.Liveness, | |
| 110 | do_codegen: bool, | |
| 111 | ) !void { | |
| 112 | const zcu = pt.zcu; | |
| 113 | const gpa = zcu.gpa; | |
| 114 | const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg; | |
| 115 | ||
| 116 | linker.cg.control_flow.deinit(gpa); | |
| 117 | linker.cg.args.clearRetainingCapacity(); | |
| 118 | linker.cg.inst_results.clearRetainingCapacity(); | |
| 119 | linker.cg.id_scratch.clearRetainingCapacity(); | |
| 120 | linker.cg.prologue.reset(); | |
| 121 | linker.cg.body.reset(); | |
| 122 | ||
| 123 | linker.cg = .{ | |
| 124 | .pt = pt, | |
| 125 | .air = air, | |
| 126 | .liveness = liveness, | |
| 127 | .owner_nav = nav_index, | |
| 128 | .module = &linker.module, | |
| 129 | .control_flow = switch (structured_cfg) { | |
| 130 | true => .{ .structured = .{} }, | |
| 131 | false => .{ .unstructured = .{} }, | |
| 132 | }, | |
| 133 | .base_line = zcu.navSrcLine(nav_index), | |
| 134 | ||
| 135 | .args = linker.cg.args, | |
| 136 | .inst_results = linker.cg.inst_results, | |
| 137 | .id_scratch = linker.cg.id_scratch, | |
| 138 | .prologue = linker.cg.prologue, | |
| 139 | .body = linker.cg.body, | |
| 140 | }; | |
| 141 | ||
| 142 | linker.cg.genNav(do_codegen) catch |err| switch (err) { | |
| 143 | error.AlreadyReported => return, | |
| 144 | else => |e| return e, | |
| 145 | }; | |
| 96 | const gpa = linker.base.comp.gpa; | |
| 97 | for (linker.fragments.values()) |*mir| { | |
| 98 | mir.deinit(gpa); | |
| 99 | } | |
| 100 | linker.fragments.deinit(gpa); | |
| 101 | linker.pending_navs.deinit(gpa); | |
| 102 | linker.entry_points.deinit(gpa); | |
| 146 | 103 | } |
| 147 | 104 | |
| 148 | 105 | pub fn updateFunc( |
| 149 | 106 | linker: *Linker, |
| 150 | 107 | pt: Zcu.PerThread, |
| 151 | 108 | func_index: InternPool.Index, |
| 152 | air: *const Air, | |
| 153 | liveness: *const ?Air.Liveness, | |
| 109 | mir: *codegen.AnyMir, | |
| 154 | 110 | ) !void { |
| 111 | const gpa = linker.base.comp.gpa; | |
| 155 | 112 | const nav = pt.zcu.funcInfo(func_index).owner_nav; |
| 156 | // TODO: Separate types for generating decls and functions? | |
| 157 | try linker.generate(pt, nav, air.*, liveness.*.?, true); | |
| 113 | ||
| 114 | if (linker.fragments.getPtr(nav)) |existing| { | |
| 115 | existing.deinit(gpa); | |
| 116 | } | |
| 117 | ||
| 118 | try linker.fragments.put(gpa, nav, mir.spirv); | |
| 119 | mir.spirv = .{ | |
| 120 | .extended_instruction_set = &.{}, | |
| 121 | .globals = &.{}, | |
| 122 | .functions = &.{}, | |
| 123 | .annotations = &.{}, | |
| 124 | .debug_names = &.{}, | |
| 125 | .debug_strings = &.{}, | |
| 126 | .execution_modes = &.{}, | |
| 127 | .id_bound = 0, | |
| 128 | .owner_nav = mir.spirv.owner_nav, | |
| 129 | .kind = mir.spirv.kind, | |
| 130 | .decl_result_id = .none, | |
| 131 | .nav_refs = &.{}, | |
| 132 | .uav_refs = &.{}, | |
| 133 | .decl_deps = &.{}, | |
| 134 | .internal_globals = &.{}, | |
| 135 | .entry_points = &.{}, | |
| 136 | }; | |
| 158 | 137 | } |
| 159 | 138 | |
| 160 | 139 | pub fn updateNav(linker: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.Error!void { |
| 161 | 140 | const ip = &pt.zcu.intern_pool; |
| 162 | log.debug("lowering nav {f}({d})", .{ ip.getNav(nav).fqn.fmt(ip), nav }); | |
| 163 | try linker.generate(pt, nav, undefined, undefined, false); | |
| 141 | log.debug("deferring nav {f}({d}) to flush", .{ ip.getNav(nav).fqn.fmt(ip), nav }); | |
| 142 | ||
| 143 | const gpa = linker.base.comp.gpa; | |
| 144 | linker.pending_navs.append(gpa, nav) catch return error.OutOfMemory; | |
| 164 | 145 | } |
| 165 | 146 | |
| 166 | 147 | pub fn updateExports( |
| ... | ... | @@ -171,6 +152,7 @@ pub fn updateExports( |
| 171 | 152 | ) !void { |
| 172 | 153 | const zcu = pt.zcu; |
| 173 | 154 | const ip = &zcu.intern_pool; |
| 155 | const gpa = linker.base.comp.gpa; | |
| 174 | 156 | const nav_index = switch (exported) { |
| 175 | 157 | .nav => |nav| nav, |
| 176 | 158 | .uav => |uav| { |
| ... | ... | @@ -180,21 +162,18 @@ pub fn updateExports( |
| 180 | 162 | }; |
| 181 | 163 | const nav_ty = ip.getNav(nav_index).resolved.?.type; |
| 182 | 164 | if (ip.isFunctionType(nav_ty)) { |
| 183 | const spv_decl_index = try linker.module.resolveNav(ip, nav_index); | |
| 184 | 165 | const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu); |
| 185 | 166 | if (cc == .spirv_device) return; |
| 186 | 167 | |
| 187 | 168 | for (export_indices) |export_idx| { |
| 188 | 169 | const exp = export_idx.ptr(zcu); |
| 189 | try linker.module.declareEntryPoint( | |
| 190 | spv_decl_index, | |
| 191 | exp.opts.name.toSlice(ip), | |
| 192 | cc, | |
| 193 | ); | |
| 170 | try linker.entry_points.append(gpa, .{ | |
| 171 | .nav = nav_index, | |
| 172 | .name = exp.opts.name.toSlice(ip), | |
| 173 | .cc = cc, | |
| 174 | }); | |
| 194 | 175 | } |
| 195 | 176 | } |
| 196 | ||
| 197 | // TODO: Export regular functions, variables, etc using Linkage attributes. | |
| 198 | 177 | } |
| 199 | 178 | |
| 200 | 179 | pub fn flush( |
| ... | ... | @@ -203,11 +182,6 @@ pub fn flush( |
| 203 | 182 | tid: Zcu.PerThread.Id, |
| 204 | 183 | prog_node: std.Progress.Node, |
| 205 | 184 | ) link.Error!void { |
| 206 | // The goal is to never use this because it's only needed if we need to | |
| 207 | // write to InternPool, but flush is too late to be writing to the | |
| 208 | // InternPool. | |
| 209 | _ = tid; | |
| 210 | ||
| 211 | 185 | const tracy = trace(@src()); |
| 212 | 186 | defer tracy.end(); |
| 213 | 187 | |
| ... | ... | @@ -219,19 +193,334 @@ pub fn flush( |
| 219 | 193 | const gpa = comp.gpa; |
| 220 | 194 | const io = comp.io; |
| 221 | 195 | |
| 222 | // We need to export the list of error names somewhere so that we can pretty-print them in the | |
| 223 | // executor. This is not really an important thing though, so we can just dump it in any old | |
| 224 | // nonsemantic instruction. For now, just put it in OpSourceExtension with a special name. | |
| 225 | var error_info: std.Io.Writer.Allocating = .init(linker.module.gpa); | |
| 226 | defer error_info.deinit(); | |
| 196 | const zcu = comp.zcu.?; | |
| 197 | const active = zcu.activate(tid); | |
| 198 | defer active.deactivate(); | |
| 199 | const pt = active.pt; | |
| 200 | for (linker.pending_navs.items) |nav| { | |
| 201 | if (linker.fragments.contains(nav)) continue; | |
| 202 | ||
| 203 | const mir = CodeGen.generateNav(pt, nav) catch |err| switch (err) { | |
| 204 | error.OutOfMemory => return error.OutOfMemory, | |
| 205 | error.AlreadyReported => continue, | |
| 206 | error.Canceled => return error.Canceled, | |
| 207 | }; | |
| 208 | ||
| 209 | linker.fragments.put(gpa, nav, mir) catch return error.OutOfMemory; | |
| 210 | } | |
| 211 | linker.pending_navs.clearRetainingCapacity(); | |
| 212 | ||
| 213 | const merged = mergeFragments(linker, gpa, arena) catch |err| switch (err) { | |
| 214 | error.OutOfMemory => return error.OutOfMemory, | |
| 215 | }; | |
| 216 | ||
| 217 | var binary = linkModule(arena, merged.words, merged.id_bound, sub_prog_node) catch |err| switch (err) { | |
| 218 | error.OutOfMemory => |e| return e, | |
| 219 | else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}), | |
| 220 | }; | |
| 221 | defer binary.deinit(arena); | |
| 222 | ||
| 223 | const header = [_]Word{ | |
| 224 | spec.magic_number, | |
| 225 | merged.version.toWord(), | |
| 226 | merged.generator_id, | |
| 227 | binary.id_bound, | |
| 228 | 0, | |
| 229 | }; | |
| 230 | ||
| 231 | linker.base.file.?.writeStreamingAll(io, @ptrCast(&header)) catch |err| | |
| 232 | return diags.fail("failed to write: {t}", .{err}); | |
| 233 | linker.base.file.?.writeStreamingAll(io, @ptrCast(binary.instructions)) catch |err| | |
| 234 | return diags.fail("failed to write: {t}", .{err}); | |
| 235 | } | |
| 236 | ||
| 237 | fn linkModule(arena: Allocator, words: []const Word, id_bound: u32, progress: std.Progress.Node) !BinaryModule { | |
| 238 | var parser = try BinaryModule.Parser.init(arena); | |
| 239 | defer parser.deinit(); | |
| 240 | var binary = try parser.initFromWords(words, id_bound); | |
| 241 | try prune_unused.run(&parser, &binary); | |
| 242 | try dedup_types.run(&parser, &binary); | |
| 243 | try lower_invocation_globals.run(&parser, &binary, progress); | |
| 244 | return binary; | |
| 245 | } | |
| 246 | ||
| 247 | fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOfMemory}!MergedModule { | |
| 248 | const comp = linker.base.comp; | |
| 249 | const zcu = comp.zcu.?; | |
| 250 | const target = zcu.getTarget(); | |
| 251 | ||
| 252 | var next_id: Word = 1; | |
| 253 | ||
| 254 | var nav_final_ids: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Id) = .empty; | |
| 255 | defer nav_final_ids.deinit(gpa); | |
| 256 | ||
| 257 | var uav_final_ids: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Id) = .empty; | |
| 258 | defer uav_final_ids.deinit(gpa); | |
| 259 | ||
| 260 | var frag_infos: std.ArrayList(FragmentInfo) = .empty; | |
| 261 | defer frag_infos.deinit(gpa); | |
| 262 | try frag_infos.ensureTotalCapacity(gpa, @intCast(linker.fragments.count())); | |
| 263 | ||
| 264 | for (linker.fragments.keys(), linker.fragments.values()) |nav, *mir| { | |
| 265 | const id_offset = next_id - 1; | |
| 266 | frag_infos.appendAssumeCapacity(.{ | |
| 267 | .id_offset = id_offset, | |
| 268 | }); | |
| 269 | ||
| 270 | if (mir.decl_result_id != .none) { | |
| 271 | try nav_final_ids.put(gpa, nav, @enumFromInt(@intFromEnum(mir.decl_result_id) + id_offset)); | |
| 272 | } | |
| 273 | ||
| 274 | next_id += mir.id_bound - 1; | |
| 275 | } | |
| 276 | ||
| 277 | for (linker.fragments.values(), frag_infos.items) |*mir, frag_info| { | |
| 278 | for (mir.nav_refs) |ref| { | |
| 279 | if (!nav_final_ids.contains(ref.nav)) { | |
| 280 | try nav_final_ids.put(gpa, ref.nav, @enumFromInt(@intFromEnum(ref.local_id) + frag_info.id_offset)); | |
| 281 | } | |
| 282 | } | |
| 283 | ||
| 284 | for (mir.uav_refs) |ref| { | |
| 285 | const key = .{ ref.val, ref.storage_class }; | |
| 286 | if (!uav_final_ids.contains(key)) { | |
| 287 | try uav_final_ids.put(gpa, key, @enumFromInt(@intFromEnum(ref.local_id) + frag_info.id_offset)); | |
| 288 | } | |
| 289 | } | |
| 290 | } | |
| 291 | ||
| 292 | var parser = BinaryModule.Parser.init(gpa) catch return error.OutOfMemory; | |
| 293 | defer parser.deinit(); | |
| 294 | var ext_inst_section = Section{}; | |
| 295 | defer ext_inst_section.deinit(gpa); | |
| 296 | var globals_section = Section{}; | |
| 297 | defer globals_section.deinit(gpa); | |
| 298 | var functions_section = Section{}; | |
| 299 | defer functions_section.deinit(gpa); | |
| 300 | var annotations_section = Section{}; | |
| 301 | defer annotations_section.deinit(gpa); | |
| 302 | var debug_names_section = Section{}; | |
| 303 | defer debug_names_section.deinit(gpa); | |
| 304 | var debug_strings_section = Section{}; | |
| 305 | defer debug_strings_section.deinit(gpa); | |
| 306 | var execution_modes_section = Section{}; | |
| 307 | defer execution_modes_section.deinit(gpa); | |
| 308 | ||
| 309 | for (linker.fragments.values(), frag_infos.items) |*mir, frag_info| { | |
| 310 | var id_remap: std.AutoHashMapUnmanaged(Id, Id) = .empty; | |
| 311 | defer id_remap.deinit(gpa); | |
| 312 | ||
| 313 | for (mir.nav_refs) |ref| { | |
| 314 | if (nav_final_ids.get(ref.nav)) |final_id| { | |
| 315 | try id_remap.put(gpa, ref.local_id, final_id); | |
| 316 | } | |
| 317 | } | |
| 318 | ||
| 319 | for (mir.uav_refs) |ref| { | |
| 320 | const key = .{ ref.val, ref.storage_class }; | |
| 321 | if (uav_final_ids.get(key)) |final_id| { | |
| 322 | try id_remap.put(gpa, ref.local_id, final_id); | |
| 323 | } | |
| 324 | } | |
| 325 | ||
| 326 | try remapAndAppend(gpa, &ext_inst_section, mir.extended_instruction_set, frag_info.id_offset, &id_remap, &parser); | |
| 327 | try remapAndAppend(gpa, &globals_section, mir.globals, frag_info.id_offset, &id_remap, &parser); | |
| 328 | try remapAndAppend(gpa, &functions_section, mir.functions, frag_info.id_offset, &id_remap, &parser); | |
| 329 | try remapAndAppend(gpa, &annotations_section, mir.annotations, frag_info.id_offset, &id_remap, &parser); | |
| 330 | try remapAndAppend(gpa, &debug_names_section, mir.debug_names, frag_info.id_offset, &id_remap, &parser); | |
| 331 | try remapAndAppend(gpa, &debug_strings_section, mir.debug_strings, frag_info.id_offset, &id_remap, &parser); | |
| 332 | try remapAndAppend(gpa, &execution_modes_section, mir.execution_modes, frag_info.id_offset, &id_remap, &parser); | |
| 333 | ||
| 334 | for (mir.entry_points) |ep| { | |
| 335 | try linker.entry_points.append(gpa, .{ | |
| 336 | .nav = mir.owner_nav, | |
| 337 | .name = ep.name, | |
| 338 | .cc = ep.cc, | |
| 339 | }); | |
| 340 | } | |
| 341 | } | |
| 342 | ||
| 343 | var capabilities_section = Section{}; | |
| 344 | defer capabilities_section.deinit(gpa); | |
| 345 | var extensions_section = Section{}; | |
| 346 | defer extensions_section.deinit(gpa); | |
| 347 | var memory_model_section = Section{}; | |
| 348 | defer memory_model_section.deinit(gpa); | |
| 349 | var entry_points_section = Section{}; | |
| 350 | defer entry_points_section.deinit(gpa); | |
| 351 | ||
| 352 | const cap_pairs = [_]struct { cap: spec.Capability, ext: ?[]const u8 }{ | |
| 353 | .{ .cap = .int8, .ext = null }, | |
| 354 | .{ .cap = .int16, .ext = null }, | |
| 355 | }; | |
| 356 | for (cap_pairs) |pair| { | |
| 357 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = pair.cap }); | |
| 358 | if (pair.ext) |ext| { | |
| 359 | try extensions_section.emit(gpa, .OpExtension, .{ .name = ext }); | |
| 360 | } | |
| 361 | } | |
| 362 | ||
| 363 | switch (target.os.tag) { | |
| 364 | .opengl => { | |
| 365 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .shader }); | |
| 366 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .matrix }); | |
| 367 | }, | |
| 368 | .vulkan => { | |
| 369 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .shader }); | |
| 370 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .matrix }); | |
| 371 | if (target.cpu.arch == .spirv64) { | |
| 372 | try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_physical_storage_buffer" }); | |
| 373 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .physical_storage_buffer_addresses }); | |
| 374 | } | |
| 375 | }, | |
| 376 | .opencl, .amdhsa => { | |
| 377 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .kernel }); | |
| 378 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .addresses }); | |
| 379 | }, | |
| 380 | else => unreachable, | |
| 381 | } | |
| 382 | if (target.cpu.arch == .spirv64) | |
| 383 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .int64 }); | |
| 384 | if (target.cpu.has(.spirv, .int64)) | |
| 385 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .int64 }); | |
| 386 | if (target.cpu.has(.spirv, .float16)) { | |
| 387 | if (target.os.tag == .opencl) try extensions_section.emit(gpa, .OpExtension, .{ .name = "cl_khr_fp16" }); | |
| 388 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .float16 }); | |
| 389 | } | |
| 390 | if (target.cpu.has(.spirv, .float64)) | |
| 391 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .float64 }); | |
| 392 | if (target.cpu.has(.spirv, .generic_pointer)) | |
| 393 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .generic_pointer }); | |
| 394 | if (target.cpu.has(.spirv, .vector16)) | |
| 395 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .vector16 }); | |
| 396 | if (target.cpu.has(.spirv, .storage_push_constant16)) { | |
| 397 | try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_16bit_storage" }); | |
| 398 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .storage_push_constant16 }); | |
| 399 | } | |
| 400 | if (target.cpu.has(.spirv, .arbitrary_precision_integers)) { | |
| 401 | try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_INTEL_arbitrary_precision_integers" }); | |
| 402 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .arbitrary_precision_integers_intel }); | |
| 403 | } | |
| 404 | if (target.cpu.has(.spirv, .variable_pointers)) { | |
| 405 | try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_variable_pointers" }); | |
| 406 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .variable_pointers_storage_buffer }); | |
| 407 | try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .variable_pointers }); | |
| 408 | } | |
| 409 | ||
| 410 | const addressing_model: spec.AddressingModel = switch (target.os.tag) { | |
| 411 | .opengl => .logical, | |
| 412 | .vulkan => if (target.cpu.arch == .spirv32) .logical else .physical_storage_buffer64, | |
| 413 | .opencl => if (target.cpu.arch == .spirv32) .physical32 else .physical64, | |
| 414 | .amdhsa => .physical64, | |
| 415 | else => unreachable, | |
| 416 | }; | |
| 417 | try memory_model_section.emit(gpa, .OpMemoryModel, .{ | |
| 418 | .addressing_model = addressing_model, | |
| 419 | .memory_model = switch (target.os.tag) { | |
| 420 | .opencl => .open_cl, | |
| 421 | .vulkan, .opengl => .glsl450, | |
| 422 | else => unreachable, | |
| 423 | }, | |
| 424 | }); | |
| 425 | ||
| 426 | for (linker.entry_points.items) |ep| { | |
| 427 | const final_id = nav_final_ids.get(ep.nav) orelse continue; | |
| 428 | ||
| 429 | var interface: std.ArrayList(Id) = .empty; | |
| 430 | defer interface.deinit(gpa); | |
| 431 | ||
| 432 | var visited: std.AutoHashMapUnmanaged(InternPool.Nav.Index, void) = .empty; | |
| 433 | defer visited.deinit(gpa); | |
| 227 | 434 | |
| 435 | try collectEntryPointInterface(linker, ep.nav, &interface, &visited, &nav_final_ids, &uav_final_ids, &frag_infos, gpa); | |
| 436 | ||
| 437 | const exec_model: spec.ExecutionModel = switch (target.os.tag) { | |
| 438 | .vulkan, .opengl => switch (ep.cc) { | |
| 439 | .spirv_vertex => .vertex, | |
| 440 | .spirv_fragment => .fragment, | |
| 441 | .spirv_kernel => .gl_compute, | |
| 442 | .spirv_task => .task_ext, | |
| 443 | .spirv_mesh => .mesh_ext, | |
| 444 | .spirv_device => continue, | |
| 445 | else => unreachable, | |
| 446 | }, | |
| 447 | .opencl => switch (ep.cc) { | |
| 448 | .spirv_kernel => .kernel, | |
| 449 | .spirv_device => continue, | |
| 450 | else => unreachable, | |
| 451 | }, | |
| 452 | else => unreachable, | |
| 453 | }; | |
| 454 | ||
| 455 | try entry_points_section.emit(gpa, .OpEntryPoint, .{ | |
| 456 | .execution_model = exec_model, | |
| 457 | .entry_point = final_id, | |
| 458 | .name = ep.name, | |
| 459 | .interface = interface.items, | |
| 460 | }); | |
| 461 | ||
| 462 | switch (ep.cc) { | |
| 463 | .spirv_kernel, .spirv_task => |kernel| { | |
| 464 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | |
| 465 | .entry_point = final_id, | |
| 466 | .mode = .{ .local_size = .{ | |
| 467 | .x_size = kernel.x, | |
| 468 | .y_size = kernel.y, | |
| 469 | .z_size = kernel.z, | |
| 470 | } }, | |
| 471 | }); | |
| 472 | }, | |
| 473 | .spirv_fragment => |fragment| { | |
| 474 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | |
| 475 | .entry_point = final_id, | |
| 476 | .mode = if (target.os.tag == .vulkan) .origin_upper_left else .origin_lower_left, | |
| 477 | }); | |
| 478 | if (fragment.pixel_centered_integer) { | |
| 479 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | |
| 480 | .entry_point = final_id, | |
| 481 | .mode = .pixel_center_integer, | |
| 482 | }); | |
| 483 | } | |
| 484 | const exec_mode: ?spec.ExecutionMode.Extended = switch (fragment.depth_assumption) { | |
| 485 | .none => null, | |
| 486 | .greater => .depth_greater, | |
| 487 | .less => .depth_less, | |
| 488 | .unchanged => .depth_unchanged, | |
| 489 | }; | |
| 490 | if (exec_mode) |mode| { | |
| 491 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | |
| 492 | .entry_point = final_id, | |
| 493 | .mode = mode, | |
| 494 | }); | |
| 495 | } | |
| 496 | }, | |
| 497 | .spirv_mesh => |mesh| { | |
| 498 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | |
| 499 | .entry_point = final_id, | |
| 500 | .mode = .{ .output_vertices = .{ .vertex_count = mesh.max_vertices } }, | |
| 501 | }); | |
| 502 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | |
| 503 | .entry_point = final_id, | |
| 504 | .mode = .{ .output_primitives_ext = .{ .primitive_count = mesh.max_primitives } }, | |
| 505 | }); | |
| 506 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | |
| 507 | .entry_point = final_id, | |
| 508 | .mode = switch (mesh.stage_output) { | |
| 509 | .output_points => .output_points, | |
| 510 | .output_lines => .output_lines_ext, | |
| 511 | .output_triangles => .output_triangles_ext, | |
| 512 | }, | |
| 513 | }); | |
| 514 | }, | |
| 515 | else => {}, | |
| 516 | } | |
| 517 | } | |
| 518 | ||
| 519 | const ip = &zcu.intern_pool; | |
| 520 | var error_info: std.Io.Writer.Allocating = .init(gpa); | |
| 521 | defer error_info.deinit(); | |
| 228 | 522 | error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory; |
| 229 | const ip = &linker.base.comp.zcu.?.intern_pool; | |
| 230 | 523 | for (ip.global_error_set.getNamesFromMainThread()) |name| { |
| 231 | // Errors can contain pretty much any character - to encode them in a string we must escape | |
| 232 | // them somehow. Easiest here is to use some established scheme, one which also preseves the | |
| 233 | // name if it contains no strange characters is nice for debugging. URI encoding fits the bill. | |
| 234 | // We're using : as separator, which is a reserved character. | |
| 235 | 524 | error_info.writer.writeByte(':') catch return error.OutOfMemory; |
| 236 | 525 | std.Uri.Component.percentEncode( |
| 237 | 526 | &error_info.writer, |
| ... | ... | @@ -246,27 +535,249 @@ pub fn flush( |
| 246 | 535 | }.isValidChar, |
| 247 | 536 | ) catch return error.OutOfMemory; |
| 248 | 537 | } |
| 249 | try linker.module.sections.debug_strings.emit(gpa, .OpSourceExtension, .{ | |
| 538 | try debug_strings_section.emit(gpa, .OpSourceExtension, .{ | |
| 250 | 539 | .extension = error_info.written(), |
| 251 | 540 | }); |
| 252 | 541 | |
| 253 | const module = try linker.module.finalize(arena); | |
| 254 | errdefer arena.free(module); | |
| 542 | const zig_version = @import("builtin").zig_version; | |
| 543 | const zig_spirv_compiler_version = comptime (zig_version.major << 12) | (zig_version.minor << 7) | zig_version.patch; | |
| 544 | try debug_strings_section.emit(gpa, .OpSource, .{ | |
| 545 | .source_language = .zig, | |
| 546 | .version = zig_spirv_compiler_version, | |
| 547 | .file = null, | |
| 548 | .source = null, | |
| 549 | }); | |
| 255 | 550 | |
| 256 | const linked_module = linkModule(arena, module, sub_prog_node) catch |err| switch (err) { | |
| 257 | error.OutOfMemory => |e| return e, | |
| 258 | else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}), | |
| 551 | const version: spec.Version = .{ | |
| 552 | .major = 1, | |
| 553 | .minor = blk: { | |
| 554 | if (target.cpu.has(.spirv, .v1_6)) break :blk 6; | |
| 555 | if (target.cpu.has(.spirv, .v1_5)) break :blk 5; | |
| 556 | if (target.cpu.has(.spirv, .v1_4)) break :blk 4; | |
| 557 | if (target.cpu.has(.spirv, .v1_3)) break :blk 3; | |
| 558 | if (target.cpu.has(.spirv, .v1_2)) break :blk 2; | |
| 559 | if (target.cpu.has(.spirv, .v1_1)) break :blk 1; | |
| 560 | break :blk 0; | |
| 561 | }, | |
| 259 | 562 | }; |
| 260 | 563 | |
| 261 | // TODO endianness bug. use file writer and call writeSliceEndian instead | |
| 262 | linker.base.file.?.writeStreamingAll(io, @ptrCast(linked_module)) catch |err| | |
| 263 | return diags.fail("failed to write: {t}", .{err}); | |
| 564 | const generator_id: u32 = (spec.zig_generator_id << 16) | zig_spirv_compiler_version; | |
| 565 | ||
| 566 | const buffers = &[_][]const Word{ | |
| 567 | capabilities_section.toWords(), | |
| 568 | extensions_section.toWords(), | |
| 569 | ext_inst_section.toWords(), | |
| 570 | memory_model_section.toWords(), | |
| 571 | entry_points_section.toWords(), | |
| 572 | execution_modes_section.toWords(), | |
| 573 | debug_strings_section.toWords(), | |
| 574 | debug_names_section.toWords(), | |
| 575 | annotations_section.toWords(), | |
| 576 | globals_section.toWords(), | |
| 577 | functions_section.toWords(), | |
| 578 | }; | |
| 579 | ||
| 580 | var total_size: usize = 0; | |
| 581 | for (buffers) |buffer| { | |
| 582 | total_size += buffer.len; | |
| 583 | } | |
| 584 | const result = try arena.alloc(Word, total_size); | |
| 585 | ||
| 586 | var offset: usize = 0; | |
| 587 | for (buffers) |buffer| { | |
| 588 | @memcpy(result[offset..][0..buffer.len], buffer); | |
| 589 | offset += buffer.len; | |
| 590 | } | |
| 591 | ||
| 592 | return .{ | |
| 593 | .words = result, | |
| 594 | .id_bound = next_id, | |
| 595 | .version = version, | |
| 596 | .generator_id = generator_id, | |
| 597 | }; | |
| 264 | 598 | } |
| 265 | 599 | |
| 266 | fn linkModule(arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word { | |
| 267 | var parser = try BinaryModule.Parser.init(arena); | |
| 268 | defer parser.deinit(); | |
| 269 | var binary = try parser.parse(module); | |
| 270 | try lower_invocation_globals.run(&parser, &binary, progress); | |
| 271 | return binary.finalize(arena); | |
| 600 | const MergedModule = struct { | |
| 601 | words: []const Word, | |
| 602 | id_bound: Word, | |
| 603 | version: spec.Version, | |
| 604 | generator_id: u32, | |
| 605 | }; | |
| 606 | ||
| 607 | const FragmentInfo = struct { | |
| 608 | id_offset: Word, | |
| 609 | }; | |
| 610 | ||
| 611 | fn collectEntryPointInterface( | |
| 612 | linker: *Linker, | |
| 613 | nav: InternPool.Nav.Index, | |
| 614 | interface: *std.ArrayList(Id), | |
| 615 | visited: *std.AutoHashMapUnmanaged(InternPool.Nav.Index, void), | |
| 616 | nav_final_ids: *const std.AutoHashMapUnmanaged(InternPool.Nav.Index, Id), | |
| 617 | uav_final_ids: *const std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Id), | |
| 618 | frag_infos: *const std.ArrayList(FragmentInfo), | |
| 619 | gpa: Allocator, | |
| 620 | ) error{OutOfMemory}!void { | |
| 621 | const visited_gop = try visited.getOrPut(gpa, nav); | |
| 622 | if (visited_gop.found_existing) return; | |
| 623 | ||
| 624 | const frag_index = linker.fragments.getIndex(nav) orelse return; | |
| 625 | const mir = &linker.fragments.values()[frag_index]; | |
| 626 | const id_offset = frag_infos.items[frag_index].id_offset; | |
| 627 | ||
| 628 | if (mir.kind == .global) { | |
| 629 | if (nav_final_ids.get(nav)) |final_id| { | |
| 630 | try interface.append(gpa, final_id); | |
| 631 | } | |
| 632 | } | |
| 633 | ||
| 634 | for (mir.uav_refs) |ref| { | |
| 635 | if (ref.kind == .global) { | |
| 636 | if (uav_final_ids.get(.{ ref.val, ref.storage_class })) |final_id| { | |
| 637 | try interface.append(gpa, final_id); | |
| 638 | } | |
| 639 | } | |
| 640 | } | |
| 641 | ||
| 642 | for (mir.internal_globals) |local_id| { | |
| 643 | const global_id: Id = @enumFromInt(@intFromEnum(local_id) + id_offset); | |
| 644 | try interface.append(gpa, global_id); | |
| 645 | } | |
| 646 | ||
| 647 | for (mir.decl_deps) |dep| { | |
| 648 | try collectEntryPointInterface(linker, dep.nav, interface, visited, nav_final_ids, uav_final_ids, frag_infos, gpa); | |
| 649 | } | |
| 650 | ||
| 651 | for (mir.nav_refs) |ref| { | |
| 652 | try collectEntryPointInterface(linker, ref.nav, interface, visited, nav_final_ids, uav_final_ids, frag_infos, gpa); | |
| 653 | } | |
| 654 | } | |
| 655 | ||
| 656 | fn remapAndAppend( | |
| 657 | gpa: Allocator, | |
| 658 | dest: *Section, | |
| 659 | words: []const Word, | |
| 660 | id_offset: Word, | |
| 661 | id_remap: *const std.AutoHashMapUnmanaged(Id, Id), | |
| 662 | parser: *BinaryModule.Parser, | |
| 663 | ) error{OutOfMemory}!void { | |
| 664 | if (words.len == 0) return; | |
| 665 | ||
| 666 | try dest.instructions.ensureUnusedCapacity(gpa, words.len); | |
| 667 | ||
| 668 | var iter = BinaryModule.Instruction.Iterator.init(words, 0); | |
| 669 | while (iter.next()) |inst| { | |
| 670 | const dest_start = dest.instructions.items.len; | |
| 671 | const inst_words = words[inst.offset..][0..((words[inst.offset] >> 16))]; | |
| 672 | dest.instructions.appendSliceAssumeCapacity(inst_words); | |
| 673 | const inst_slice = dest.instructions.items[dest_start..][0..inst_words.len]; | |
| 674 | ||
| 675 | const inst_spec = parser.getInstSpec(inst.opcode) orelse continue; | |
| 676 | var offset: usize = 0; | |
| 677 | for (inst_spec.operands) |operand| { | |
| 678 | const cat = operand.kind.category(); | |
| 679 | switch (operand.quantifier) { | |
| 680 | .required => { | |
| 681 | if (offset >= inst.operands.len) break; | |
| 682 | if (cat == .id) { | |
| 683 | remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); | |
| 684 | offset += 1; | |
| 685 | } else if (cat == .literal) { | |
| 686 | offset += operandLiteralWordCount(operand.kind, inst, offset); | |
| 687 | } else if (cat == .composite) { | |
| 688 | remapCompositeOperand(operand.kind, inst_slice, offset, id_offset, id_remap); | |
| 689 | offset += 2; | |
| 690 | } else { | |
| 691 | offset += 1; | |
| 692 | } | |
| 693 | }, | |
| 694 | .optional => { | |
| 695 | if (offset >= inst.operands.len) break; | |
| 696 | if (cat == .id) { | |
| 697 | remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); | |
| 698 | offset += 1; | |
| 699 | } else if (cat == .literal) { | |
| 700 | offset += operandLiteralWordCount(operand.kind, inst, offset); | |
| 701 | } else { | |
| 702 | offset += 1; | |
| 703 | } | |
| 704 | }, | |
| 705 | .variadic => { | |
| 706 | while (offset < inst.operands.len) { | |
| 707 | if (cat == .id) { | |
| 708 | remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); | |
| 709 | offset += 1; | |
| 710 | } else if (cat == .literal) { | |
| 711 | offset += operandLiteralWordCount(operand.kind, inst, offset); | |
| 712 | } else if (cat == .composite) { | |
| 713 | if (offset + 1 < inst.operands.len) { | |
| 714 | remapCompositeOperand(operand.kind, inst_slice, offset, id_offset, id_remap); | |
| 715 | } | |
| 716 | offset += 2; | |
| 717 | } else { | |
| 718 | offset += 1; | |
| 719 | } | |
| 720 | } | |
| 721 | }, | |
| 722 | } | |
| 723 | } | |
| 724 | } | |
| 725 | } | |
| 726 | ||
| 727 | fn remapCompositeOperand( | |
| 728 | kind: spec.OperandKind, | |
| 729 | inst_slice: []Word, | |
| 730 | offset: usize, | |
| 731 | id_offset: Word, | |
| 732 | id_remap: *const std.AutoHashMapUnmanaged(Id, Id), | |
| 733 | ) void { | |
| 734 | switch (kind) { | |
| 735 | .pair_literal_integer_id_ref => { | |
| 736 | remapSingleId(&inst_slice[1 + offset + 1], id_offset, id_remap); | |
| 737 | }, | |
| 738 | .pair_id_ref_literal_integer => { | |
| 739 | remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); | |
| 740 | }, | |
| 741 | .pair_id_ref_id_ref => { | |
| 742 | remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); | |
| 743 | remapSingleId(&inst_slice[1 + offset + 1], id_offset, id_remap); | |
| 744 | }, | |
| 745 | else => {}, | |
| 746 | } | |
| 747 | } | |
| 748 | ||
| 749 | fn operandLiteralWordCount(kind: spec.OperandKind, inst: BinaryModule.Instruction, offset: usize) usize { | |
| 750 | return switch (kind) { | |
| 751 | .literal_integer, .literal_float => 1, | |
| 752 | .literal_string => blk: { | |
| 753 | var count: usize = 0; | |
| 754 | var off = offset; | |
| 755 | while (off < inst.operands.len) { | |
| 756 | const word = inst.operands[off]; | |
| 757 | count += 1; | |
| 758 | off += 1; | |
| 759 | if (word & 0xFF000000 == 0 or | |
| 760 | word & 0x00FF0000 == 0 or | |
| 761 | word & 0x0000FF00 == 0 or | |
| 762 | word & 0x000000FF == 0) | |
| 763 | { | |
| 764 | break; | |
| 765 | } | |
| 766 | } | |
| 767 | break :blk count; | |
| 768 | }, | |
| 769 | .literal_context_dependent_number => inst.operands.len - offset, | |
| 770 | .literal_ext_inst_integer => 1, | |
| 771 | else => 1, | |
| 772 | }; | |
| 773 | } | |
| 774 | ||
| 775 | fn remapSingleId(word: *Word, id_offset: Word, id_remap: *const std.AutoHashMapUnmanaged(Id, Id)) void { | |
| 776 | const id: Id = @enumFromInt(word.*); | |
| 777 | if (id == .none) return; | |
| 778 | if (id_remap.get(id)) |final_id| { | |
| 779 | word.* = @intFromEnum(final_id); | |
| 780 | } else { | |
| 781 | word.* = @intFromEnum(id) + id_offset; | |
| 782 | } | |
| 272 | 783 | } |
src/link/SpirV/BinaryModule.zig+82-199| ... | ... | @@ -11,278 +11,161 @@ const ResultId = spec.Id; |
| 11 | 11 | |
| 12 | 12 | const BinaryModule = @This(); |
| 13 | 13 | |
| 14 | pub const header_words = 5; | |
| 15 | ||
| 16 | /// The module SPIR-V version. | |
| 17 | version: spec.Version, | |
| 18 | ||
| 19 | /// The generator magic number. | |
| 20 | generator_magic: u32, | |
| 21 | ||
| 22 | 14 | /// The result-id bound of this SPIR-V module. |
| 23 | 15 | id_bound: u32, |
| 24 | 16 | |
| 25 | /// The instructions of this module. This does not contain the header. | |
| 17 | /// The instructions of this module (no header). | |
| 26 | 18 | instructions: []const Word, |
| 27 | 19 | |
| 28 | 20 | /// Maps OpExtInstImport result-ids to their InstructionSet. |
| 29 | 21 | ext_inst_map: std.AutoHashMapUnmanaged(ResultId, InstructionSet), |
| 30 | 22 | |
| 31 | /// This map contains the width of arithmetic types (OpTypeInt and | |
| 32 | /// OpTypeFloat). We need this information to correctly parse the operands | |
| 33 | /// of Op(Spec)Constant and OpSwitch. | |
| 23 | /// Width of arithmetic types (OpTypeInt/OpTypeFloat). Needed to correctly | |
| 24 | /// parse operands of Op(Spec)Constant and OpSwitch. | |
| 34 | 25 | arith_type_width: std.AutoHashMapUnmanaged(ResultId, u16), |
| 35 | 26 | |
| 36 | /// The starting offsets of some sections | |
| 37 | sections: struct { | |
| 38 | functions: usize, | |
| 39 | }, | |
| 27 | functions_start: usize, | |
| 40 | 28 | |
| 41 | pub fn deinit(self: *BinaryModule, a: Allocator) void { | |
| 42 | self.ext_inst_map.deinit(a); | |
| 43 | self.arith_type_width.deinit(a); | |
| 44 | self.* = undefined; | |
| 29 | pub fn deinit(bm: *BinaryModule, gpa: Allocator) void { | |
| 30 | bm.ext_inst_map.deinit(gpa); | |
| 31 | bm.arith_type_width.deinit(gpa); | |
| 32 | bm.* = undefined; | |
| 45 | 33 | } |
| 46 | 34 | |
| 47 | pub fn iterateInstructions(self: BinaryModule) Instruction.Iterator { | |
| 48 | return Instruction.Iterator.init(self.instructions, 0); | |
| 35 | pub fn iterateInstructions(bm: BinaryModule) Instruction.Iterator { | |
| 36 | return Instruction.Iterator.init(bm.instructions, 0); | |
| 49 | 37 | } |
| 50 | 38 | |
| 51 | pub fn iterateInstructionsFrom(self: BinaryModule, offset: usize) Instruction.Iterator { | |
| 52 | return Instruction.Iterator.init(self.instructions, offset); | |
| 39 | pub fn iterateInstructionsFrom(bm: BinaryModule, offset: usize) Instruction.Iterator { | |
| 40 | return Instruction.Iterator.init(bm.instructions, offset); | |
| 53 | 41 | } |
| 54 | 42 | |
| 55 | pub fn instructionAt(self: BinaryModule, offset: usize) Instruction { | |
| 56 | var it = self.iterateInstructionsFrom(offset); | |
| 57 | return it.next().?; | |
| 58 | } | |
| 59 | ||
| 60 | pub fn finalize(self: BinaryModule, a: Allocator) ![]Word { | |
| 61 | const result = try a.alloc(Word, 5 + self.instructions.len); | |
| 62 | errdefer a.free(result); | |
| 63 | ||
| 64 | result[0] = spec.magic_number; | |
| 65 | result[1] = @bitCast(self.version); | |
| 66 | result[2] = @bitCast(self.generator_magic); | |
| 67 | result[3] = self.id_bound; | |
| 68 | result[4] = 0; // Schema | |
| 69 | ||
| 70 | @memcpy(result[5..], self.instructions); | |
| 71 | return result; | |
| 72 | } | |
| 73 | ||
| 74 | /// Errors that can be raised when the module is not correct. | |
| 75 | /// Note that the parser doesn't validate SPIR-V modules by a | |
| 76 | /// long shot. It only yields errors that critically prevent | |
| 77 | /// further analysis of the module. | |
| 78 | pub const ParseError = error{ | |
| 79 | /// Raised when the module doesn't start with the SPIR-V magic. | |
| 80 | /// This usually means that the module isn't actually SPIR-V. | |
| 81 | InvalidMagic, | |
| 82 | /// Raised when the module has an invalid "physical" format: | |
| 83 | /// For example when the header is incomplete, or an instruction | |
| 84 | /// has an illegal format. | |
| 85 | InvalidPhysicalFormat, | |
| 86 | /// OpExtInstImport was used with an unknown extension string. | |
| 87 | InvalidExtInstImport, | |
| 88 | /// The module had an instruction with an invalid (unknown) opcode. | |
| 89 | InvalidOpcode, | |
| 90 | /// An instruction's operands did not conform to the SPIR-V specification | |
| 91 | /// for that instruction. | |
| 92 | InvalidOperands, | |
| 93 | /// A result-id was declared more than once. | |
| 94 | DuplicateId, | |
| 95 | /// Some ID did not resolve. | |
| 96 | InvalidId, | |
| 97 | /// This opcode or instruction is not supported yet. | |
| 98 | UnsupportedOperation, | |
| 99 | /// Parser ran out of memory. | |
| 100 | OutOfMemory, | |
| 101 | }; | |
| 102 | ||
| 103 | 43 | pub const Instruction = struct { |
| 104 | 44 | pub const Iterator = struct { |
| 105 | 45 | words: []const Word, |
| 106 | index: usize = 0, | |
| 107 | 46 | offset: usize = 0, |
| 108 | 47 | |
| 109 | 48 | pub fn init(words: []const Word, start_offset: usize) Iterator { |
| 110 | 49 | return .{ .words = words, .offset = start_offset }; |
| 111 | 50 | } |
| 112 | 51 | |
| 113 | pub fn next(self: *Iterator) ?Instruction { | |
| 114 | if (self.offset >= self.words.len) return null; | |
| 52 | pub fn next(it: *Iterator) ?Instruction { | |
| 53 | if (it.offset >= it.words.len) return null; | |
| 115 | 54 | |
| 116 | const instruction_len = self.words[self.offset] >> 16; | |
| 117 | defer self.offset += instruction_len; | |
| 118 | defer self.index += 1; | |
| 55 | const instruction_len = it.words[it.offset] >> 16; | |
| 56 | defer it.offset += instruction_len; | |
| 119 | 57 | assert(instruction_len != 0); |
| 120 | assert(self.offset < self.words.len); | |
| 58 | assert(it.offset < it.words.len); | |
| 121 | 59 | |
| 122 | 60 | return Instruction{ |
| 123 | .opcode = @enumFromInt(self.words[self.offset] & 0xFFFF), | |
| 124 | .index = self.index, | |
| 125 | .offset = self.offset, | |
| 126 | .operands = self.words[self.offset..][1..instruction_len], | |
| 61 | .opcode = @enumFromInt(it.words[it.offset] & 0xFFFF), | |
| 62 | .offset = it.offset, | |
| 63 | .operands = it.words[it.offset..][1..instruction_len], | |
| 127 | 64 | }; |
| 128 | 65 | } |
| 129 | 66 | }; |
| 130 | 67 | |
| 131 | /// The opcode for this instruction. | |
| 132 | 68 | opcode: Opcode, |
| 133 | /// The instruction's index. | |
| 134 | index: usize, | |
| 135 | /// The instruction's word offset in the module. | |
| 136 | 69 | offset: usize, |
| 137 | /// The raw (unparsed) operands for this instruction. | |
| 138 | 70 | operands: []const Word, |
| 139 | 71 | }; |
| 140 | 72 | |
| 141 | /// This parser contains information (acceleration tables) | |
| 142 | /// that can be persisted across different modules. This is | |
| 143 | /// used to initialize the module, and is also used when | |
| 144 | /// further analyzing it. | |
| 145 | 73 | pub const Parser = struct { |
| 146 | /// The allocator used to allocate this parser's structures, | |
| 147 | /// and also the structures of any parsed module. | |
| 148 | a: Allocator, | |
| 149 | ||
| 150 | /// Maps (instruction set, opcode) => instruction index (for instruction set) | |
| 74 | gpa: Allocator, | |
| 151 | 75 | opcode_table: std.AutoHashMapUnmanaged(u32, u16) = .empty, |
| 152 | 76 | |
| 153 | pub fn init(a: Allocator) !Parser { | |
| 154 | var self = Parser{ | |
| 155 | .a = a, | |
| 156 | }; | |
| 157 | errdefer self.deinit(); | |
| 77 | pub fn init(gpa: Allocator) !Parser { | |
| 78 | var parser = Parser{ .gpa = gpa }; | |
| 79 | errdefer parser.deinit(); | |
| 158 | 80 | |
| 159 | 81 | inline for (std.meta.tags(InstructionSet)) |set| { |
| 160 | 82 | const instructions = set.instructions(); |
| 161 | try self.opcode_table.ensureUnusedCapacity(a, @intCast(instructions.len)); | |
| 83 | try parser.opcode_table.ensureUnusedCapacity(gpa, @intCast(instructions.len)); | |
| 162 | 84 | for (instructions, 0..) |inst, i| { |
| 163 | // Note: Some instructions may alias another. In this case we don't really care | |
| 164 | // which one is first: they all (should) have the same operands anyway. Just pick | |
| 165 | // the first, which is usually the core, KHR or EXT variant. | |
| 166 | const entry = self.opcode_table.getOrPutAssumeCapacity(mapSetAndOpcode(set, @intCast(inst.opcode))); | |
| 85 | const entry = parser.opcode_table.getOrPutAssumeCapacity(mapSetAndOpcode(set, @intCast(inst.opcode))); | |
| 167 | 86 | if (!entry.found_existing) { |
| 168 | 87 | entry.value_ptr.* = @intCast(i); |
| 169 | 88 | } |
| 170 | 89 | } |
| 171 | 90 | } |
| 172 | 91 | |
| 173 | return self; | |
| 92 | return parser; | |
| 174 | 93 | } |
| 175 | 94 | |
| 176 | pub fn deinit(self: *Parser) void { | |
| 177 | self.opcode_table.deinit(self.a); | |
| 95 | pub fn deinit(parser: *Parser) void { | |
| 96 | parser.opcode_table.deinit(parser.gpa); | |
| 178 | 97 | } |
| 179 | 98 | |
| 180 | 99 | fn mapSetAndOpcode(set: InstructionSet, opcode: u16) u32 { |
| 181 | 100 | return (@as(u32, @intFromEnum(set)) << 16) | opcode; |
| 182 | 101 | } |
| 183 | 102 | |
| 184 | pub fn getInstSpec(self: Parser, opcode: Opcode) ?spec.Instruction { | |
| 185 | const index = self.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(opcode))) orelse return null; | |
| 103 | pub fn getInstSpec(parser: Parser, opcode: Opcode) ?spec.Instruction { | |
| 104 | const index = parser.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(opcode))) orelse return null; | |
| 186 | 105 | return InstructionSet.core.instructions()[index]; |
| 187 | 106 | } |
| 188 | 107 | |
| 189 | pub fn parse(self: *Parser, module: []const u32) ParseError!BinaryModule { | |
| 190 | if (module[0] != spec.magic_number) { | |
| 191 | return error.InvalidMagic; | |
| 192 | } else if (module.len < header_words) { | |
| 193 | log.err("module only has {}/{} header words", .{ module.len, header_words }); | |
| 194 | return error.InvalidPhysicalFormat; | |
| 195 | } | |
| 196 | ||
| 108 | /// Build a BinaryModule from raw instruction words (no header). | |
| 109 | /// Scans for ext_inst_map, arith_type_width, and the functions section offset. | |
| 110 | pub fn initFromWords(parser: *Parser, words: []const Word, id_bound: u32) !BinaryModule { | |
| 197 | 111 | var binary = BinaryModule{ |
| 198 | .version = @bitCast(module[1]), | |
| 199 | .generator_magic = @bitCast(module[2]), | |
| 200 | .id_bound = module[3], | |
| 201 | .instructions = module[header_words..], | |
| 112 | .id_bound = id_bound, | |
| 113 | .instructions = words, | |
| 202 | 114 | .ext_inst_map = .{}, |
| 203 | 115 | .arith_type_width = .{}, |
| 204 | .sections = undefined, | |
| 116 | .functions_start = undefined, | |
| 205 | 117 | }; |
| 206 | 118 | |
| 207 | 119 | var maybe_function_section: ?usize = null; |
| 120 | var it = binary.iterateInstructions(); | |
| 121 | while (it.next()) |inst| { | |
| 122 | const inst_spec = parser.getInstSpec(inst.opcode) orelse continue; | |
| 123 | const operands = inst.operands; | |
| 208 | 124 | |
| 209 | // First pass through the module to verify basic structure and | |
| 210 | // to gather some initial stuff for more detailed analysis. | |
| 211 | // We want to check some stuff that Instruction.Iterator is no good for, | |
| 212 | // so just iterate manually. | |
| 213 | var offset: usize = 0; | |
| 214 | while (offset < binary.instructions.len) { | |
| 215 | const len = binary.instructions[offset] >> 16; | |
| 216 | if (len == 0 or len + offset > binary.instructions.len) { | |
| 217 | log.err("invalid instruction format: len={}, end={}, module len={}", .{ len, len + offset, binary.instructions.len }); | |
| 218 | return error.InvalidPhysicalFormat; | |
| 219 | } | |
| 220 | defer offset += len; | |
| 221 | ||
| 222 | // We can't really efficiently use non-exhaustive enums here, because we would | |
| 223 | // need to manually write out all valid cases. Since we have this map anyway, just | |
| 224 | // use that. | |
| 225 | const opcode: Opcode = @enumFromInt(@as(u16, @truncate(binary.instructions[offset]))); | |
| 226 | const inst_spec = self.getInstSpec(opcode) orelse { | |
| 227 | log.err("invalid opcode for core set: {}", .{@intFromEnum(opcode)}); | |
| 228 | return error.InvalidOpcode; | |
| 229 | }; | |
| 230 | ||
| 231 | const operands = binary.instructions[offset..][1..len]; | |
| 232 | switch (opcode) { | |
| 125 | switch (inst.opcode) { | |
| 233 | 126 | .OpExtInstImport => { |
| 234 | 127 | const set_name = std.mem.sliceTo(std.mem.sliceAsBytes(operands[1..]), 0); |
| 235 | const set = std.meta.stringToEnum(InstructionSet, set_name) orelse { | |
| 236 | log.err("invalid instruction set '{s}'", .{set_name}); | |
| 237 | return error.InvalidExtInstImport; | |
| 238 | }; | |
| 239 | if (set == .core) return error.InvalidExtInstImport; | |
| 240 | try binary.ext_inst_map.put(self.a, @enumFromInt(operands[0]), set); | |
| 128 | const set = std.meta.stringToEnum(InstructionSet, set_name) orelse continue; | |
| 129 | if (set == .core) continue; | |
| 130 | try binary.ext_inst_map.put(parser.gpa, @enumFromInt(operands[0]), set); | |
| 241 | 131 | }, |
| 242 | 132 | .OpTypeInt, .OpTypeFloat => { |
| 243 | const entry = try binary.arith_type_width.getOrPut(self.a, @enumFromInt(operands[0])); | |
| 244 | if (entry.found_existing) return error.DuplicateId; | |
| 245 | entry.value_ptr.* = std.math.cast(u16, operands[1]) orelse return error.InvalidOperands; | |
| 133 | try binary.arith_type_width.put(parser.gpa, @enumFromInt(operands[0]), @intCast(operands[1])); | |
| 246 | 134 | }, |
| 247 | 135 | .OpFunction => if (maybe_function_section == null) { |
| 248 | maybe_function_section = offset; | |
| 136 | maybe_function_section = inst.offset; | |
| 249 | 137 | }, |
| 250 | 138 | else => {}, |
| 251 | 139 | } |
| 252 | 140 | |
| 253 | // OpSwitch takes a value as argument, not an OpType... hence we need to populate arith_type_width | |
| 254 | // with ALL operations that return an int or float. | |
| 141 | // propagate arith type widths through instructions that return int/float | |
| 255 | 142 | const spec_operands = inst_spec.operands; |
| 256 | 143 | if (spec_operands.len >= 2 and |
| 257 | 144 | spec_operands[0].kind == .id_result_type and |
| 258 | 145 | spec_operands[1].kind == .id_result) |
| 259 | 146 | { |
| 260 | if (operands.len < 2) return error.InvalidOperands; | |
| 261 | if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| { | |
| 262 | const entry = try binary.arith_type_width.getOrPut(self.a, @enumFromInt(operands[1])); | |
| 263 | if (entry.found_existing) return error.DuplicateId; | |
| 264 | entry.value_ptr.* = width; | |
| 147 | if (operands.len >= 2) { | |
| 148 | if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| { | |
| 149 | try binary.arith_type_width.put(parser.gpa, @enumFromInt(operands[1]), width); | |
| 150 | } | |
| 265 | 151 | } |
| 266 | 152 | } |
| 267 | 153 | } |
| 268 | 154 | |
| 269 | binary.sections = .{ | |
| 270 | .functions = maybe_function_section orelse binary.instructions.len, | |
| 271 | }; | |
| 155 | binary.functions_start = maybe_function_section orelse binary.instructions.len; | |
| 272 | 156 | |
| 273 | 157 | return binary; |
| 274 | 158 | } |
| 275 | 159 | |
| 276 | 160 | /// Parse offsets in the instruction that contain result-ids. |
| 277 | 161 | /// Returned offsets are relative to inst.operands. |
| 278 | /// Returns in an arraylist to armortize allocations. | |
| 279 | 162 | pub fn parseInstructionResultIds( |
| 280 | self: *Parser, | |
| 163 | parser: *Parser, | |
| 281 | 164 | binary: BinaryModule, |
| 282 | 165 | inst: Instruction, |
| 283 | offsets: *std.array_list.Managed(u16), | |
| 166 | offsets: *std.ArrayList(u16), | |
| 284 | 167 | ) !void { |
| 285 | const index = self.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(inst.opcode))).?; | |
| 168 | const index = parser.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(inst.opcode))).?; | |
| 286 | 169 | const operands = InstructionSet.core.instructions()[index].operands; |
| 287 | 170 | |
| 288 | 171 | var offset: usize = 0; |
| ... | ... | @@ -290,37 +173,37 @@ pub const Parser = struct { |
| 290 | 173 | .OpSpecConstantOp => { |
| 291 | 174 | assert(operands[0].kind == .id_result_type); |
| 292 | 175 | assert(operands[1].kind == .id_result); |
| 293 | offset = try self.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets); | |
| 176 | offset = try parser.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets); | |
| 294 | 177 | |
| 295 | 178 | if (offset >= inst.operands.len) return error.InvalidPhysicalFormat; |
| 296 | 179 | const spec_opcode = std.math.cast(u16, inst.operands[offset]) orelse return error.InvalidPhysicalFormat; |
| 297 | const spec_index = self.opcode_table.get(mapSetAndOpcode(.core, spec_opcode)) orelse | |
| 180 | const spec_index = parser.opcode_table.get(mapSetAndOpcode(.core, spec_opcode)) orelse | |
| 298 | 181 | return error.InvalidPhysicalFormat; |
| 299 | 182 | const spec_operands = InstructionSet.core.instructions()[spec_index].operands; |
| 300 | 183 | assert(spec_operands[0].kind == .id_result_type); |
| 301 | 184 | assert(spec_operands[1].kind == .id_result); |
| 302 | offset = try self.parseOperandsResultIds(binary, inst, spec_operands[2..], offset + 1, offsets); | |
| 185 | offset = try parser.parseOperandsResultIds(binary, inst, spec_operands[2..], offset + 1, offsets); | |
| 303 | 186 | }, |
| 304 | 187 | .OpExtInst => { |
| 305 | 188 | assert(operands[0].kind == .id_result_type); |
| 306 | 189 | assert(operands[1].kind == .id_result); |
| 307 | offset = try self.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets); | |
| 190 | offset = try parser.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets); | |
| 308 | 191 | |
| 309 | 192 | if (offset + 1 >= inst.operands.len) return error.InvalidPhysicalFormat; |
| 310 | 193 | const set_id: ResultId = @enumFromInt(inst.operands[offset]); |
| 311 | try offsets.append(@intCast(offset)); | |
| 194 | try offsets.append(parser.gpa, @intCast(offset)); | |
| 312 | 195 | const set = binary.ext_inst_map.get(set_id) orelse { |
| 313 | 196 | log.err("invalid instruction set {}", .{@intFromEnum(set_id)}); |
| 314 | 197 | return error.InvalidId; |
| 315 | 198 | }; |
| 316 | 199 | const ext_opcode = std.math.cast(u16, inst.operands[offset + 1]) orelse return error.InvalidPhysicalFormat; |
| 317 | const ext_index = self.opcode_table.get(mapSetAndOpcode(set, ext_opcode)) orelse | |
| 200 | const ext_index = parser.opcode_table.get(mapSetAndOpcode(set, ext_opcode)) orelse | |
| 318 | 201 | return error.InvalidPhysicalFormat; |
| 319 | 202 | const ext_operands = set.instructions()[ext_index].operands; |
| 320 | offset = try self.parseOperandsResultIds(binary, inst, ext_operands, offset + 2, offsets); | |
| 203 | offset = try parser.parseOperandsResultIds(binary, inst, ext_operands, offset + 2, offsets); | |
| 321 | 204 | }, |
| 322 | 205 | else => { |
| 323 | offset = try self.parseOperandsResultIds(binary, inst, operands, offset, offsets); | |
| 206 | offset = try parser.parseOperandsResultIds(binary, inst, operands, offset, offsets); | |
| 324 | 207 | }, |
| 325 | 208 | } |
| 326 | 209 | |
| ... | ... | @@ -328,50 +211,50 @@ pub const Parser = struct { |
| 328 | 211 | } |
| 329 | 212 | |
| 330 | 213 | fn parseOperandsResultIds( |
| 331 | self: *Parser, | |
| 214 | parser: *Parser, | |
| 332 | 215 | binary: BinaryModule, |
| 333 | 216 | inst: Instruction, |
| 334 | 217 | operands: []const spec.Operand, |
| 335 | 218 | start_offset: usize, |
| 336 | offsets: *std.array_list.Managed(u16), | |
| 219 | offsets: *std.ArrayList(u16), | |
| 337 | 220 | ) !usize { |
| 338 | 221 | var offset = start_offset; |
| 339 | 222 | for (operands) |operand| { |
| 340 | offset = try self.parseOperandResultIds(binary, inst, operand, offset, offsets); | |
| 223 | offset = try parser.parseOperandResultIds(binary, inst, operand, offset, offsets); | |
| 341 | 224 | } |
| 342 | 225 | return offset; |
| 343 | 226 | } |
| 344 | 227 | |
| 345 | 228 | fn parseOperandResultIds( |
| 346 | self: *Parser, | |
| 229 | parser: *Parser, | |
| 347 | 230 | binary: BinaryModule, |
| 348 | 231 | inst: Instruction, |
| 349 | 232 | operand: spec.Operand, |
| 350 | 233 | start_offset: usize, |
| 351 | offsets: *std.array_list.Managed(u16), | |
| 234 | offsets: *std.ArrayList(u16), | |
| 352 | 235 | ) !usize { |
| 353 | 236 | var offset = start_offset; |
| 354 | 237 | switch (operand.quantifier) { |
| 355 | 238 | .variadic => while (offset < inst.operands.len) { |
| 356 | offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); | |
| 239 | offset = try parser.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); | |
| 357 | 240 | }, |
| 358 | 241 | .optional => if (offset < inst.operands.len) { |
| 359 | offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); | |
| 242 | offset = try parser.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); | |
| 360 | 243 | }, |
| 361 | 244 | .required => { |
| 362 | offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); | |
| 245 | offset = try parser.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); | |
| 363 | 246 | }, |
| 364 | 247 | } |
| 365 | 248 | return offset; |
| 366 | 249 | } |
| 367 | 250 | |
| 368 | 251 | fn parseOperandKindResultIds( |
| 369 | self: *Parser, | |
| 252 | parser: *Parser, | |
| 370 | 253 | binary: BinaryModule, |
| 371 | 254 | inst: Instruction, |
| 372 | 255 | kind: spec.OperandKind, |
| 373 | 256 | start_offset: usize, |
| 374 | offsets: *std.array_list.Managed(u16), | |
| 257 | offsets: *std.ArrayList(u16), | |
| 375 | 258 | ) !usize { |
| 376 | 259 | var offset = start_offset; |
| 377 | 260 | if (offset >= inst.operands.len) return error.InvalidPhysicalFormat; |
| ... | ... | @@ -383,7 +266,7 @@ pub const Parser = struct { |
| 383 | 266 | for (kind.enumerants()) |enumerant| { |
| 384 | 267 | if ((mask & enumerant.value) != 0) { |
| 385 | 268 | for (enumerant.parameters) |param_kind| { |
| 386 | offset = try self.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets); | |
| 269 | offset = try parser.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets); | |
| 387 | 270 | } |
| 388 | 271 | } |
| 389 | 272 | } |
| ... | ... | @@ -394,14 +277,14 @@ pub const Parser = struct { |
| 394 | 277 | for (kind.enumerants()) |enumerant| { |
| 395 | 278 | if (value == enumerant.value) { |
| 396 | 279 | for (enumerant.parameters) |param_kind| { |
| 397 | offset = try self.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets); | |
| 280 | offset = try parser.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets); | |
| 398 | 281 | } |
| 399 | 282 | break; |
| 400 | 283 | } |
| 401 | 284 | } |
| 402 | 285 | }, |
| 403 | 286 | .id => { |
| 404 | try offsets.append(@intCast(offset)); | |
| 287 | try offsets.append(parser.gpa, @intCast(offset)); | |
| 405 | 288 | offset += 1; |
| 406 | 289 | }, |
| 407 | 290 | else => switch (kind) { |
| ... | ... | @@ -433,7 +316,7 @@ pub const Parser = struct { |
| 433 | 316 | }, |
| 434 | 317 | .literal_ext_inst_integer => unreachable, |
| 435 | 318 | .literal_spec_constant_op_integer => unreachable, |
| 436 | .pair_literal_integer_id_ref => { // Switch case | |
| 319 | .pair_literal_integer_id_ref => { | |
| 437 | 320 | assert(inst.opcode == .OpSwitch); |
| 438 | 321 | const bit_width = binary.arith_type_width.get(@enumFromInt(inst.operands[0])) orelse { |
| 439 | 322 | log.err("invalid OpSwitch type {}", .{inst.operands[0]}); |
| ... | ... | @@ -444,16 +327,16 @@ pub const Parser = struct { |
| 444 | 327 | 33...64 => 2, |
| 445 | 328 | else => unreachable, |
| 446 | 329 | }; |
| 447 | try offsets.append(@intCast(offset)); | |
| 330 | try offsets.append(parser.gpa, @intCast(offset)); | |
| 448 | 331 | offset += 1; |
| 449 | 332 | }, |
| 450 | 333 | .pair_id_ref_literal_integer => { |
| 451 | try offsets.append(@intCast(offset)); | |
| 334 | try offsets.append(parser.gpa, @intCast(offset)); | |
| 452 | 335 | offset += 2; |
| 453 | 336 | }, |
| 454 | 337 | .pair_id_ref_id_ref => { |
| 455 | try offsets.append(@intCast(offset)); | |
| 456 | try offsets.append(@intCast(offset + 1)); | |
| 338 | try offsets.append(parser.gpa, @intCast(offset)); | |
| 339 | try offsets.append(parser.gpa, @intCast(offset + 1)); | |
| 457 | 340 | offset += 2; |
| 458 | 341 | }, |
| 459 | 342 | else => unreachable, |
src/link/SpirV/dedup_types.zig created+255| ... | ... | @@ -0,0 +1,255 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Allocator = std.mem.Allocator; | |
| 3 | const BinaryModule = @import("BinaryModule.zig"); | |
| 4 | ||
| 5 | const spec = @import("../../codegen/spirv/spec.zig"); | |
| 6 | const Word = spec.Word; | |
| 7 | const Id = spec.Id; | |
| 8 | const Opcode = spec.Opcode; | |
| 9 | const Instruction = BinaryModule.Instruction; | |
| 10 | ||
| 11 | /// Deduplicate types and constants in a SPIR-V binary module. | |
| 12 | /// | |
| 13 | /// The SPIR-V spec requires that non-aggregate types be unique. | |
| 14 | /// When merging fragments from parallel codegen, duplicate type definitions | |
| 15 | /// may exist. This pass identifies structurally identical types/constants, | |
| 16 | /// keeps one canonical instance, and remaps all references to duplicates. | |
| 17 | /// | |
| 18 | /// Decorations and names (OpName, OpMemberName) are included in the | |
| 19 | /// equality check: two types that are structurally identical but have | |
| 20 | /// different decorations or names are NOT considered duplicates. | |
| 21 | pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void { | |
| 22 | const gpa = parser.gpa; | |
| 23 | ||
| 24 | const Decoration = struct { offset: usize, len: usize }; | |
| 25 | var decorations_by_id: std.array_hash_map.Auto(Id, std.ArrayList(Decoration)) = .empty; | |
| 26 | defer { | |
| 27 | for (decorations_by_id.values()) |*list| list.deinit(gpa); | |
| 28 | decorations_by_id.deinit(gpa); | |
| 29 | } | |
| 30 | ||
| 31 | var it = binary.iterateInstructions(); | |
| 32 | while (it.next()) |inst| { | |
| 33 | if (inst.offset >= binary.functions_start) break; | |
| 34 | switch (inst.opcode) { | |
| 35 | .OpName, .OpMemberName => {}, | |
| 36 | else => switch (inst.opcode.class()) { | |
| 37 | .annotation => {}, | |
| 38 | else => continue, | |
| 39 | }, | |
| 40 | } | |
| 41 | if (inst.operands.len == 0) continue; | |
| 42 | const target_id: Id = @enumFromInt(inst.operands[0]); | |
| 43 | ||
| 44 | const gop = try decorations_by_id.getOrPut(gpa, target_id); | |
| 45 | if (!gop.found_existing) gop.value_ptr.* = .empty; | |
| 46 | try gop.value_ptr.append(gpa, .{ | |
| 47 | .offset = inst.offset, | |
| 48 | .len = 1 + inst.operands.len, | |
| 49 | }); | |
| 50 | } | |
| 51 | ||
| 52 | var canonical_map: std.array_hash_map.Custom(TypeKey, Id, TypeKey.HashContext, true) = .empty; | |
| 53 | defer { | |
| 54 | for (canonical_map.keys()) |key| gpa.free(key.words); | |
| 55 | canonical_map.deinit(gpa); | |
| 56 | } | |
| 57 | ||
| 58 | var id_remap: std.AutoHashMapUnmanaged(Id, Id) = .empty; | |
| 59 | defer id_remap.deinit(gpa); | |
| 60 | ||
| 61 | var id_offsets: std.ArrayList(u16) = .empty; | |
| 62 | defer id_offsets.deinit(gpa); | |
| 63 | ||
| 64 | var key_words: std.ArrayList(Word) = .empty; | |
| 65 | defer key_words.deinit(gpa); | |
| 66 | ||
| 67 | var dec_hashes: std.ArrayList(u64) = .empty; | |
| 68 | defer dec_hashes.deinit(gpa); | |
| 69 | ||
| 70 | // first pass: build canonical map, identify duplicates | |
| 71 | it = binary.iterateInstructions(); | |
| 72 | while (it.next()) |inst| { | |
| 73 | if (inst.offset >= binary.functions_start) break; | |
| 74 | if (!canDeduplicate(inst.opcode)) continue; | |
| 75 | ||
| 76 | const result_id_index: usize = switch (inst.opcode.class()) { | |
| 77 | .type_declaration, .extension => 0, | |
| 78 | .constant_creation => 1, | |
| 79 | else => continue, | |
| 80 | }; | |
| 81 | if (result_id_index >= inst.operands.len) continue; | |
| 82 | const result_id: Id = @enumFromInt(inst.operands[result_id_index]); | |
| 83 | ||
| 84 | key_words.items.len = 0; | |
| 85 | try key_words.append(gpa, @intFromEnum(inst.opcode)); | |
| 86 | ||
| 87 | id_offsets.items.len = 0; | |
| 88 | parser.parseInstructionResultIds(binary.*, inst, &id_offsets) catch continue; | |
| 89 | ||
| 90 | for (inst.operands, 0..) |word, i| { | |
| 91 | if (i == result_id_index) continue; | |
| 92 | if (std.mem.indexOfScalar(u16, id_offsets.items, @intCast(i)) != null) { | |
| 93 | const canonical = id_remap.get(@enumFromInt(word)) orelse @as(Id, @enumFromInt(word)); | |
| 94 | try key_words.append(gpa, @intFromEnum(canonical)); | |
| 95 | } else { | |
| 96 | try key_words.append(gpa, word); | |
| 97 | } | |
| 98 | } | |
| 99 | ||
| 100 | if (decorations_by_id.getPtr(result_id)) |dec_list| { | |
| 101 | dec_hashes.items.len = 0; | |
| 102 | for (dec_list.items) |dec| { | |
| 103 | const dec_words = binary.instructions[dec.offset..][0..dec.len]; | |
| 104 | const dec_opcode: Opcode = @enumFromInt(dec_words[0] & 0xFFFF); | |
| 105 | var hasher = std.hash.Wyhash.init(0); | |
| 106 | hasher.update(std.mem.asBytes(&dec_words[0])); | |
| 107 | // OpName/OpMemberName operands are literals (member index, string), | |
| 108 | // not ids — hash them directly without remapping | |
| 109 | if (dec_opcode == .OpName or dec_opcode == .OpMemberName) { | |
| 110 | hasher.update(std.mem.sliceAsBytes(dec_words[2..])); | |
| 111 | } else { | |
| 112 | for (dec_words[2..]) |w| { | |
| 113 | const w_val = if (id_remap.get(@enumFromInt(w))) |c| @intFromEnum(c) else w; | |
| 114 | hasher.update(std.mem.asBytes(&w_val)); | |
| 115 | } | |
| 116 | } | |
| 117 | try dec_hashes.append(gpa, hasher.final()); | |
| 118 | } | |
| 119 | std.mem.sort(u64, dec_hashes.items, {}, std.sort.asc(u64)); | |
| 120 | var prev: u64 = 0; | |
| 121 | for (dec_hashes.items) |h| { | |
| 122 | if (h == prev) continue; | |
| 123 | prev = h; | |
| 124 | try key_words.append(gpa, @truncate(h)); | |
| 125 | try key_words.append(gpa, @truncate(h >> 32)); | |
| 126 | } | |
| 127 | } | |
| 128 | ||
| 129 | const key = TypeKey{ .words = try gpa.dupe(Word, key_words.items) }; | |
| 130 | const gop = try canonical_map.getOrPut(gpa, key); | |
| 131 | if (gop.found_existing) { | |
| 132 | try id_remap.put(gpa, result_id, gop.value_ptr.*); | |
| 133 | gpa.free(key.words); | |
| 134 | } else { | |
| 135 | gop.value_ptr.* = result_id; | |
| 136 | } | |
| 137 | } | |
| 138 | ||
| 139 | if (id_remap.count() == 0) return; | |
| 140 | ||
| 141 | // second pass: rewrite id references, remove duplicates and redundant annotations | |
| 142 | var new_words: std.ArrayList(Word) = .empty; | |
| 143 | defer new_words.deinit(gpa); | |
| 144 | try new_words.ensureTotalCapacity(gpa, binary.instructions.len); | |
| 145 | ||
| 146 | var emitted_annotations: std.AutoHashMapUnmanaged(u64, void) = .empty; | |
| 147 | defer emitted_annotations.deinit(gpa); | |
| 148 | ||
| 149 | var new_functions_offset: ?usize = null; | |
| 150 | var max_id: Word = 0; | |
| 151 | ||
| 152 | it = binary.iterateInstructions(); | |
| 153 | while (it.next()) |inst| { | |
| 154 | if (new_functions_offset == null and inst.offset >= binary.functions_start) { | |
| 155 | new_functions_offset = new_words.items.len; | |
| 156 | } | |
| 157 | ||
| 158 | if (canDeduplicate(inst.opcode)) { | |
| 159 | const result_id_index: usize = switch (inst.opcode.class()) { | |
| 160 | .type_declaration, .extension => 0, | |
| 161 | .constant_creation => 1, | |
| 162 | else => unreachable, | |
| 163 | }; | |
| 164 | if (result_id_index < inst.operands.len) { | |
| 165 | const result_id: Id = @enumFromInt(inst.operands[result_id_index]); | |
| 166 | if (id_remap.contains(result_id)) continue; | |
| 167 | } | |
| 168 | } | |
| 169 | ||
| 170 | switch (inst.opcode.class()) { | |
| 171 | .annotation, .debug => { | |
| 172 | if (inst.operands.len > 0) { | |
| 173 | const target: Id = @enumFromInt(inst.operands[0]); | |
| 174 | if (id_remap.contains(target)) continue; | |
| 175 | } | |
| 176 | }, | |
| 177 | else => {}, | |
| 178 | } | |
| 179 | ||
| 180 | const inst_start = new_words.items.len; | |
| 181 | new_words.appendAssumeCapacity(binary.instructions[inst.offset]); | |
| 182 | new_words.appendSliceAssumeCapacity(inst.operands); | |
| 183 | const inst_slice = new_words.items[inst_start + 1 ..]; | |
| 184 | ||
| 185 | id_offsets.items.len = 0; | |
| 186 | parser.parseInstructionResultIds(binary.*, inst, &id_offsets) catch continue; | |
| 187 | ||
| 188 | const inst_spec = parser.getInstSpec(inst.opcode); | |
| 189 | const maybe_result_id_index: ?usize = if (inst_spec) |ispec| blk: { | |
| 190 | break :blk for (0..@min(2, ispec.operands.len)) |i| { | |
| 191 | if (ispec.operands[i].kind == .id_result) break @intCast(i); | |
| 192 | } else null; | |
| 193 | } else null; | |
| 194 | ||
| 195 | for (inst_slice, 0..) |*word, i| { | |
| 196 | if (std.mem.indexOfScalar(u16, id_offsets.items, @intCast(i)) == null) continue; | |
| 197 | max_id = @max(max_id, word.*); | |
| 198 | if (maybe_result_id_index != null and i == maybe_result_id_index.?) continue; | |
| 199 | ||
| 200 | if (id_remap.get(@enumFromInt(word.*))) |canonical| { | |
| 201 | word.* = @intFromEnum(canonical); | |
| 202 | max_id = @max(max_id, word.*); | |
| 203 | } | |
| 204 | } | |
| 205 | ||
| 206 | switch (inst.opcode.class()) { | |
| 207 | .annotation, .debug => { | |
| 208 | const ann_words = new_words.items[inst_start..]; | |
| 209 | const ann_hash = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(ann_words)); | |
| 210 | const gop = try emitted_annotations.getOrPut(gpa, ann_hash); | |
| 211 | if (gop.found_existing) { | |
| 212 | new_words.items.len = inst_start; | |
| 213 | continue; | |
| 214 | } | |
| 215 | }, | |
| 216 | else => {}, | |
| 217 | } | |
| 218 | } | |
| 219 | ||
| 220 | var remap_it = id_remap.iterator(); | |
| 221 | while (remap_it.next()) |entry| { | |
| 222 | _ = binary.ext_inst_map.remove(entry.key_ptr.*); | |
| 223 | _ = binary.arith_type_width.remove(entry.key_ptr.*); | |
| 224 | } | |
| 225 | ||
| 226 | binary.instructions = try gpa.dupe(Word, new_words.items); | |
| 227 | binary.functions_start = new_functions_offset orelse new_words.items.len; | |
| 228 | binary.id_bound = max_id + 1; | |
| 229 | } | |
| 230 | ||
| 231 | fn canDeduplicate(opcode: Opcode) bool { | |
| 232 | return switch (opcode) { | |
| 233 | .OpTypeForwardPointer => false, | |
| 234 | .OpGroupDecorate, .OpGroupMemberDecorate => false, | |
| 235 | else => switch (opcode.class()) { | |
| 236 | .type_declaration, .constant_creation => true, | |
| 237 | .extension => opcode == .OpExtInstImport, | |
| 238 | else => false, | |
| 239 | }, | |
| 240 | }; | |
| 241 | } | |
| 242 | ||
| 243 | const TypeKey = struct { | |
| 244 | words: []const Word, | |
| 245 | ||
| 246 | const HashContext = struct { | |
| 247 | pub fn hash(_: @This(), key: TypeKey) u32 { | |
| 248 | return @truncate(std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(key.words))); | |
| 249 | } | |
| 250 | ||
| 251 | pub fn eql(_: @This(), a: TypeKey, b: TypeKey, _: usize) bool { | |
| 252 | return std.mem.eql(Word, a.words, b.words); | |
| 253 | } | |
| 254 | }; | |
| 255 | }; |
src/link/SpirV/lower_invocation_globals.zig+18-18| ... | ... | @@ -71,7 +71,7 @@ const ModuleInfo = struct { |
| 71 | 71 | arena: Allocator, |
| 72 | 72 | parser: *BinaryModule.Parser, |
| 73 | 73 | binary: BinaryModule, |
| 74 | ) BinaryModule.ParseError!ModuleInfo { | |
| 74 | ) !ModuleInfo { | |
| 75 | 75 | var entry_points: std.array_hash_map.Auto(ResultId, void) = .empty; |
| 76 | 76 | var functions: std.array_hash_map.Auto(ResultId, Fn) = .empty; |
| 77 | 77 | var fn_types = std.AutoHashMap(ResultId, struct { |
| ... | ... | @@ -79,9 +79,9 @@ const ModuleInfo = struct { |
| 79 | 79 | param_types: []const ResultId, |
| 80 | 80 | }).init(arena); |
| 81 | 81 | var calls: std.array_hash_map.Auto(ResultId, void) = .empty; |
| 82 | var callee_store = std.array_list.Managed(ResultId).init(arena); | |
| 82 | var callee_store: std.ArrayList(ResultId) = .empty; | |
| 83 | 83 | var function_invocation_globals: std.array_hash_map.Auto(ResultId, void) = .empty; |
| 84 | var result_id_offsets = std.array_list.Managed(u16).init(arena); | |
| 84 | var result_id_offsets: std.ArrayList(u16) = .empty; | |
| 85 | 85 | var invocation_globals: std.array_hash_map.Auto(ResultId, InvocationGlobal) = .empty; |
| 86 | 86 | |
| 87 | 87 | var maybe_current_function: ?ResultId = null; |
| ... | ... | @@ -164,7 +164,7 @@ const ModuleInfo = struct { |
| 164 | 164 | } |
| 165 | 165 | |
| 166 | 166 | const first_callee = callee_store.items.len; |
| 167 | try callee_store.appendSlice(calls.keys()); | |
| 167 | try callee_store.appendSlice(arena, calls.keys()); | |
| 168 | 168 | |
| 169 | 169 | const fn_type = fn_types.get(fn_ty_id) orelse { |
| 170 | 170 | log.err("Function {f} has invalid OpFunction type", .{current_function}); |
| ... | ... | @@ -395,12 +395,12 @@ const ModuleBuilder = struct { |
| 395 | 395 | return @enumFromInt(self.id_bound); |
| 396 | 396 | } |
| 397 | 397 | |
| 398 | fn finalize(self: *ModuleBuilder, a: Allocator, binary: *BinaryModule) !void { | |
| 398 | fn finalize(self: *ModuleBuilder, arena: Allocator, binary: *BinaryModule) !void { | |
| 399 | 399 | binary.id_bound = self.id_bound; |
| 400 | binary.instructions = try a.dupe(Word, self.section.instructions.items); | |
| 400 | binary.instructions = try arena.dupe(Word, self.section.instructions.items); | |
| 401 | 401 | // Nothing is removed in this pass so we don't need to change any of the maps, |
| 402 | 402 | // just make sure the section is updated. |
| 403 | binary.sections.functions = self.new_functions_section orelse binary.instructions.len; | |
| 403 | binary.functions_start = self.new_functions_section orelse binary.instructions.len; | |
| 404 | 404 | } |
| 405 | 405 | |
| 406 | 406 | /// Process everything from `binary` up to the first function and emit it into the builder. |
| ... | ... | @@ -525,12 +525,12 @@ const ModuleBuilder = struct { |
| 525 | 525 | binary: BinaryModule, |
| 526 | 526 | info: ModuleInfo, |
| 527 | 527 | ) !void { |
| 528 | var result_id_offsets = std.array_list.Managed(u16).init(self.arena); | |
| 529 | var operands = std.array_list.Managed(u32).init(self.arena); | |
| 528 | var result_id_offsets: std.ArrayList(u16) = .empty; | |
| 529 | var operands: std.ArrayList(u32) = .empty; | |
| 530 | 530 | |
| 531 | 531 | var maybe_current_function: ?ResultId = null; |
| 532 | 532 | var skip_until_end: bool = false; |
| 533 | var it = binary.iterateInstructionsFrom(binary.sections.functions); | |
| 533 | var it = binary.iterateInstructionsFrom(binary.functions_start); | |
| 534 | 534 | self.new_functions_section = self.section.instructions.items.len; |
| 535 | 535 | while (it.next()) |inst| { |
| 536 | 536 | if (skip_until_end) { |
| ... | ... | @@ -541,7 +541,7 @@ const ModuleBuilder = struct { |
| 541 | 541 | try parser.parseInstructionResultIds(binary, inst, &result_id_offsets); |
| 542 | 542 | |
| 543 | 543 | operands.items.len = 0; |
| 544 | try operands.appendSlice(inst.operands); | |
| 544 | try operands.appendSlice(self.arena, inst.operands); | |
| 545 | 545 | |
| 546 | 546 | // Replace the result-ids with the global's new result-id if required. |
| 547 | 547 | for (result_id_offsets.items) |off| { |
| ... | ... | @@ -741,14 +741,14 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr |
| 741 | 741 | const sub_node = progress.start("Lower invocation globals", 6); |
| 742 | 742 | defer sub_node.end(); |
| 743 | 743 | |
| 744 | var arena = std.heap.ArenaAllocator.init(parser.a); | |
| 745 | defer arena.deinit(); | |
| 746 | const a = arena.allocator(); | |
| 744 | var arena_state = std.heap.ArenaAllocator.init(parser.gpa); | |
| 745 | defer arena_state.deinit(); | |
| 746 | const arena = arena_state.allocator(); | |
| 747 | 747 | |
| 748 | var info = try ModuleInfo.parse(a, parser, binary.*); | |
| 749 | try info.resolve(a); | |
| 748 | var info = try ModuleInfo.parse(arena, parser, binary.*); | |
| 749 | try info.resolve(arena); | |
| 750 | 750 | |
| 751 | var builder = try ModuleBuilder.init(a, binary.*, info); | |
| 751 | var builder = try ModuleBuilder.init(arena, binary.*, info); | |
| 752 | 752 | sub_node.completeOne(); |
| 753 | 753 | try builder.deriveNewFnInfo(info); |
| 754 | 754 | sub_node.completeOne(); |
| ... | ... | @@ -760,5 +760,5 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr |
| 760 | 760 | sub_node.completeOne(); |
| 761 | 761 | try builder.emitNewEntryPoints(info); |
| 762 | 762 | sub_node.completeOne(); |
| 763 | try builder.finalize(parser.a, binary); | |
| 763 | try builder.finalize(parser.gpa, binary); | |
| 764 | 764 | } |
src/link/SpirV/prune_unused.zig created+234| ... | ... | @@ -0,0 +1,234 @@ |
| 1 | const std = @import("std"); | |
| 2 | const BinaryModule = @import("BinaryModule.zig"); | |
| 3 | const spec = @import("../../codegen/spirv/spec.zig"); | |
| 4 | const Opcode = spec.Opcode; | |
| 5 | const ResultId = spec.Id; | |
| 6 | const Word = spec.Word; | |
| 7 | ||
| 8 | pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void { | |
| 9 | const gpa = parser.gpa; | |
| 10 | ||
| 11 | // map result-id → index in id_offsets for preamble instructions and function headers | |
| 12 | var id_to_index: std.AutoHashMapUnmanaged(ResultId, u32) = .empty; | |
| 13 | defer id_to_index.deinit(gpa); | |
| 14 | ||
| 15 | // for each indexed instruction, its offset in the binary | |
| 16 | var code_offsets: std.ArrayList(usize) = .empty; | |
| 17 | defer code_offsets.deinit(gpa); | |
| 18 | ||
| 19 | var it = binary.iterateInstructions(); | |
| 20 | while (it.next()) |inst| { | |
| 21 | const inst_spec = parser.getInstSpec(inst.opcode) orelse continue; | |
| 22 | const result_id = getResultId(inst, inst_spec) orelse continue; | |
| 23 | ||
| 24 | // only index preamble instructions and function headers | |
| 25 | if (inst.offset < binary.functions_start or inst.opcode == .OpFunction) { | |
| 26 | const index: u32 = @intCast(code_offsets.items.len); | |
| 27 | try id_to_index.put(gpa, result_id, index); | |
| 28 | try code_offsets.append(gpa, inst.offset); | |
| 29 | } | |
| 30 | } | |
| 31 | ||
| 32 | var alive: std.bit_set.Dynamic = try .initEmpty(gpa, code_offsets.items.len); | |
| 33 | defer alive.deinit(gpa); | |
| 34 | ||
| 35 | var id_offset_buf: std.ArrayList(u16) = .empty; | |
| 36 | defer id_offset_buf.deinit(gpa); | |
| 37 | ||
| 38 | // mark non-prunable preamble instructions alive | |
| 39 | it = binary.iterateInstructions(); | |
| 40 | while (it.next()) |inst| { | |
| 41 | if (inst.offset >= binary.functions_start) break; | |
| 42 | if (!canPrune(inst.opcode)) { | |
| 43 | markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf) catch {}; | |
| 44 | } | |
| 45 | } | |
| 46 | ||
| 47 | // mark alive functions' contents alive | |
| 48 | it = binary.iterateInstructionsFrom(binary.functions_start); | |
| 49 | while (it.next()) |inst| { | |
| 50 | if (inst.opcode == .OpFunction) { | |
| 51 | const inst_spec = parser.getInstSpec(inst.opcode) orelse continue; | |
| 52 | const result_id = getResultId(inst, inst_spec) orelse continue; | |
| 53 | const index = id_to_index.get(result_id) orelse continue; | |
| 54 | if (!alive.isSet(index)) { | |
| 55 | // skip dead function | |
| 56 | while (it.next()) |inner| { | |
| 57 | if (inner.opcode == .OpFunctionEnd) break; | |
| 58 | } | |
| 59 | continue; | |
| 60 | } | |
| 61 | } | |
| 62 | ||
| 63 | // mark operands of alive function contents | |
| 64 | if (!canPrune(inst.opcode)) { | |
| 65 | markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf) catch {}; | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 69 | // rewrite | |
| 70 | var new_words: std.ArrayList(Word) = .empty; | |
| 71 | defer new_words.deinit(gpa); | |
| 72 | try new_words.ensureTotalCapacity(gpa, binary.instructions.len); | |
| 73 | ||
| 74 | var new_functions_start: ?usize = null; | |
| 75 | ||
| 76 | it = binary.iterateInstructions(); | |
| 77 | while (it.next()) |inst| { | |
| 78 | if (inst.offset >= binary.functions_start and inst.opcode == .OpFunction) { | |
| 79 | const inst_spec = parser.getInstSpec(inst.opcode) orelse continue; | |
| 80 | const result_id = getResultId(inst, inst_spec) orelse continue; | |
| 81 | const index = id_to_index.get(result_id) orelse continue; | |
| 82 | if (!alive.isSet(index)) { | |
| 83 | while (it.next()) |inner| { | |
| 84 | if (inner.opcode == .OpFunctionEnd) break; | |
| 85 | } | |
| 86 | continue; | |
| 87 | } | |
| 88 | } | |
| 89 | ||
| 90 | if (canPrune(inst.opcode)) { | |
| 91 | const inst_spec = parser.getInstSpec(inst.opcode) orelse { | |
| 92 | appendInst(&new_words, binary, inst, &new_functions_start); | |
| 93 | continue; | |
| 94 | }; | |
| 95 | ||
| 96 | if (getResultId(inst, inst_spec)) |result_id| { | |
| 97 | const index = id_to_index.get(result_id) orelse { | |
| 98 | appendInst(&new_words, binary, inst, &new_functions_start); | |
| 99 | continue; | |
| 100 | }; | |
| 101 | if (!alive.isSet(index)) continue; | |
| 102 | } else { | |
| 103 | // annotation-style: emit only if all id operands are alive | |
| 104 | id_offset_buf.items.len = 0; | |
| 105 | parser.parseInstructionResultIds(binary.*, inst, &id_offset_buf) catch continue; | |
| 106 | var all_alive = true; | |
| 107 | for (id_offset_buf.items) |off| { | |
| 108 | const id: ResultId = @enumFromInt(inst.operands[off]); | |
| 109 | if (id_to_index.get(id)) |idx| { | |
| 110 | if (!alive.isSet(idx)) { | |
| 111 | all_alive = false; | |
| 112 | break; | |
| 113 | } | |
| 114 | } | |
| 115 | } | |
| 116 | if (!all_alive) continue; | |
| 117 | } | |
| 118 | } | |
| 119 | ||
| 120 | appendInst(&new_words, binary, inst, &new_functions_start); | |
| 121 | } | |
| 122 | ||
| 123 | { | |
| 124 | var to_remove: std.ArrayList(ResultId) = .empty; | |
| 125 | defer to_remove.deinit(gpa); | |
| 126 | ||
| 127 | var ext_it = binary.ext_inst_map.iterator(); | |
| 128 | while (ext_it.next()) |entry| { | |
| 129 | if (id_to_index.get(entry.key_ptr.*)) |index| { | |
| 130 | if (!alive.isSet(index)) try to_remove.append(gpa, entry.key_ptr.*); | |
| 131 | } | |
| 132 | } | |
| 133 | for (to_remove.items) |id| _ = binary.ext_inst_map.remove(id); | |
| 134 | ||
| 135 | to_remove.items.len = 0; | |
| 136 | var arith_it = binary.arith_type_width.iterator(); | |
| 137 | while (arith_it.next()) |entry| { | |
| 138 | if (id_to_index.get(entry.key_ptr.*)) |index| { | |
| 139 | if (!alive.isSet(index)) try to_remove.append(gpa, entry.key_ptr.*); | |
| 140 | } | |
| 141 | } | |
| 142 | for (to_remove.items) |id| _ = binary.arith_type_width.remove(id); | |
| 143 | } | |
| 144 | ||
| 145 | binary.instructions = try gpa.dupe(Word, new_words.items); | |
| 146 | binary.functions_start = new_functions_start orelse new_words.items.len; | |
| 147 | } | |
| 148 | ||
| 149 | fn appendInst( | |
| 150 | new_words: *std.ArrayList(Word), | |
| 151 | binary: *const BinaryModule, | |
| 152 | inst: BinaryModule.Instruction, | |
| 153 | new_functions_start: *?usize, | |
| 154 | ) void { | |
| 155 | if (new_functions_start.* == null and inst.offset >= binary.functions_start) { | |
| 156 | new_functions_start.* = new_words.items.len; | |
| 157 | } | |
| 158 | const len = @as(usize, binary.instructions[inst.offset] >> 16); | |
| 159 | new_words.appendSliceAssumeCapacity(binary.instructions[inst.offset..][0..len]); | |
| 160 | } | |
| 161 | ||
| 162 | fn markAlive( | |
| 163 | parser: *BinaryModule.Parser, | |
| 164 | binary: BinaryModule, | |
| 165 | inst: BinaryModule.Instruction, | |
| 166 | alive: *std.DynamicBitSetUnmanaged, | |
| 167 | id_to_index: *const std.AutoHashMapUnmanaged(ResultId, u32), | |
| 168 | code_offsets: *const std.ArrayList(usize), | |
| 169 | id_offset_buf: *std.ArrayList(u16), | |
| 170 | ) !void { | |
| 171 | const start = id_offset_buf.items.len; | |
| 172 | try parser.parseInstructionResultIds(binary, inst, id_offset_buf); | |
| 173 | const end = id_offset_buf.items.len; | |
| 174 | ||
| 175 | var i = start; | |
| 176 | while (i < end) : (i += 1) { | |
| 177 | const off = id_offset_buf.items[i]; | |
| 178 | const id: ResultId = @enumFromInt(inst.operands[off]); | |
| 179 | const index = id_to_index.get(id) orelse continue; | |
| 180 | if (alive.isSet(index)) continue; | |
| 181 | alive.set(index); | |
| 182 | ||
| 183 | const offset = code_offsets.items[index]; | |
| 184 | const ref_inst = BinaryModule.Instruction{ | |
| 185 | .opcode = @enumFromInt(binary.instructions[offset] & 0xFFFF), | |
| 186 | .offset = offset, | |
| 187 | .operands = blk: { | |
| 188 | const l = binary.instructions[offset] >> 16; | |
| 189 | break :blk binary.instructions[offset..][1..l]; | |
| 190 | }, | |
| 191 | }; | |
| 192 | ||
| 193 | if (ref_inst.opcode == .OpFunction) { | |
| 194 | var fn_it = binary.iterateInstructionsFrom(ref_inst.offset); | |
| 195 | _ = fn_it.next(); | |
| 196 | while (fn_it.next()) |fn_inst| { | |
| 197 | if (fn_inst.opcode == .OpFunctionEnd) break; | |
| 198 | markAlive(parser, binary, fn_inst, alive, id_to_index, code_offsets, id_offset_buf) catch {}; | |
| 199 | } | |
| 200 | markAlive(parser, binary, ref_inst, alive, id_to_index, code_offsets, id_offset_buf) catch {}; | |
| 201 | } else { | |
| 202 | markAlive(parser, binary, ref_inst, alive, id_to_index, code_offsets, id_offset_buf) catch {}; | |
| 203 | } | |
| 204 | } | |
| 205 | } | |
| 206 | ||
| 207 | fn getResultId(inst: BinaryModule.Instruction, inst_spec: spec.Instruction) ?ResultId { | |
| 208 | for (0..@min(2, inst_spec.operands.len)) |i| { | |
| 209 | if (inst_spec.operands[i].kind == .id_result) { | |
| 210 | if (i < inst.operands.len) return @enumFromInt(inst.operands[i]); | |
| 211 | } | |
| 212 | } | |
| 213 | return null; | |
| 214 | } | |
| 215 | ||
| 216 | fn canPrune(op: Opcode) bool { | |
| 217 | return switch (op.class()) { | |
| 218 | .type_declaration, | |
| 219 | .constant_creation, | |
| 220 | .annotation, | |
| 221 | => true, | |
| 222 | else => switch (op) { | |
| 223 | .OpFunction, | |
| 224 | .OpUndef, | |
| 225 | .OpString, | |
| 226 | .OpName, | |
| 227 | .OpMemberName, | |
| 228 | .OpExtInstImport, | |
| 229 | .OpVariable, | |
| 230 | => true, | |
| 231 | else => false, | |
| 232 | }, | |
| 233 | }; | |
| 234 | } |
src/target.zig+1-3| ... | ... | @@ -953,9 +953,7 @@ pub inline fn backendSupportsFeature(backend: std.lang.CompilerBackend, comptime |
| 953 | 953 | // threads because they would all just be locking the same mutex to |
| 954 | 954 | // protect Builder. |
| 955 | 955 | .stage2_llvm => false, |
| 956 | // Same problem. Frontend needs to allow this backend to run in the | |
| 957 | // linker thread. | |
| 958 | .stage2_spirv => false, | |
| 956 | .stage2_spirv => true, | |
| 959 | 957 | // Please do not make any more exceptions. Backends must support |
| 960 | 958 | // being run in a separate thread from now on. |
| 961 | 959 | else => true, |
test/behavior/align.zig+2| ... | ... | @@ -639,6 +639,8 @@ test "function pointer align mask" { |
| 639 | 639 | } |
| 640 | 640 | |
| 641 | 641 | test "align expression is implicitly comptime" { |
| 642 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 643 | ||
| 642 | 644 | const S = struct { |
| 643 | 645 | fn alignment() usize { |
| 644 | 646 | return 4; |
test/behavior/enum.zig+2| ... | ... | @@ -1318,6 +1318,8 @@ test "switch on an extern enum with negative value" { |
| 1318 | 1318 | } |
| 1319 | 1319 | |
| 1320 | 1320 | test "switch on an enum with small signed tag type" { |
| 1321 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1322 | ||
| 1321 | 1323 | const E = enum(i3) { |
| 1322 | 1324 | y = -2, |
| 1323 | 1325 | z = -1, |
test/behavior/inline_switch.zig+7| ... | ... | @@ -4,6 +4,7 @@ const builtin = @import("builtin"); |
| 4 | 4 | |
| 5 | 5 | test "inline scalar prongs" { |
| 6 | 6 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 7 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 7 | 8 | |
| 8 | 9 | var x: usize = 0; |
| 9 | 10 | switch (x) { |
| ... | ... | @@ -18,6 +19,7 @@ test "inline scalar prongs" { |
| 18 | 19 | |
| 19 | 20 | test "inline prong ranges" { |
| 20 | 21 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 22 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 21 | 23 | |
| 22 | 24 | var x: usize = 0; |
| 23 | 25 | _ = &x; |
| ... | ... | @@ -32,6 +34,7 @@ test "inline prong ranges" { |
| 32 | 34 | const E = enum { a, b, c, d }; |
| 33 | 35 | test "inline switch enums" { |
| 34 | 36 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 37 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 35 | 38 | |
| 36 | 39 | var x: E = .a; |
| 37 | 40 | _ = &x; |
| ... | ... | @@ -71,6 +74,7 @@ test "inline switch unions" { |
| 71 | 74 | |
| 72 | 75 | test "inline else bool" { |
| 73 | 76 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 77 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 74 | 78 | |
| 75 | 79 | var a = true; |
| 76 | 80 | _ = &a; |
| ... | ... | @@ -82,6 +86,7 @@ test "inline else bool" { |
| 82 | 86 | |
| 83 | 87 | test "inline else error" { |
| 84 | 88 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 89 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 85 | 90 | |
| 86 | 91 | const Err = error{ a, b, c }; |
| 87 | 92 | var a = Err.a; |
| ... | ... | @@ -94,6 +99,7 @@ test "inline else error" { |
| 94 | 99 | |
| 95 | 100 | test "inline else enum" { |
| 96 | 101 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 102 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 97 | 103 | |
| 98 | 104 | const E2 = enum(u8) { a = 2, b = 3, c = 4, d = 5 }; |
| 99 | 105 | var a: E2 = .a; |
| ... | ... | @@ -124,6 +130,7 @@ test "inline else int with gaps" { |
| 124 | 130 | |
| 125 | 131 | test "inline else int all values" { |
| 126 | 132 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 133 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 127 | 134 | |
| 128 | 135 | var a: u2 = 0; |
| 129 | 136 | _ = &a; |
test/behavior/ir_block_deps.zig+1| ... | ... | @@ -20,6 +20,7 @@ fn getErrInt() anyerror!i32 { |
| 20 | 20 | test "ir block deps" { |
| 21 | 21 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 22 | 22 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 23 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 23 | 24 | |
| 24 | 25 | try expect((foo(1) catch unreachable) == 0); |
| 25 | 26 | try expect((foo(2) catch unreachable) == 0); |
test/behavior/switch.zig+1| ... | ... | @@ -801,6 +801,7 @@ test "enum value without tag name used as switch item" { |
| 801 | 801 | } |
| 802 | 802 | |
| 803 | 803 | test "switch item sizeof" { |
| 804 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 804 | 805 | const S = struct { |
| 805 | 806 | fn doTheTest() !void { |
| 806 | 807 | var a: usize = 0; |
test/behavior/tuple.zig+1| ... | ... | @@ -299,6 +299,7 @@ test "tuple type with void field and a runtime field" { |
| 299 | 299 | test "branching inside tuple literal" { |
| 300 | 300 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 301 | 301 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 302 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 302 | 303 | |
| 303 | 304 | const S = struct { |
| 304 | 305 | fn foo(a: anytype) !void { |
test/behavior/union.zig+3| ... | ... | @@ -885,6 +885,8 @@ test "union no tag with struct member" { |
| 885 | 885 | } |
| 886 | 886 | |
| 887 | 887 | test "extern union doesn't trigger field check at comptime" { |
| 888 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 889 | ||
| 888 | 890 | const U = extern union { |
| 889 | 891 | x: u32, |
| 890 | 892 | y: u8, |
| ... | ... | @@ -1214,6 +1216,7 @@ test "return an extern union from C calling convention" { |
| 1214 | 1216 | test "noreturn field in union" { |
| 1215 | 1217 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 1216 | 1218 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 1219 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1217 | 1220 | |
| 1218 | 1221 | const U = union(enum) { |
| 1219 | 1222 | a: u32, |
test/cases/compile_errors/illegal_operation_on_logical_ptr.zig+8-24| ... | ... | @@ -1,15 +1,3 @@ |
| 1 | export fn elemPtr() void { | |
| 2 | var ptr: [*]u8 = undefined; | |
| 3 | ptr[0] = 0; | |
| 4 | } | |
| 5 | ||
| 6 | export fn elemVal() void { | |
| 7 | var ptr: [*]u8 = undefined; | |
| 8 | var val = ptr[0]; | |
| 9 | _ = &ptr; | |
| 10 | _ = &val; | |
| 11 | } | |
| 12 | ||
| 13 | 1 | export fn intFromPtr() void { |
| 14 | 2 | var value: u8 = 0; |
| 15 | 3 | _ = @intFromPtr(&value); |
| ... | ... | @@ -37,15 +25,11 @@ export fn ptrIntArithmetic() void { |
| 37 | 25 | // error |
| 38 | 26 | // target=spirv64-vulkan |
| 39 | 27 | // |
| 40 | // :3:8: error: illegal operation on logical pointer of type '[*]u8' | |
| 41 | // :3:8: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan | |
| 42 | // :8:18: error: illegal operation on logical pointer of type '[*]u8' | |
| 43 | // :8:18: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan | |
| 44 | // :15:21: error: illegal operation on logical pointer of type '*u8' | |
| 45 | // :15:21: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan | |
| 46 | // :20:20: error: illegal operation on logical pointer of type '*u8' | |
| 47 | // :20:20: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan | |
| 48 | // :28:17: error: illegal operation on logical pointer of type '*u8' | |
| 49 | // :28:17: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan | |
| 50 | // :34:14: error: illegal operation on logical pointer of type '[*]u8' | |
| 51 | // :34:14: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan | |
| 28 | // :3:21: error: illegal operation on logical pointer of type '*u8' | |
| 29 | // :3:21: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan | |
| 30 | // :8:20: error: illegal operation on logical pointer of type '*u8' | |
| 31 | // :8:20: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan | |
| 32 | // :16:17: error: illegal operation on logical pointer of type '*u8' | |
| 33 | // :16:17: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan | |
| 34 | // :22:14: error: illegal operation on logical pointer of type '[*]u8' | |
| 35 | // :22:14: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan |