From f51d2cf02ab5593f8e0e7923b34d8a68048bc0d8 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Thu, 15 Oct 2026 01:35:56 +0330 Subject: [PATCH] llvm: pass extensions and enable debug info --- src/Compilation.zig | 3 +- src/Compilation/Config.zig | 5 +- src/codegen/llvm.zig | 1 + src/link/Elf/ZigObject.zig | 2 +- src/link/Elf2.zig | 16 ++-- src/link/MachO/ZigObject.zig | 2 +- src/link/Spirv/Flush.zig | 171 ++++++++++++++++++++++------------- src/main.zig | 29 ++++++ 8 files changed, 155 insertions(+), 74 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 94dad2b74fa251bafaa9dd8246d52d8e45fdf0e1..945072f0039a541b8b588cca76d8eae9fdbce878 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1300,7 +1300,7 @@ pub const cache_helpers = struct { const tag: @typeInfo(Config.DebugFormat).@"union".tag_type.? = x; hh.add(tag); switch (x) { - .strip, .code_view => {}, + .strip, .code_view, .nsdi => {}, .dwarf => |f| hh.add(f), } } @@ -6303,6 +6303,7 @@ fn addCommonCCArgs( try argv.ensureUnusedCapacity(2); switch (comp.config.debug_format) { .strip => {}, + .nsdi => argv.appendAssumeCapacity("-g"), .code_view => { // -g is required here because -gcodeview doesn't trigger debug info // generation, it only changes the type of information generated. diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index 4ebe3137be59c9bf90f8cce48e072ff74b413a63..0294268834e9b7c6f4fdf61deaab7a7b8f32bddb 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -72,6 +72,8 @@ pub const DebugFormat = union(enum) { strip, dwarf: std.dwarf.Format, code_view, + /// Non-semantic Shader Debug Info + nsdi, }; pub const Options = struct { @@ -517,7 +519,8 @@ pub fn resolve(options: Options) ResolveError!Config { .windows, .uefi => .code_view, else => .{ .dwarf = .@"32" }, }, - .spirv, .hex, .raw, .plan9 => .strip, + .spirv => .nsdi, + .hex, .raw, .plan9 => .strip, }; }; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 279979085b12c4da65da09f2868676ca567af7e0..79c7d9aeb89a91cf41e9f3588941bc740baf933e 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -455,6 +455,7 @@ pub const Object = struct { switch (comp.config.debug_format) { .strip => unreachable, + .nsdi => {}, .dwarf => |f| { module_flags.appendAssumeCapacity(try o.builder.metadataTuple(&.{ behavior_max, diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 08ba32f95e93d7a5c267137a9323187b7f12924b..ff02d7932fb9036abd576e80b22b9cbd0bd81e76 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -224,7 +224,7 @@ pub fn init(self: *ZigObject, elf_file: *Elf, options: InitOptions) !void { try dwarf.initMetadata(); self.dwarf = dwarf; }, - .code_view => unreachable, + .code_view, .nsdi => unreachable, } } diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index a585287261a2d73f365a4cb22ff89f041ddf7e2b..099dc580aebf141346b7a6e5d321da69d5ada577 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -5146,7 +5146,7 @@ fn create( .dwarf = .init(&elf.base, switch (comp.config.debug_format) { .strip => .@"32", // for .eh_frame .dwarf => |v| v, - .code_view => unreachable, + .code_view, .nsdi => unreachable, }), .dwarf_shared = comptime .initFill(.{ .first_target_reloc = .none, @@ -5261,7 +5261,7 @@ fn initHeaders( const have_debug_frame = machine == .X86_64 and switch (comp.config.debug_format) { .strip => false, .dwarf => !comp.config.any_unwind_tables, - .code_view => unreachable, + .code_view, .nsdi => unreachable, }; const addr_align: Alignment = switch (class) { .NONE, _ => unreachable, @@ -5322,7 +5322,7 @@ fn initHeaders( shnum += 1; // .debug_str shnum += 1; // .debug_str_offsets }, - .code_view => unreachable, + .code_view, .nsdi => unreachable, } if (@"type" != .REL) { shnum += 1; // .got @@ -6548,7 +6548,7 @@ fn initHeaders( .node_align = elf.mf.flags.block_size, }); }, - .code_view => unreachable, + .code_view, .nsdi => unreachable, } // If we have an entry point symbol, create the undefined reference now. @@ -10400,7 +10400,7 @@ fn updateContainerTypeInner( try elf.dwarf.const_pool.updateContainerType(pt, .{ .elf2 = elf }, ty, success); try elf.dwarf.const_pool.flushPending(pt, .{ .elf2 = elf }); }, - .code_view => unreachable, + .code_view, .nsdi => unreachable, } if (!success) return; var lazy_it = elf.lazy.iterator(); @@ -10426,7 +10426,7 @@ pub fn addConst( try elf.dwarf_consts.ensureUnusedCapacity(gpa, 1); try elf.dwarf.addConst(cpi, val, &addConstNode); }, - .code_view => unreachable, + .code_view, .nsdi => unreachable, } } fn addConstNode( @@ -10502,7 +10502,7 @@ fn updateConstInner( } try elf.genPending(pt); }, - .code_view => unreachable, + .code_view, .nsdi => unreachable, } } @@ -11259,7 +11259,7 @@ fn genPending(elf: *Elf, pt: Zcu.PerThread) link.Error!void { elf.resetNodeRelocs(debug_info_ni); try elf.dwarf.genDecl(pt, &di_nw, pending.instance); }, - .code_view => unreachable, + .code_view, .nsdi => unreachable, } try elf.genPendingDebug(gpa); assert(elf.pending_uavs.items.len == 0); // no UAVs added by lazy code/data or by debug info diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index dd4acfd15ab7be86de4cb2a7bd9f86709ce4a93d..b4ac58529b1b2ef37c659dbc0320144eaa2814fa 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -62,7 +62,7 @@ pub fn init(self: *ZigObject, macho_file: *MachO) !void { self.debug_info_header_dirty = true; self.debug_line_header_dirty = true; }, - .code_view => unreachable, + .code_view, .nsdi => unreachable, } } diff --git a/src/link/Spirv/Flush.zig b/src/link/Spirv/Flush.zig index c07873869fafb1415441331758c9552f9d26132f..a6cb06ad0cd3bb6928f7f920e840b4205daffd32 100644 --- a/src/link/Spirv/Flush.zig +++ b/src/link/Spirv/Flush.zig @@ -2,6 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const Path = std.Build.Cache.Path; const assert = std.debug.assert; +const stringToEnum = std.meta.stringToEnum; const InternPool = @import("../../InternPool.zig"); const link = @import("../../link.zig"); const Type = @import("../../Type.zig"); @@ -34,6 +35,7 @@ decorations: Section, globals: Section, declarations: Section, functions: Section, +nonsemantic_info: bool = false, const Symbol = struct { id: Id, @@ -42,7 +44,7 @@ const Symbol = struct { }; const Annotation = struct { - target: Word, + target_id: Word, class: spec.Class, start: usize, end: usize, @@ -224,6 +226,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { const gpa = comp.gpa; const arena = flush.arena; const diags = &comp.link_diags; + const target = &comp.root_mod.resolved_target.result; const ids = try gpa.alloc(Id, mir.id_bound); defer gpa.free(ids); @@ -264,7 +267,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { offset += inst.len; if (header.opcode == .OpDecorate) { - const target = inst[1]; + const target_id = inst[1]; const decoration: spec.Decoration = @fromBackingInt(inst[2]); if (decoration == .linkage_attributes) { const parameters = inst[3..]; @@ -273,7 +276,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { const linkage: spec.LinkageType = @fromBackingInt(parameters[parameters.len - 1]); const gop = try flush.symbols.getOrPut(arena, name); if (!gop.found_existing) gop.value_ptr.* = .{ - .id = ids[target], + .id = ids[target_id], .defined = false, .declaration = &.{}, }; @@ -285,19 +288,19 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { } gop.value_ptr.defined = true; }, - .import => try imports.put(gpa, target, @intCast(gop.index)), + .import => try imports.put(gpa, target_id, @intCast(gop.index)), .link_once_odr => { return diags.failParse(path.?, "unsupported linkage '{t}'", .{linkage}); }, } - ids[target] = gop.value_ptr.id; + ids[target_id] = gop.value_ptr.id; continue; } } const class = header.opcode.class(); - const target: Word = switch (header.opcode) { + const target_id: Word = switch (header.opcode) { .OpName, .OpMemberName => inst[1], .OpDecorationGroup, .OpGroupDecorate, .OpGroupMemberDecorate => { 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 { else => if (class == .annotation) inst[1] else continue, }; try annotations.append(gpa, .{ - .target = target, + .target_id = target_id, .class = class, .start = start, .end = offset, }); } + var result_ids: std.ArrayList(Word) = .empty; + defer result_ids.deinit(gpa); + const starts = try gpa.alloc(u32, mir.id_bound + 1); defer gpa.free(starts); @memset(starts, 0); - for (annotations.items) |annotation| starts[annotation.target + 1] += 1; + for (annotations.items) |annotation| starts[annotation.target_id + 1] += 1; for (1..starts.len) |i| starts[i] += starts[i - 1]; - const cursors = try gpa.dupe(u32, starts); - defer gpa.free(cursors); - const by_target = try gpa.alloc(u32, annotations.items.len); - defer gpa.free(by_target); - for (annotations.items, 0..) |annotation, i| { - by_target[cursors[annotation.target]] = @intCast(i); - cursors[annotation.target] += 1; + const grouped_indices = try gpa.alloc(u32, annotations.items.len); + defer gpa.free(grouped_indices); + { + const next_slot = try gpa.dupe(u32, starts); + defer gpa.free(next_slot); + for (annotations.items, 0..) |annotation, i| { + grouped_indices[next_slot[annotation.target_id]] = @intCast(i); + next_slot[annotation.target_id] += 1; + } } - var results: std.ArrayList(Word) = .empty; - defer results.deinit(gpa); - offset = 0; while (offset < words.len) { const start = offset; @@ -340,25 +345,67 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { input.mark += 1; flush.deps.clearRetainingCapacity(); - const result_index = header.opcode.resultIndex() orelse { - switch (header.opcode) { - .OpEntryPoint => { - try input.copy(flush, &flush.object_entry_points, start, offset, null); - }, - .OpExecutionMode, .OpExecutionModeId => { - try input.copy(flush, &flush.object_execution_modes, start, offset, null); - }, - .OpTypeForwardPointer => { - forward_pointers.set(inst[1]); - try input.copy(flush, &flush.globals, start, offset, null); - }, - else => {}, - } - continue; + const result_index = header.opcode.resultIndex() orelse switch (header.opcode) { + .OpCapability => { + const capability = std.enums.fromInt(spec.Capability, inst[1]) orelse { + return diags.failParse(path.?, "unknown capability '{d}'", .{inst[1]}); + }; + + // TODO: this isn't a full validation. we probably have to automate this + // based on some generated info from SPIRV-Headers/SPIRV-Registry + const allowed = switch (capability) { + .linkage => true, + .shader => target.os.tag == .vulkan or target.os.tag == .opengl, + .kernel, .addresses => target.os.tag == .opencl, + .physical_storage_buffer_addresses => target.os.tag == .vulkan and target.cpu.arch == .spirv64, + else => if (stringToEnum(std.Target.spirv.Feature, @tagName(capability))) |f| target.cpu.has(.spirv, f) else false, + }; + if (!allowed) return diags.failParse( + path.?, + "SPIR-V capability '{t}' is not enabled by the target feature set", + .{capability}, + ); + continue; + }, + .OpExtension => { + const name = std.mem.sliceTo(std.mem.sliceAsBytes(inst[1..]), 0); + if (std.mem.eql(u8, name, "SPV_KHR_non_semantic_info")) { + flush.nonsemantic_info = true; + continue; + } + + const allowed = allowed: { + if (stringToEnum(std.Target.spirv.Feature, name)) |f| { + break :allowed target.cpu.has(.spirv, f); + } + break :allowed true; + }; + if (!allowed) return diags.failParse( + path.?, + "SPIR-V extension '{s}' is not enabled by the target feature set", + .{name}, + ); + continue; + }, + .OpEntryPoint => { + try input.copy(flush, &flush.object_entry_points, start, offset, null); + continue; + }, + .OpExecutionMode, .OpExecutionModeId => { + try input.copy(flush, &flush.object_execution_modes, start, offset, null); + continue; + }, + .OpTypeForwardPointer => { + forward_pointers.set(inst[1]); + try input.copy(flush, &flush.globals, start, offset, null); + continue; + }, + else => continue, }; - const result = inst[result_index]; - results.clearRetainingCapacity(); - try results.append(gpa, result); + + const result_id = inst[result_index]; + result_ids.clearRetainingCapacity(); + try result_ids.append(gpa, result_id); var has_body = false; if (header.opcode == .OpFunction) { @@ -366,16 +413,16 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { const body: spec.InstructionHeader = @bitCast(words[offset]); if (body.opcode == .OpLabel) has_body = true; if (body.opcode.resultIndex()) |index| { - try results.append(gpa, words[offset + index]); + try result_ids.append(gpa, words[offset + index]); } offset += body.word_count; if (body.opcode == .OpFunctionEnd) break; } } - for (results.items) |own| input.marks[own] = input.mark; + for (result_ids.items) |own| input.marks[own] = input.mark; - if (imports.get(result)) |symbol_index| { + if (imports.get(result_id)) |symbol_index| { flush.scratch.reset(); try input.copy(flush, &flush.scratch, start, offset, null); 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 { switch (header.opcode) { .OpFunction => if (!has_body) return diags.failParse(path.?, "function declaration without linkage", .{}), - .OpVariable => try flush.variables.put(arena, ids[result], @fromBackingInt(inst[3])), - else => if (!forward_pointers.isSet(result)) { + .OpVariable => try flush.variables.put(arena, ids[result_id], @fromBackingInt(inst[3])), + else => if (!forward_pointers.isSet(result_id)) { flush.scratch.reset(); try input.copy(flush, &flush.scratch, start, offset, start + result_index); - for (by_target[starts[result]..starts[result + 1]]) |index| { + for (grouped_indices[starts[result_id]..starts[result_id + 1]]) |index| { const annotation = annotations.items[index]; if (annotation.class != .annotation) continue; - const target = annotation.start + 1; - try input.copy(flush, &flush.scratch, annotation.start, annotation.end, target); + const target_id = annotation.start + 1; + try input.copy(flush, &flush.scratch, annotation.start, annotation.end, target_id); } const key = std.mem.sliceAsBytes(flush.scratch.instructions.items); const gop = try flush.interned.getOrPut(arena, key); if (gop.found_existing) { - ids[result] = gop.value_ptr.*; + ids[result_id] = gop.value_ptr.*; continue; } gop.key_ptr.* = try arena.dupe(u8, key); - gop.value_ptr.* = ids[result]; + gop.value_ptr.* = ids[result_id]; }, } - for (results.items) |target| { - for (by_target[starts[target]..starts[target + 1]]) |index| { + for (result_ids.items) |target_id| { + for (grouped_indices[starts[target_id]..starts[target_id + 1]]) |index| { const annotation = annotations.items[index]; const section = switch (annotation.class) { .debug => &flush.names, @@ -428,7 +475,7 @@ fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { try input.copy(flush, section, start, offset, null); if (header.opcode == .OpFunction) { const references = try arena.dupe(Id, flush.deps.items); - try flush.function_references.put(arena, ids[result], references); + try flush.function_references.put(arena, ids[result_id], references); } } @@ -468,25 +515,25 @@ fn emitCapabilities(flush: *Flush, out: *Section) link.Error!void { try out.emit(gpa, .OpCapability, .{ .capability = .linkage }); } - inline for (@typeInfo(spec.Capability).@"enum".field_names) |name| { - if (@hasField(std.Target.spirv.Feature, name)) { - const feature = @field(std.Target.spirv.Feature, name); - if (target.cpu.has(.spirv, feature)) { - try out.emit(gpa, .OpCapability, .{ .capability = @field(spec.Capability, name) }); - } - } + for (std.enums.values(spec.Capability)) |capability| { + const feature = stringToEnum(std.Target.spirv.Feature, @tagName(capability)) orelse continue; + if (!target.cpu.has(.spirv, feature)) continue; + try out.emit(gpa, .OpCapability, .{ .capability = capability }); } if (target.cpu.arch == .spirv64 and target.os.tag == .vulkan) { try out.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_physical_storage_buffer" }); } - inline for (@typeInfo(spec.Extension).@"enum".field_names) |name| { - if (comptime std.mem.startsWith(u8, name, "v1_")) continue; - if (@hasField(std.Target.spirv.Feature, name)) { - const feature = @field(std.Target.spirv.Feature, name); - if (target.cpu.has(.spirv, feature)) try out.emit(gpa, .OpExtension, .{ .name = name }); - } + for (std.enums.values(spec.Extension)) |extension| { + const name = @tagName(extension); + if (std.mem.startsWith(u8, name, "v1_")) continue; + const feature = stringToEnum(std.Target.spirv.Feature, name) orelse continue; + if (!target.cpu.has(.spirv, feature)) continue; + try out.emit(gpa, .OpExtension, .{ .name = name }); + } + if (flush.nonsemantic_info) { + try out.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_non_semantic_info" }); } } diff --git a/src/main.zig b/src/main.zig index 49838feb8beb6de697b763fc6852054050033a6e..af4f98fbe6ce50eb9779dbd1b951ad7530e23506 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3582,6 +3582,35 @@ fn buildOutputType( @import("codegen/llvm/bindings.zig").ParseCommandLineOptions(argv.len, &argv); } + if (build_options.have_llvm and create_module.resolved_options.use_llvm and target.cpu.arch.isSpirV()) { + // LLVM has no way to set this non-globally other + // than SPIRVTranslate() but it cannot emits assembly. + var extensions: std.ArrayList(u8) = .empty; + defer extensions.deinit(gpa); + try extensions.appendSlice(gpa, "--spirv-ext="); + + var has_extension = false; + if (!root_mod.strip) { + try extensions.appendSlice(gpa, "+SPV_KHR_non_semantic_info"); + has_extension = true; + } + + for (std.enums.values(std.Target.spirv.Feature)) |feature| { + const name = @tagName(feature); + if (!mem.startsWith(u8, name, "SPV_")) continue; + if (!target.cpu.has(.spirv, feature)) continue; + if (mem.eql(u8, name, "SPV_KHR_variable_pointers") and target.cpu.has(.spirv, .v1_3)) continue; + + if (has_extension) try extensions.append(gpa, ','); + try extensions.append(gpa, '+'); + try extensions.appendSlice(gpa, name); + has_extension = true; + } + + const argv = [_][*:0]const u8{ "zig (LLVM option parsing)", try extensions.toOwnedSliceSentinel(gpa, 0) }; + @import("codegen/llvm/bindings.zig").ParseCommandLineOptions(argv.len, &argv); + } + const clang_passthrough_mode = switch (arg_mode) { .cc, .cpp, .translate_c => true, else => false, -- 2.54.0