1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const Path = std.Build.Cache.Path;
4const assert = std.debug.assert;
5const stringToEnum = std.meta.stringToEnum;
6const InternPool = @import("../../InternPool.zig");
7const link = @import("../../link.zig");
8const Type = @import("../../Type.zig");
9const Linker = @import("../Spirv.zig");
10const Mir = @import("../../codegen/spirv/Mir.zig");
11const spec = @import("../../codegen/spirv/spec.zig");
12const Section = @import("../../codegen/spirv/Section.zig");
13const zig_version = @import("builtin").zig_version;
14const Id = spec.Id;
15const Word = spec.Word;
16const Flush = @This();
17
18linker: *Linker,
19arena: Allocator,
20id_bound: Word,
21nonsemantic_info: bool,
22navs: std.array_hash_map.Auto(InternPool.Nav.Index, Id),
23symbols: std.array_hash_map.String(Symbol),
24interned: std.array_hash_map.String(Id),
25variables: std.array_hash_map.Auto(Id, spec.StorageClass),
26fn_refs: std.array_hash_map.Auto(Id, []const Id),
27entry_points: std.ArrayList(Mir.EntryPoint),
28deps: std.ArrayList(Id),
29scratch: Section,
30extended_instruction_sets: Section,
31object_entry_points: Section,
32object_execution_modes: Section,
33debug_strings: Section,
34names: Section,
35decorations: Section,
36globals: Section,
37declarations: Section,
38functions: Section,
39
40const Symbol = struct {
41 id: Id,
42 defined: bool,
43 declaration: []const Word,
44};
45
46const Annotation = struct {
47 target_id: Word,
48 class: spec.Class,
49 start: usize,
50 end: usize,
51};
52
53const Input = struct {
54 mir: *const Mir,
55 ids: []Id,
56 marks: []u32,
57 mark: u32,
58
59 fn copy(
60 input: *Input,
61 flush: *Flush,
62 dest: *Section,
63 start: usize,
64 end: usize,
65 result_index: ?usize,
66 ) !void {
67 const words = input.mir.module.instructions.items;
68 try dest.ensureUnusedCapacity(flush.arena, end - start);
69 for (words[start..end], start..) |word, i| {
70 if (i == result_index) {
71 dest.writeWord(0);
72 continue;
73 }
74 if (!input.mir.module.ids.isSet(i)) {
75 dest.writeWord(word);
76 continue;
77 }
78 if (input.marks[word] != input.mark) {
79 input.marks[word] = input.mark;
80 try flush.deps.append(flush.arena, input.ids[word]);
81 }
82 dest.writeWord(@backingInt(input.ids[word]));
83 }
84 }
85};
86
87fn allocId(flush: *Flush) Id {
88 const id: Id = @fromBackingInt(flush.id_bound);
89 flush.id_bound += 1;
90 return id;
91}
92
93fn navId(flush: *Flush, nav: InternPool.Nav.Index) !Id {
94 const gop = try flush.navs.getOrPut(flush.arena, nav);
95 if (!gop.found_existing) gop.value_ptr.* = flush.allocId();
96 return gop.value_ptr.*;
97}
98
99pub fn emitModule(flush: *Flush, out: *Section) link.Error!void {
100 const linker = flush.linker;
101 const comp = linker.base.comp;
102 const zcu = comp.zcu.?;
103 const ip = &zcu.intern_pool;
104 const gpa = comp.gpa;
105 const arena = flush.arena;
106 const diags = &comp.link_diags;
107 const target = &comp.root_mod.resolved_target.result;
108 const is_object = comp.config.output_mode == .Obj;
109
110 for (linker.exports.keys(), linker.exports.values()) |nav_index, name| {
111 if (!linker.mirs.contains(nav_index)) {
112 assert(zcu.failed_codegen.contains(nav_index));
113 return error.AlreadyReported;
114 }
115
116 const id = try flush.navId(nav_index);
117 const nav_ty: Type = .fromInterned(ip.getNav(nav_index).resolved.?.type);
118 if (nav_ty.zigTypeTag(zcu) == .@"fn") {
119 const cc = nav_ty.fnCallingConvention(zcu);
120 if (cc != .spirv_device) {
121 try flush.entry_points.append(arena, .{
122 .id = id,
123 .name = name.toSlice(ip),
124 .cc = cc,
125 });
126 continue;
127 }
128 }
129 if (!is_object) continue;
130
131 try flush.symbols.putNoClobber(arena, name.toSlice(ip), .{
132 .id = id,
133 .defined = true,
134 .declaration = &.{},
135 });
136 }
137
138 for (linker.objects.items) |*object| {
139 try flush.load(&object.mir, object.path);
140 }
141
142 const references = try zcu.resolveReferences();
143 for (linker.mirs.keys(), linker.mirs.values()) |nav_index, *mir| {
144 const nav = ip.getNav(nav_index);
145 if (nav.getExtern(ip) != null) continue;
146
147 const unit: InternPool.AnalUnit = switch (ip.indexToKey(nav.resolved.?.type)) {
148 .func_type => .wrap(.{ .func = zcu.navValue(nav_index).toIntern() }),
149 else => .wrap(.{ .nav_val = nav_index }),
150 };
151 if (references.contains(unit)) try flush.load(mir, null);
152 }
153
154 for (linker.mirs.keys(), linker.mirs.values()) |nav_index, *mir| {
155 if (ip.getNav(nav_index).getExtern(ip) == null) continue;
156 if (flush.navs.contains(nav_index)) try flush.load(mir, null);
157 }
158
159 var failed = false;
160 for (flush.navs.keys()) |nav_index| {
161 if (linker.mirs.contains(nav_index) or zcu.failed_codegen.contains(nav_index)) continue;
162 diags.addError("undefined reference to '{f}'", .{ip.getNav(nav_index).fqn.fmt(ip)});
163 failed = true;
164 }
165 for (flush.symbols.keys(), flush.symbols.values()) |name, symbol| {
166 if (is_object) {
167 try flush.decorations.emit(arena, .OpDecorate, .{
168 .target = symbol.id,
169 .decoration = .{ .linkage_attributes = .{
170 .name = name,
171 .linkage_type = if (symbol.defined) .@"export" else .import,
172 } },
173 });
174 }
175 if (symbol.defined) continue;
176 if (!is_object or symbol.declaration.len == 0) {
177 diags.addError("undefined symbol '{s}'", .{name});
178 failed = true;
179 continue;
180 }
181 const header: spec.InstructionHeader = @bitCast(symbol.declaration[0]);
182 const section = if (header.opcode == .OpVariable) &flush.globals else &flush.declarations;
183 try section.writeWords(arena, symbol.declaration);
184 }
185 if (failed) return error.AlreadyReported;
186
187 var version: spec.Version = .{ .major = 1, .minor = 0 };
188 for ([_]std.Target.spirv.Feature{
189 .v1_1,
190 .v1_2,
191 .v1_3,
192 .v1_4,
193 .v1_5,
194 .v1_6,
195 }) |feature| {
196 if (target.cpu.has(.spirv, feature)) version.minor += 1;
197 }
198 const zig_packed_version = (zig_version.major << 12) | (zig_version.minor << 7) | zig_version.patch;
199 const header: spec.Header = .{
200 .magic = spec.magic_number,
201 .version = version,
202 .generator = .{ .tool = spec.zig_generator_id, .version = zig_packed_version },
203 .id_bound = flush.id_bound,
204 .schema = 0,
205 };
206
207 try out.writeWords(gpa, @ptrCast(@alignCast(&header)));
208 try flush.emitCapabilities(out);
209 try out.writeWords(gpa, flush.extended_instruction_sets.instructions.items);
210 try flush.emitMemoryModel(out);
211 try flush.emitEntryPoints(out);
212 try out.writeWords(gpa, flush.object_entry_points.instructions.items);
213 try flush.emitExecutionModes(out);
214 try out.writeWords(gpa, flush.object_execution_modes.instructions.items);
215 try out.writeWords(gpa, flush.debug_strings.instructions.items);
216 try flush.emitSourceInfo(out, zig_packed_version);
217 try out.writeWords(gpa, flush.names.instructions.items);
218 try out.writeWords(gpa, flush.decorations.instructions.items);
219 try out.writeWords(gpa, flush.globals.instructions.items);
220 try out.writeWords(gpa, flush.declarations.instructions.items);
221 try out.writeWords(gpa, flush.functions.instructions.items);
222}
223
224fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void {
225 const comp = flush.linker.base.comp;
226 const gpa = comp.gpa;
227 const arena = flush.arena;
228 const diags = &comp.link_diags;
229 const target = &comp.root_mod.resolved_target.result;
230 const ids = try gpa.alloc(Id, mir.id_bound);
231 defer gpa.free(ids);
232 ids[0] = .none;
233 for (ids[1..], flush.id_bound..) |*id, i| id.* = @fromBackingInt(@intCast(i));
234 flush.id_bound += mir.id_bound - 1;
235
236 const marks = try gpa.alloc(u32, mir.id_bound);
237 defer gpa.free(marks);
238 @memset(marks, 0);
239 var input: Input = .{ .mir = mir, .ids = ids, .marks = marks, .mark = 0 };
240
241 var forward_pointers: std.bit_set.Dynamic = try .initEmpty(gpa, mir.id_bound);
242 defer forward_pointers.deinit(gpa);
243 var imports: std.array_hash_map.Auto(Word, u32) = .empty;
244 defer imports.deinit(gpa);
245 var annotations: std.ArrayList(Annotation) = .empty;
246 defer annotations.deinit(gpa);
247
248 for (mir.nav_refs) |ref| ids[@backingInt(ref.id)] = try flush.navId(ref.nav);
249 for (mir.externs) |ext| {
250 const gop = try flush.symbols.getOrPut(arena, ext.name);
251 if (!gop.found_existing) gop.value_ptr.* = .{
252 .id = ids[@backingInt(ext.id)],
253 .defined = false,
254 .declaration = &.{},
255 };
256 ids[@backingInt(ext.id)] = gop.value_ptr.id;
257 try imports.put(gpa, @backingInt(ext.id), @intCast(gop.index));
258 }
259
260 var it = mir.module.iterate();
261 while (it.next()) |inst| {
262 const opcode = inst.opcode;
263 const words = inst.words;
264
265 if (opcode == .OpDecorate) {
266 const target_id = words[1];
267 const decoration: spec.Decoration = @fromBackingInt(words[2]);
268 if (decoration == .linkage_attributes) {
269 const parameters = words[3..];
270 const name_words = parameters[0 .. parameters.len - 1];
271 const name = std.mem.sliceTo(std.mem.sliceAsBytes(name_words), 0);
272 const linkage: spec.LinkageType = @fromBackingInt(parameters[parameters.len - 1]);
273 const gop = try flush.symbols.getOrPut(arena, name);
274 if (!gop.found_existing) gop.value_ptr.* = .{
275 .id = ids[target_id],
276 .defined = false,
277 .declaration = &.{},
278 };
279
280 switch (linkage) {
281 .@"export" => {
282 if (gop.value_ptr.defined) {
283 return diags.failParse(path.?, "duplicate export '{s}'", .{name});
284 }
285 gop.value_ptr.defined = true;
286 },
287 .import => try imports.put(gpa, target_id, @intCast(gop.index)),
288 .link_once_odr => {
289 return diags.failParse(path.?, "unsupported linkage '{t}'", .{linkage});
290 },
291 }
292
293 ids[target_id] = gop.value_ptr.id;
294 continue;
295 }
296 }
297
298 const class = opcode.class();
299 const target_id: Word = switch (opcode) {
300 .OpName, .OpMemberName => words[1],
301 .OpDecorationGroup, .OpGroupDecorate, .OpGroupMemberDecorate => {
302 return diags.failParse(path.?, "decoration groups are not supported", .{});
303 },
304 else => if (class == .annotation) words[1] else continue,
305 };
306 try annotations.append(gpa, .{
307 .target_id = target_id,
308 .class = class,
309 .start = inst.start,
310 .end = inst.end,
311 });
312 }
313
314 var result_ids: std.ArrayList(Word) = .empty;
315 defer result_ids.deinit(gpa);
316
317 const starts = try gpa.alloc(u32, mir.id_bound + 1);
318 defer gpa.free(starts);
319 @memset(starts, 0);
320
321 for (annotations.items) |annotation| starts[annotation.target_id + 1] += 1;
322 for (1..starts.len) |i| starts[i] += starts[i - 1];
323
324 const grouped_indices = try gpa.alloc(u32, annotations.items.len);
325 defer gpa.free(grouped_indices);
326 {
327 const next_slot = try gpa.dupe(u32, starts);
328 defer gpa.free(next_slot);
329 for (annotations.items, 0..) |annotation, i| {
330 grouped_indices[next_slot[annotation.target_id]] = @intCast(i);
331 next_slot[annotation.target_id] += 1;
332 }
333 }
334
335 it = mir.module.iterate();
336 while (it.next()) |inst| {
337 const start = inst.start;
338 var end = inst.end;
339 const opcode = inst.opcode;
340 const words = inst.words;
341 input.mark += 1;
342 flush.deps.clearRetainingCapacity();
343
344 const result_index = opcode.resultIndex() orelse switch (opcode) {
345 .OpCapability => {
346 const capability = std.enums.fromInt(spec.Capability, words[1]) orelse {
347 return diags.failParse(path.?, "unknown capability '{d}'", .{words[1]});
348 };
349
350 // TODO: this isn't a full validation. we probably have to automate this
351 // based on some generated info from SPIRV-Headers/SPIRV-Registry
352 const allowed = switch (capability) {
353 .linkage => true,
354 .shader => target.os.tag == .vulkan or target.os.tag == .opengl,
355 .kernel, .addresses => target.os.tag == .opencl,
356 .physical_storage_buffer_addresses => target.os.tag == .vulkan and target.cpu.arch == .spirv64,
357 else => if (stringToEnum(std.Target.spirv.Feature, @tagName(capability))) |f| target.cpu.has(.spirv, f) else false,
358 };
359 if (!allowed) return diags.failParse(
360 path.?,
361 "SPIR-V capability '{t}' is not enabled by the target feature set",
362 .{capability},
363 );
364 continue;
365 },
366 .OpExtension => {
367 const name = std.mem.sliceTo(std.mem.sliceAsBytes(words[1..]), 0);
368 if (std.mem.eql(u8, name, "SPV_KHR_non_semantic_info")) {
369 flush.nonsemantic_info = true;
370 continue;
371 }
372
373 const allowed = allowed: {
374 if (stringToEnum(std.Target.spirv.Feature, name)) |f| {
375 break :allowed target.cpu.has(.spirv, f);
376 }
377 break :allowed true;
378 };
379 if (!allowed) return diags.failParse(
380 path.?,
381 "SPIR-V extension '{s}' is not enabled by the target feature set",
382 .{name},
383 );
384 continue;
385 },
386 .OpEntryPoint => {
387 try input.copy(flush, &flush.object_entry_points, start, end, null);
388 continue;
389 },
390 .OpExecutionMode, .OpExecutionModeId => {
391 try input.copy(flush, &flush.object_execution_modes, start, end, null);
392 continue;
393 },
394 .OpTypeForwardPointer => {
395 forward_pointers.set(words[1]);
396 try input.copy(flush, &flush.globals, start, end, null);
397 continue;
398 },
399 else => continue,
400 };
401
402 const result_id = words[result_index];
403 result_ids.clearRetainingCapacity();
404 try result_ids.append(gpa, result_id);
405
406 var has_body = false;
407 if (opcode == .OpFunction) {
408 while (it.next()) |body| {
409 end = body.end;
410 if (body.opcode == .OpLabel) has_body = true;
411 if (body.result_id != .none) try result_ids.append(gpa, @backingInt(body.result_id));
412 if (body.opcode == .OpFunctionEnd) break;
413 }
414 }
415
416 for (result_ids.items) |own| input.marks[own] = input.mark;
417
418 if (imports.get(result_id)) |symbol_index| {
419 flush.scratch.reset();
420 try input.copy(flush, &flush.scratch, start, end, null);
421 const declaration = try arena.dupe(Word, flush.scratch.instructions.items);
422 flush.symbols.values()[symbol_index].declaration = declaration;
423 continue;
424 }
425
426 switch (opcode) {
427 .OpFunction => if (!has_body) return diags.failParse(path.?, "function declaration without linkage", .{}),
428 .OpVariable => try flush.variables.put(arena, ids[result_id], @fromBackingInt(words[3])),
429 else => if (!forward_pointers.isSet(result_id)) {
430 flush.scratch.reset();
431 try input.copy(flush, &flush.scratch, start, end, start + result_index);
432 for (grouped_indices[starts[result_id]..starts[result_id + 1]]) |index| {
433 const annotation = annotations.items[index];
434 if (annotation.class != .annotation) continue;
435 const target_id = annotation.start + 1;
436 try input.copy(flush, &flush.scratch, annotation.start, annotation.end, target_id);
437 }
438
439 const key = std.mem.sliceAsBytes(flush.scratch.instructions.items);
440 const gop = try flush.interned.getOrPut(arena, key);
441 if (gop.found_existing) {
442 ids[result_id] = gop.value_ptr.*;
443 continue;
444 }
445 gop.key_ptr.* = try arena.dupe(u8, key);
446 gop.value_ptr.* = ids[result_id];
447 },
448 }
449
450 for (result_ids.items) |target_id| {
451 for (grouped_indices[starts[target_id]..starts[target_id + 1]]) |index| {
452 const annotation = annotations.items[index];
453 const section = switch (annotation.class) {
454 .debug => &flush.names,
455 .annotation => &flush.decorations,
456 else => unreachable,
457 };
458 try input.copy(flush, section, annotation.start, annotation.end, null);
459 }
460 }
461
462 const section = switch (opcode) {
463 .OpFunction => &flush.functions,
464 .OpExtInstImport => &flush.extended_instruction_sets,
465 .OpString => &flush.debug_strings,
466 else => &flush.globals,
467 };
468 try input.copy(flush, section, start, end, null);
469 if (opcode == .OpFunction) {
470 const references = try arena.dupe(Id, flush.deps.items);
471 try flush.fn_refs.put(arena, ids[result_id], references);
472 }
473 }
474
475 for (mir.entry_points) |entry_point| {
476 try flush.entry_points.append(arena, .{
477 .id = ids[@backingInt(entry_point.id)],
478 .name = entry_point.name,
479 .cc = entry_point.cc,
480 });
481 }
482}
483
484fn emitCapabilities(flush: *Flush, out: *Section) link.Error!void {
485 const comp = flush.linker.base.comp;
486 const target = &comp.root_mod.resolved_target.result;
487 const gpa = comp.gpa;
488
489 switch (target.os.tag) {
490 .opengl, .vulkan => try out.emit(gpa, .OpCapability, .{ .capability = .shader }),
491 .opencl => {
492 try out.emit(gpa, .OpCapability, .{ .capability = .kernel });
493 try out.emit(gpa, .OpCapability, .{ .capability = .addresses });
494 },
495 else => unreachable,
496 }
497
498 if (target.cpu.arch == .spirv64) {
499 if (!target.cpu.has(.spirv, .int64)) {
500 try out.emit(gpa, .OpCapability, .{ .capability = .int64 });
501 }
502 if (target.os.tag == .vulkan) {
503 try out.emit(gpa, .OpCapability, .{ .capability = .physical_storage_buffer_addresses });
504 }
505 }
506
507 if (comp.config.output_mode == .Obj and flush.symbols.count() > 0) {
508 try out.emit(gpa, .OpCapability, .{ .capability = .linkage });
509 }
510
511 for (std.enums.values(spec.Capability)) |capability| {
512 const feature = stringToEnum(std.Target.spirv.Feature, @tagName(capability)) orelse continue;
513 if (!target.cpu.has(.spirv, feature)) continue;
514 try out.emit(gpa, .OpCapability, .{ .capability = capability });
515 }
516
517 if (target.cpu.arch == .spirv64 and target.os.tag == .vulkan) {
518 try out.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_physical_storage_buffer" });
519 }
520
521 for (std.enums.values(spec.Extension)) |extension| {
522 const name = @tagName(extension);
523 if (std.mem.startsWith(u8, name, "v1_")) continue;
524 const feature = stringToEnum(std.Target.spirv.Feature, name) orelse continue;
525 if (!target.cpu.has(.spirv, feature)) continue;
526 try out.emit(gpa, .OpExtension, .{ .name = name });
527 }
528
529 if (flush.nonsemantic_info) {
530 try out.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_non_semantic_info" });
531 }
532}
533
534fn emitMemoryModel(flush: *Flush, out: *Section) link.Error!void {
535 const comp = flush.linker.base.comp;
536 const target = &comp.root_mod.resolved_target.result;
537 const addressing_model: spec.AddressingModel = switch (target.os.tag) {
538 .opengl => .logical,
539 .vulkan => switch (target.cpu.arch) {
540 .spirv32 => .logical,
541 .spirv64 => .physical_storage_buffer64,
542 else => unreachable,
543 },
544 .opencl => switch (target.cpu.arch) {
545 .spirv32 => .physical32,
546 .spirv64 => .physical64,
547 else => unreachable,
548 },
549 else => unreachable,
550 };
551 try out.emit(comp.gpa, .OpMemoryModel, .{
552 .addressing_model = addressing_model,
553 .memory_model = switch (target.os.tag) {
554 .opencl => .open_cl,
555 .vulkan, .opengl => .glsl450,
556 else => unreachable,
557 },
558 });
559}
560
561fn emitEntryPoints(flush: *Flush, out: *Section) link.Error!void {
562 const comp = flush.linker.base.comp;
563 const gpa = comp.gpa;
564 const arena = flush.arena;
565 const target = &comp.root_mod.resolved_target.result;
566 const v1_4 = target.cpu.has(.spirv, .v1_4);
567
568 var visited: std.bit_set.Dynamic = try .initEmpty(arena, flush.id_bound);
569 var pending: std.ArrayList(Id) = .empty;
570 var interface: std.ArrayList(Id) = .empty;
571 for (flush.entry_points.items) |entry_point| {
572 const execution_model: spec.ExecutionModel = switch (entry_point.cc) {
573 .spirv_kernel => if (target.os.tag == .opencl) .kernel else .gl_compute,
574 .spirv_vertex => .vertex,
575 .spirv_fragment => .fragment,
576 .spirv_task => .task_ext,
577 .spirv_mesh => .mesh_ext,
578 else => unreachable,
579 };
580
581 visited.unsetAll();
582 interface.clearRetainingCapacity();
583 try pending.append(arena, entry_point.id);
584 while (pending.pop()) |id| {
585 if (visited.isSet(@backingInt(id))) continue;
586 visited.set(@backingInt(id));
587 if (flush.variables.get(id)) |storage_class| {
588 if (storage_class == .function) continue;
589 if (!v1_4 and storage_class != .input and storage_class != .output) continue;
590 try interface.append(arena, id);
591 } else if (flush.fn_refs.get(id)) |references| {
592 try pending.appendSlice(arena, references);
593 }
594 }
595
596 try out.emit(gpa, .OpEntryPoint, .{
597 .execution_model = execution_model,
598 .entry_point = entry_point.id,
599 .name = entry_point.name,
600 .interface = interface.items,
601 });
602 }
603}
604
605fn emitExecutionModes(flush: *Flush, out: *Section) link.Error!void {
606 const comp = flush.linker.base.comp;
607 const gpa = comp.gpa;
608 const target = &comp.root_mod.resolved_target.result;
609
610 for (flush.entry_points.items) |entry_point| switch (entry_point.cc) {
611 .spirv_kernel, .spirv_task => |kernel| {
612 try out.emit(gpa, .OpExecutionMode, .{
613 .entry_point = entry_point.id,
614 .mode = .{ .local_size = .{
615 .x_size = kernel.x,
616 .y_size = kernel.y,
617 .z_size = kernel.z,
618 } },
619 });
620 },
621 .spirv_fragment => |fragment| {
622 try out.emit(gpa, .OpExecutionMode, .{
623 .entry_point = entry_point.id,
624 .mode = if (target.os.tag == .vulkan) .origin_upper_left else .origin_lower_left,
625 });
626 if (fragment.pixel_centered_integer) {
627 try out.emit(gpa, .OpExecutionMode, .{
628 .entry_point = entry_point.id,
629 .mode = .pixel_center_integer,
630 });
631 }
632 if (fragment.depth_assumption != .none) {
633 try out.emit(gpa, .OpExecutionMode, .{
634 .entry_point = entry_point.id,
635 .mode = switch (fragment.depth_assumption) {
636 .none => unreachable,
637 .greater => .depth_greater,
638 .less => .depth_less,
639 .unchanged => .depth_unchanged,
640 },
641 });
642 }
643 },
644 .spirv_mesh => |mesh| {
645 try out.emit(gpa, .OpExecutionMode, .{
646 .entry_point = entry_point.id,
647 .mode = .{ .output_vertices = .{ .vertex_count = mesh.max_vertices } },
648 });
649 try out.emit(gpa, .OpExecutionMode, .{
650 .entry_point = entry_point.id,
651 .mode = .{ .output_primitives_ext = .{ .primitive_count = mesh.max_primitives } },
652 });
653 try out.emit(gpa, .OpExecutionMode, .{
654 .entry_point = entry_point.id,
655 .mode = .{ .local_size = .{
656 .x_size = mesh.x,
657 .y_size = mesh.y,
658 .z_size = mesh.z,
659 } },
660 });
661 try out.emit(gpa, .OpExecutionMode, .{
662 .entry_point = entry_point.id,
663 .mode = switch (mesh.stage_output) {
664 .output_points => .output_points,
665 .output_lines => .output_lines_ext,
666 .output_triangles => .output_triangles_ext,
667 },
668 });
669 },
670 else => {},
671 };
672}
673
674fn emitSourceInfo(flush: *Flush, out: *Section, version: u32) link.Error!void {
675 const comp = flush.linker.base.comp;
676 const gpa = comp.gpa;
677 const zcu = comp.zcu orelse return;
678 const ip = &zcu.intern_pool;
679
680 var error_info: std.Io.Writer.Allocating = .init(gpa);
681 defer error_info.deinit();
682 error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
683 for (ip.global_error_set.getNamesFromMainThread()) |name| {
684 error_info.writer.writeByte(':') catch return error.OutOfMemory;
685 std.Uri.Component.percentEncode(
686 &error_info.writer,
687 name.toSlice(ip),
688 struct {
689 fn isValidChar(c: u8) bool {
690 return switch (c) {
691 0, '%', ':' => false,
692 else => true,
693 };
694 }
695 }.isValidChar,
696 ) catch return error.OutOfMemory;
697 }
698
699 try out.emit(gpa, .OpSourceExtension, .{ .extension = error_info.written() });
700 try out.emit(gpa, .OpSource, .{
701 .source_language = .zig,
702 .version = version,
703 .file = null,
704 .source = null,
705 });
706}