| ... | @@ -2,6 +2,7 @@ const std = @import("std"); | ... | @@ -2,6 +2,7 @@ const std = @import("std"); |
| 2 | const Allocator = std.mem.Allocator; | 2 | const Allocator = std.mem.Allocator; |
| 3 | const Path = std.Build.Cache.Path; | 3 | const Path = std.Build.Cache.Path; |
| 4 | const assert = std.debug.assert; | 4 | const assert = std.debug.assert; |
| | 5 | const stringToEnum = std.meta.stringToEnum; |
| 5 | const InternPool = @import("../../InternPool.zig"); | 6 | const InternPool = @import("../../InternPool.zig"); |
| 6 | const link = @import("../../link.zig"); | 7 | const link = @import("../../link.zig"); |
| 7 | const Type = @import("../../Type.zig"); | 8 | const Type = @import("../../Type.zig"); |
| ... | @@ -34,6 +35,7 @@ decorations: Section, | ... | @@ -34,6 +35,7 @@ decorations: Section, |
| 34 | globals: Section, | 35 | globals: Section, |
| 35 | declarations: Section, | 36 | declarations: Section, |
| 36 | functions: Section, | 37 | functions: Section, |
| | 38 | nonsemantic_info: bool = false, |
| 37 | | 39 | |
| 38 | const Symbol = struct { | 40 | const Symbol = struct { |
| 39 | id: Id, | 41 | id: Id, |
| ... | @@ -42,7 +44,7 @@ const Symbol = struct { | ... | @@ -42,7 +44,7 @@ const Symbol = struct { |
| 42 | }; | 44 | }; |
| 43 | | 45 | |
| 44 | const Annotation = struct { | 46 | const Annotation = struct { |
| 45 | target: Word, | 47 | target_id: Word, |
| 46 | class: spec.Class, | 48 | class: spec.Class, |
| 47 | start: usize, | 49 | start: usize, |
| 48 | end: usize, | 50 | end: usize, |
| ... | @@ -224,6 +226,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { | ... | @@ -224,6 +226,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { |
| 224 | const gpa = comp.gpa; | 226 | const gpa = comp.gpa; |
| 225 | const arena = flush.arena; | 227 | const arena = flush.arena; |
| 226 | const diags = &comp.link_diags; | 228 | const diags = &comp.link_diags; |
| | 229 | const target = &comp.root_mod.resolved_target.result; |
| 227 | | 230 | |
| 228 | const ids = try gpa.alloc(Id, mir.id_bound); | 231 | const ids = try gpa.alloc(Id, mir.id_bound); |
| 229 | defer gpa.free(ids); | 232 | defer gpa.free(ids); |
| ... | @@ -264,7 +267,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { | ... | @@ -264,7 +267,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { |
| 264 | offset += inst.len; | 267 | offset += inst.len; |
| 265 | | 268 | |
| 266 | if (header.opcode == .OpDecorate) { | 269 | if (header.opcode == .OpDecorate) { |
| 267 | const target = inst[1]; | 270 | const target_id = inst[1]; |
| 268 | const decoration: spec.Decoration = @fromBackingInt(inst[2]); | 271 | const decoration: spec.Decoration = @fromBackingInt(inst[2]); |
| 269 | if (decoration == .linkage_attributes) { | 272 | if (decoration == .linkage_attributes) { |
| 270 | const parameters = inst[3..]; | 273 | const parameters = inst[3..]; |
| ... | @@ -273,7 +276,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { | ... | @@ -273,7 +276,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { |
| 273 | const linkage: spec.LinkageType = @fromBackingInt(parameters[parameters.len - 1]); | 276 | const linkage: spec.LinkageType = @fromBackingInt(parameters[parameters.len - 1]); |
| 274 | const gop = try flush.symbols.getOrPut(arena, name); | 277 | const gop = try flush.symbols.getOrPut(arena, name); |
| 275 | if (!gop.found_existing) gop.value_ptr.* = .{ | 278 | if (!gop.found_existing) gop.value_ptr.* = .{ |
| 276 | .id = ids[target], | 279 | .id = ids[target_id], |
| 277 | .defined = false, | 280 | .defined = false, |
| 278 | .declaration = &.{}, | 281 | .declaration = &.{}, |
| 279 | }; | 282 | }; |
| ... | @@ -285,19 +288,19 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { | ... | @@ -285,19 +288,19 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { |
| 285 | } | 288 | } |
| 286 | gop.value_ptr.defined = true; | 289 | gop.value_ptr.defined = true; |
| 287 | }, | 290 | }, |
| 288 | .import => try imports.put(gpa, target, @intCast(gop.index)), | 291 | .import => try imports.put(gpa, target_id, @intCast(gop.index)), |
| 289 | .link_once_odr => { | 292 | .link_once_odr => { |
| 290 | return diags.failParse(path.?, "unsupported linkage '{t}'", .{linkage}); | 293 | return diags.failParse(path.?, "unsupported linkage '{t}'", .{linkage}); |
| 291 | }, | 294 | }, |
| 292 | } | 295 | } |
| 293 | | 296 | |
| 294 | ids[target] = gop.value_ptr.id; | 297 | ids[target_id] = gop.value_ptr.id; |
| 295 | continue; | 298 | continue; |
| 296 | } | 299 | } |
| 297 | } | 300 | } |
| 298 | | 301 | |
| 299 | const class = header.opcode.class(); | 302 | const class = header.opcode.class(); |
| 300 | const target: Word = switch (header.opcode) { | 303 | const target_id: Word = switch (header.opcode) { |
| 301 | .OpName, .OpMemberName => inst[1], | 304 | .OpName, .OpMemberName => inst[1], |
| 302 | .OpDecorationGroup, .OpGroupDecorate, .OpGroupMemberDecorate => { | 305 | .OpDecorationGroup, .OpGroupDecorate, .OpGroupMemberDecorate => { |
| 303 | return diags.failParse(path.?, "decoration groups are not supported", .{}); | 306 | return diags.failParse(path.?, "decoration groups are not supported", .{}); |
| ... | @@ -305,32 +308,34 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { | ... | @@ -305,32 +308,34 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { |
| 305 | else => if (class == .annotation) inst[1] else continue, | 308 | else => if (class == .annotation) inst[1] else continue, |
| 306 | }; | 309 | }; |
| 307 | try annotations.append(gpa, .{ | 310 | try annotations.append(gpa, .{ |
| 308 | .target = target, | 311 | .target_id = target_id, |
| 309 | .class = class, | 312 | .class = class, |
| 310 | .start = start, | 313 | .start = start, |
| 311 | .end = offset, | 314 | .end = offset, |
| 312 | }); | 315 | }); |
| 313 | } | 316 | } |
| 314 | | 317 | |
| | 318 | var result_ids: std.ArrayList(Word) = .empty; |
| | 319 | defer result_ids.deinit(gpa); |
| | 320 | |
| 315 | const starts = try gpa.alloc(u32, mir.id_bound + 1); | 321 | const starts = try gpa.alloc(u32, mir.id_bound + 1); |
| 316 | defer gpa.free(starts); | 322 | defer gpa.free(starts); |
| 317 | @memset(starts, 0); | 323 | @memset(starts, 0); |
| 318 | | 324 | |
| 319 | for (annotations.items) |annotation| starts[annotation.target + 1] += 1; | 325 | for (annotations.items) |annotation| starts[annotation.target_id + 1] += 1; |
| 320 | for (1..starts.len) |i| starts[i] += starts[i - 1]; | 326 | for (1..starts.len) |i| starts[i] += starts[i - 1]; |
| 321 | | 327 | |
| 322 | const cursors = try gpa.dupe(u32, starts); | 328 | const grouped_indices = try gpa.alloc(u32, annotations.items.len); |
| 323 | defer gpa.free(cursors); | 329 | defer gpa.free(grouped_indices); |
| 324 | const by_target = try gpa.alloc(u32, annotations.items.len); | 330 | { |
| 325 | defer gpa.free(by_target); | 331 | const next_slot = try gpa.dupe(u32, starts); |
| 326 | for (annotations.items, 0..) |annotation, i| { | 332 | defer gpa.free(next_slot); |
| 327 | by_target[cursors[annotation.target]] = @intCast(i); | 333 | for (annotations.items, 0..) |annotation, i| { |
| 328 | cursors[annotation.target] += 1; | 334 | grouped_indices[next_slot[annotation.target_id]] = @intCast(i); |
| | 335 | next_slot[annotation.target_id] += 1; |
| | 336 | } |
| 329 | } | 337 | } |
| 330 | | 338 | |
| 331 | var results: std.ArrayList(Word) = .empty; | | |
| 332 | defer results.deinit(gpa); | | |
| 333 | | | |
| 334 | offset = 0; | 339 | offset = 0; |
| 335 | while (offset < words.len) { | 340 | while (offset < words.len) { |
| 336 | const start = offset; | 341 | const start = offset; |
| ... | @@ -340,25 +345,67 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { | ... | @@ -340,25 +345,67 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { |
| 340 | input.mark += 1; | 345 | input.mark += 1; |
| 341 | flush.deps.clearRetainingCapacity(); | 346 | flush.deps.clearRetainingCapacity(); |
| 342 | | 347 | |
| 343 | const result_index = header.opcode.resultIndex() orelse { | 348 | const result_index = header.opcode.resultIndex() orelse switch (header.opcode) { |
| 344 | switch (header.opcode) { | 349 | .OpCapability => { |
| 345 | .OpEntryPoint => { | 350 | const capability = std.enums.fromInt(spec.Capability, inst[1]) orelse { |
| 346 | try input.copy(flush, &flush.object_entry_points, start, offset, null); | 351 | return diags.failParse(path.?, "unknown capability '{d}'", .{inst[1]}); |
| 347 | }, | 352 | }; |
| 348 | .OpExecutionMode, .OpExecutionModeId => { | 353 | |
| 349 | try input.copy(flush, &flush.object_execution_modes, start, offset, null); | 354 | // TODO: this isn't a full validation. we probably have to automate this |
| 350 | }, | 355 | // based on some generated info from SPIRV-Headers/SPIRV-Registry |
| 351 | .OpTypeForwardPointer => { | 356 | const allowed = switch (capability) { |
| 352 | forward_pointers.set(inst[1]); | 357 | .linkage => true, |
| 353 | try input.copy(flush, &flush.globals, start, offset, null); | 358 | .shader => target.os.tag == .vulkan or target.os.tag == .opengl, |
| 354 | }, | 359 | .kernel, .addresses => target.os.tag == .opencl, |
| 355 | else => {}, | 360 | .physical_storage_buffer_addresses => target.os.tag == .vulkan and target.cpu.arch == .spirv64, |
| 356 | } | 361 | else => if (stringToEnum(std.Target.spirv.Feature, @tagName(capability))) |f| target.cpu.has(.spirv, f) else false, |
| 357 | continue; | 362 | }; |
| | 363 | if (!allowed) return diags.failParse( |
| | 364 | path.?, |
| | 365 | "SPIR-V capability '{t}' is not enabled by the target feature set", |
| | 366 | .{capability}, |
| | 367 | ); |
| | 368 | continue; |
| | 369 | }, |
| | 370 | .OpExtension => { |
| | 371 | const name = std.mem.sliceTo(std.mem.sliceAsBytes(inst[1..]), 0); |
| | 372 | if (std.mem.eql(u8, name, "SPV_KHR_non_semantic_info")) { |
| | 373 | flush.nonsemantic_info = true; |
| | 374 | continue; |
| | 375 | } |
| | 376 | |
| | 377 | const allowed = allowed: { |
| | 378 | if (stringToEnum(std.Target.spirv.Feature, name)) |f| { |
| | 379 | break :allowed target.cpu.has(.spirv, f); |
| | 380 | } |
| | 381 | break :allowed true; |
| | 382 | }; |
| | 383 | if (!allowed) return diags.failParse( |
| | 384 | path.?, |
| | 385 | "SPIR-V extension '{s}' is not enabled by the target feature set", |
| | 386 | .{name}, |
| | 387 | ); |
| | 388 | continue; |
| | 389 | }, |
| | 390 | .OpEntryPoint => { |
| | 391 | try input.copy(flush, &flush.object_entry_points, start, offset, null); |
| | 392 | continue; |
| | 393 | }, |
| | 394 | .OpExecutionMode, .OpExecutionModeId => { |
| | 395 | try input.copy(flush, &flush.object_execution_modes, start, offset, null); |
| | 396 | continue; |
| | 397 | }, |
| | 398 | .OpTypeForwardPointer => { |
| | 399 | forward_pointers.set(inst[1]); |
| | 400 | try input.copy(flush, &flush.globals, start, offset, null); |
| | 401 | continue; |
| | 402 | }, |
| | 403 | else => continue, |
| 358 | }; | 404 | }; |
| 359 | const result = inst[result_index]; | 405 | |
| 360 | results.clearRetainingCapacity(); | 406 | const result_id = inst[result_index]; |
| 361 | try results.append(gpa, result); | 407 | result_ids.clearRetainingCapacity(); |
| | 408 | try result_ids.append(gpa, result_id); |
| 362 | | 409 | |
| 363 | var has_body = false; | 410 | var has_body = false; |
| 364 | if (header.opcode == .OpFunction) { | 411 | if (header.opcode == .OpFunction) { |
| ... | @@ -366,16 +413,16 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { | ... | @@ -366,16 +413,16 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { |
| 366 | const body: spec.InstructionHeader = @bitCast(words[offset]); | 413 | const body: spec.InstructionHeader = @bitCast(words[offset]); |
| 367 | if (body.opcode == .OpLabel) has_body = true; | 414 | if (body.opcode == .OpLabel) has_body = true; |
| 368 | if (body.opcode.resultIndex()) |index| { | 415 | if (body.opcode.resultIndex()) |index| { |
| 369 | try results.append(gpa, words[offset + index]); | 416 | try result_ids.append(gpa, words[offset + index]); |
| 370 | } | 417 | } |
| 371 | offset += body.word_count; | 418 | offset += body.word_count; |
| 372 | if (body.opcode == .OpFunctionEnd) break; | 419 | if (body.opcode == .OpFunctionEnd) break; |
| 373 | } | 420 | } |
| 374 | } | 421 | } |
| 375 | | 422 | |
| 376 | for (results.items) |own| input.marks[own] = input.mark; | 423 | for (result_ids.items) |own| input.marks[own] = input.mark; |
| 377 | | 424 | |
| 378 | if (imports.get(result)) |symbol_index| { | 425 | if (imports.get(result_id)) |symbol_index| { |
| 379 | flush.scratch.reset(); | 426 | flush.scratch.reset(); |
| 380 | try input.copy(flush, &flush.scratch, start, offset, null); | 427 | try input.copy(flush, &flush.scratch, start, offset, null); |
| 381 | const declaration = try arena.dupe(Word, flush.scratch.instructions.items); | 428 | const declaration = try arena.dupe(Word, flush.scratch.instructions.items); |
| ... | @@ -385,30 +432,30 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { | ... | @@ -385,30 +432,30 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { |
| 385 | | 432 | |
| 386 | switch (header.opcode) { | 433 | switch (header.opcode) { |
| 387 | .OpFunction => if (!has_body) return diags.failParse(path.?, "function declaration without linkage", .{}), | 434 | .OpFunction => if (!has_body) return diags.failParse(path.?, "function declaration without linkage", .{}), |
| 388 | .OpVariable => try flush.variables.put(arena, ids[result], @fromBackingInt(inst[3])), | 435 | .OpVariable => try flush.variables.put(arena, ids[result_id], @fromBackingInt(inst[3])), |
| 389 | else => if (!forward_pointers.isSet(result)) { | 436 | else => if (!forward_pointers.isSet(result_id)) { |
| 390 | flush.scratch.reset(); | 437 | flush.scratch.reset(); |
| 391 | try input.copy(flush, &flush.scratch, start, offset, start + result_index); | 438 | try input.copy(flush, &flush.scratch, start, offset, start + result_index); |
| 392 | for (by_target[starts[result]..starts[result + 1]]) |index| { | 439 | for (grouped_indices[starts[result_id]..starts[result_id + 1]]) |index| { |
| 393 | const annotation = annotations.items[index]; | 440 | const annotation = annotations.items[index]; |
| 394 | if (annotation.class != .annotation) continue; | 441 | if (annotation.class != .annotation) continue; |
| 395 | const target = annotation.start + 1; | 442 | const target_id = annotation.start + 1; |
| 396 | try input.copy(flush, &flush.scratch, annotation.start, annotation.end, target); | 443 | try input.copy(flush, &flush.scratch, annotation.start, annotation.end, target_id); |
| 397 | } | 444 | } |
| 398 | | 445 | |
| 399 | const key = std.mem.sliceAsBytes(flush.scratch.instructions.items); | 446 | const key = std.mem.sliceAsBytes(flush.scratch.instructions.items); |
| 400 | const gop = try flush.interned.getOrPut(arena, key); | 447 | const gop = try flush.interned.getOrPut(arena, key); |
| 401 | if (gop.found_existing) { | 448 | if (gop.found_existing) { |
| 402 | ids[result] = gop.value_ptr.*; | 449 | ids[result_id] = gop.value_ptr.*; |
| 403 | continue; | 450 | continue; |
| 404 | } | 451 | } |
| 405 | gop.key_ptr.* = try arena.dupe(u8, key); | 452 | gop.key_ptr.* = try arena.dupe(u8, key); |
| 406 | gop.value_ptr.* = ids[result]; | 453 | gop.value_ptr.* = ids[result_id]; |
| 407 | }, | 454 | }, |
| 408 | } | 455 | } |
| 409 | | 456 | |
| 410 | for (results.items) |target| { | 457 | for (result_ids.items) |target_id| { |
| 411 | for (by_target[starts[target]..starts[target + 1]]) |index| { | 458 | for (grouped_indices[starts[target_id]..starts[target_id + 1]]) |index| { |
| 412 | const annotation = annotations.items[index]; | 459 | const annotation = annotations.items[index]; |
| 413 | const section = switch (annotation.class) { | 460 | const section = switch (annotation.class) { |
| 414 | .debug => &flush.names, | 461 | .debug => &flush.names, |
| ... | @@ -428,7 +475,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { | ... | @@ -428,7 +475,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { |
| 428 | try input.copy(flush, section, start, offset, null); | 475 | try input.copy(flush, section, start, offset, null); |
| 429 | if (header.opcode == .OpFunction) { | 476 | if (header.opcode == .OpFunction) { |
| 430 | const references = try arena.dupe(Id, flush.deps.items); | 477 | const references = try arena.dupe(Id, flush.deps.items); |
| 431 | try flush.function_references.put(arena, ids[result], references); | 478 | try flush.function_references.put(arena, ids[result_id], references); |
| 432 | } | 479 | } |
| 433 | } | 480 | } |
| 434 | | 481 | |
| ... | @@ -468,25 +515,25 @@ fn emitCapabilities(flush: *Flush, out: *Section) link.Error!void { | ... | @@ -468,25 +515,25 @@ fn emitCapabilities(flush: *Flush, out: *Section) link.Error!void { |
| 468 | try out.emit(gpa, .OpCapability, .{ .capability = .linkage }); | 515 | try out.emit(gpa, .OpCapability, .{ .capability = .linkage }); |
| 469 | } | 516 | } |
| 470 | | 517 | |
| 471 | inline for (@typeInfo(spec.Capability).@"enum".field_names) |name| { | 518 | for (std.enums.values(spec.Capability)) |capability| { |
| 472 | if (@hasField(std.Target.spirv.Feature, name)) { | 519 | const feature = stringToEnum(std.Target.spirv.Feature, @tagName(capability)) orelse continue; |
| 473 | const feature = @field(std.Target.spirv.Feature, name); | 520 | if (!target.cpu.has(.spirv, feature)) continue; |
| 474 | if (target.cpu.has(.spirv, feature)) { | 521 | try out.emit(gpa, .OpCapability, .{ .capability = capability }); |
| 475 | try out.emit(gpa, .OpCapability, .{ .capability = @field(spec.Capability, name) }); | | |
| 476 | } | | |
| 477 | } | | |
| 478 | } | 522 | } |
| 479 | | 523 | |
| 480 | if (target.cpu.arch == .spirv64 and target.os.tag == .vulkan) { | 524 | if (target.cpu.arch == .spirv64 and target.os.tag == .vulkan) { |
| 481 | try out.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_physical_storage_buffer" }); | 525 | try out.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_physical_storage_buffer" }); |
| 482 | } | 526 | } |
| 483 | | 527 | |
| 484 | inline for (@typeInfo(spec.Extension).@"enum".field_names) |name| { | 528 | for (std.enums.values(spec.Extension)) |extension| { |
| 485 | if (comptime std.mem.startsWith(u8, name, "v1_")) continue; | 529 | const name = @tagName(extension); |
| 486 | if (@hasField(std.Target.spirv.Feature, name)) { | 530 | if (std.mem.startsWith(u8, name, "v1_")) continue; |
| 487 | const feature = @field(std.Target.spirv.Feature, name); | 531 | const feature = stringToEnum(std.Target.spirv.Feature, name) orelse continue; |
| 488 | if (target.cpu.has(.spirv, feature)) try out.emit(gpa, .OpExtension, .{ .name = name }); | 532 | if (!target.cpu.has(.spirv, feature)) continue; |
| 489 | } | 533 | try out.emit(gpa, .OpExtension, .{ .name = name }); |
| | 534 | } |
| | 535 | if (flush.nonsemantic_info) { |
| | 536 | try out.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_non_semantic_info" }); |
| 490 | } | 537 | } |
| 491 | } | 538 | } |
| 492 | | 539 | |