1const std = @import("std");
2const build_options = @import("build_options");
3const builtin = @import("builtin");
4const assert = std.debug.assert;
5const link = @import("link.zig");
6const log = std.log.scoped(.codegen);
7const mem = std.mem;
8const math = std.math;
9const target_util = @import("target.zig");
10const trace = @import("tracy.zig").trace;
11
12const Air = @import("Air.zig");
13const Allocator = mem.Allocator;
14const Compilation = @import("Compilation.zig");
15const ErrorMsg = Zcu.ErrorMsg;
16const InternPool = @import("InternPool.zig");
17const Zcu = @import("Zcu.zig");
18
19const Type = @import("Type.zig");
20const Value = @import("Value.zig");
21const Zir = std.zig.Zir;
22const Alignment = InternPool.Alignment;
23const dev = @import("dev.zig");
24
25pub const aarch64 = @import("codegen/aarch64.zig");
26pub const loongarch = @import("codegen/loongarch.zig");
27
28pub const Error = link.Error;
29
30fn devFeatureForBackend(backend: std.lang.CompilerBackend) dev.Feature {
31 return switch (backend) {
32 .other, .stage1 => unreachable,
33 .stage2_aarch64 => .aarch64_backend,
34 .stage2_arm => .arm_backend,
35 .stage2_c => .c_backend,
36 .stage2_llvm => .llvm_backend,
37 .stage2_loongarch => .loongarch_backend,
38 .stage2_powerpc => unreachable,
39 .stage2_riscv64 => .riscv64_backend,
40 .stage2_sparc64 => .sparc64_backend,
41 .zsf_spork8 => .spork8_backend,
42 .stage2_spirv => .spirv_backend,
43 .stage2_wasm => .wasm_backend,
44 .stage2_x86 => .x86_backend,
45 .stage2_x86_64 => .x86_64_backend,
46 _ => unreachable,
47 };
48}
49
50fn importBackend(comptime backend: std.lang.CompilerBackend) type {
51 return switch (backend) {
52 .other, .stage1 => unreachable,
53 .stage2_aarch64 => aarch64,
54 .stage2_arm => unreachable,
55 .stage2_c => @import("codegen/c.zig"),
56 .stage2_llvm => @import("codegen/llvm.zig"),
57 .stage2_loongarch => loongarch,
58 .stage2_powerpc => unreachable,
59 .stage2_riscv64 => @import("codegen/riscv64/CodeGen.zig"),
60 .stage2_sparc64 => @import("codegen/sparc64/CodeGen.zig"),
61 .stage2_spirv => @import("codegen/spirv/CodeGen.zig"),
62 .zsf_spork8 => @import("codegen/spork8/CodeGen.zig"),
63 .stage2_wasm => @import("codegen/wasm/CodeGen.zig"),
64 .stage2_x86, .stage2_x86_64 => @import("codegen/x86_64/CodeGen.zig"),
65 _ => unreachable,
66 };
67}
68
69pub fn legalizeFeatures(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ?*const Air.Legalize.Features {
70 const zcu = pt.zcu;
71 const target = &zcu.navFileScope(nav_index).mod.?.resolved_target.result;
72 switch (target_util.zigBackend(target, zcu.comp.config.use_llvm)) {
73 else => unreachable,
74 inline .stage2_llvm,
75 .stage2_c,
76 .stage2_wasm,
77 .stage2_x86_64,
78 .stage2_aarch64,
79 .stage2_loongarch,
80 .stage2_x86,
81 .stage2_riscv64,
82 .stage2_sparc64,
83 .zsf_spork8,
84 .stage2_spirv,
85 => |backend| {
86 dev.check(devFeatureForBackend(backend));
87 return importBackend(backend).legalizeFeatures(target);
88 },
89 }
90}
91
92pub fn wantsLiveness(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) bool {
93 const zcu = pt.zcu;
94 const target = &zcu.navFileScope(nav_index).mod.?.resolved_target.result;
95 return switch (target_util.zigBackend(target, zcu.comp.config.use_llvm)) {
96 else => true,
97 .stage2_aarch64, .stage2_loongarch => false,
98 };
99}
100
101/// Every code generation backend has a different MIR representation. However, we want to pass
102/// MIR from codegen to the linker *regardless* of which backend is in use. So, we use this: a
103/// union of all MIR types. The active tag is known from the backend in use; see `AnyMir.tag`.
104pub const AnyMir = union {
105 aarch64: if (dev.env.supports(.aarch64_backend)) @import("codegen/aarch64/Mir.zig") else noreturn,
106 loongarch: if (dev.env.supports(.loongarch_backend)) @import("codegen/loongarch/Mir.zig") else noreturn,
107 riscv64: if (dev.env.supports(.riscv64_backend)) @import("codegen/riscv64/Mir.zig") else noreturn,
108 sparc64: if (dev.env.supports(.sparc64_backend)) @import("codegen/sparc64/Mir.zig") else noreturn,
109 x86_64: if (dev.env.supports(.x86_64_backend)) @import("codegen/x86_64/Mir.zig") else noreturn,
110 wasm: if (dev.env.supports(.wasm_backend)) @import("codegen/wasm/Mir.zig") else noreturn,
111 c: if (dev.env.supports(.c_backend)) @import("codegen/c.zig").Mir else noreturn,
112 spirv: if (dev.env.supports(.spirv_backend)) @import("codegen/spirv/Mir.zig") else noreturn,
113 spork8: if (dev.env.supports(.spork8_backend)) @import("codegen/spork8/Mir.zig") else noreturn,
114
115 pub inline fn tag(comptime backend: std.lang.CompilerBackend) []const u8 {
116 return switch (backend) {
117 .stage2_aarch64 => "aarch64",
118 .stage2_loongarch => "loongarch",
119 .stage2_riscv64 => "riscv64",
120 .stage2_sparc64 => "sparc64",
121 .stage2_x86_64 => "x86_64",
122 .stage2_wasm => "wasm",
123 .stage2_c => "c",
124 .stage2_spirv => "spirv",
125 .zsf_spork8 => "spork8",
126 else => unreachable,
127 };
128 }
129
130 pub fn deinit(mir: *AnyMir, zcu: *const Zcu) void {
131 const gpa = zcu.gpa;
132 const backend = target_util.zigBackend(&zcu.root_mod.resolved_target.result, zcu.comp.config.use_llvm);
133 switch (backend) {
134 else => unreachable,
135 inline .stage2_aarch64,
136 .stage2_loongarch,
137 .stage2_riscv64,
138 .stage2_sparc64,
139 .stage2_x86_64,
140 .stage2_wasm,
141 .stage2_c,
142 .stage2_spirv,
143 .zsf_spork8,
144 => |backend_ct| @field(mir, tag(backend_ct)).deinit(gpa),
145 }
146 }
147};
148
149/// Runs code generation for a function. This process converts the `Air` emitted by `Sema`,
150/// alongside annotated `Liveness` data, to machine code in the form of MIR (see `AnyMir`).
151///
152/// This is supposed to be a "pure" process, but some backends are currently buggy; see
153/// `Zcu.Feature.separate_thread` for details.
154pub fn generateFunction(
155 lf: *link.File,
156 pt: Zcu.PerThread,
157 func_index: InternPool.Index,
158 air: *const Air,
159 liveness: *const ?Air.Liveness,
160) Error!AnyMir {
161 const zcu = pt.zcu;
162 const func = zcu.funcInfo(func_index);
163 const target = &zcu.navFileScope(func.owner_nav).mod.?.resolved_target.result;
164 switch (target_util.zigBackend(target, false)) {
165 else => unreachable,
166 inline .stage2_aarch64,
167 .stage2_loongarch,
168 .stage2_riscv64,
169 .stage2_sparc64,
170 .stage2_x86_64,
171 .stage2_wasm,
172 .stage2_c,
173 .zsf_spork8,
174 .stage2_spirv,
175 => |backend| {
176 dev.check(devFeatureForBackend(backend));
177 const CodeGen = importBackend(backend);
178 const mir = try CodeGen.generate(lf, pt, func_index, air, liveness);
179 return @unionInit(AnyMir, AnyMir.tag(backend), mir);
180 },
181 }
182}
183
184/// Converts the MIR returned by `generateFunction` to finalized machine code to be placed in
185/// the output binary. This is called from linker implementations, and may query linker state.
186///
187/// This function is not called for the C backend, as `link.C` directly understands its MIR.
188///
189/// The `air` parameter is not supposed to exist, but some backends are currently buggy; see
190/// `Zcu.Feature.separate_thread` for details.
191pub fn emitFunction(
192 lf: *link.File,
193 pt: Zcu.PerThread,
194 func_index: InternPool.Index,
195 atom_id: link.File.AtomId,
196 any_mir: *const AnyMir,
197 w: *std.Io.Writer,
198 debug_output: link.File.DebugInfoOutput,
199) (Error || std.Io.Writer.Error)!void {
200 const zcu = pt.zcu;
201 const func = zcu.funcInfo(func_index);
202 const target = &zcu.navFileScope(func.owner_nav).mod.?.resolved_target.result;
203
204 const tracy_trace = trace(@src());
205 defer tracy_trace.end();
206 tracy_trace.addText(zcu.intern_pool.getNav(func.owner_nav).fqn.toSlice(&zcu.intern_pool));
207 tracy_trace.addTextFmt("func_ip_index={d}", .{func_index});
208
209 switch (target_util.zigBackend(target, zcu.comp.config.use_llvm)) {
210 else => unreachable,
211 inline .stage2_aarch64,
212 .stage2_loongarch,
213 .stage2_riscv64,
214 .stage2_sparc64,
215 .stage2_x86_64,
216 => |backend| {
217 dev.check(devFeatureForBackend(backend));
218 const mir = &@field(any_mir, AnyMir.tag(backend));
219 return mir.emit(lf, pt, func_index, atom_id, w, debug_output);
220 },
221 }
222}
223
224pub fn generateLazyFunction(
225 lf: *link.File,
226 pt: Zcu.PerThread,
227 lazy_sym: link.File.LazySymbol,
228 atom_id: link.File.AtomId,
229 w: *std.Io.Writer,
230 debug_output: link.File.DebugInfoOutput,
231) (Error || std.Io.Writer.Error)!void {
232 const zcu = pt.zcu;
233 const target = if (Type.fromInterned(lazy_sym.ty).typeDeclInstAllowGeneratedTag(zcu)) |inst_index|
234 &zcu.fileByIndex(inst_index.resolveFile(&zcu.intern_pool)).mod.?.resolved_target.result
235 else
236 zcu.getTarget();
237 switch (target_util.zigBackend(target, zcu.comp.config.use_llvm)) {
238 else => unreachable,
239 inline .stage2_riscv64, .stage2_x86_64 => |backend| {
240 dev.check(devFeatureForBackend(backend));
241 return importBackend(backend).generateLazy(lf, pt, lazy_sym, atom_id, w, debug_output);
242 },
243 }
244}
245
246pub fn generateLazySymbol(
247 bin_file: *link.File,
248 pt: Zcu.PerThread,
249 lazy_sym: link.File.LazySymbol,
250 // TODO don't use an "out" parameter like this; put it in the result instead
251 alignment: *Alignment,
252 w: *std.Io.Writer,
253 debug_output: link.File.DebugInfoOutput,
254 reloc_parent: link.File.RelocInfo.Parent,
255) (Error || std.Io.Writer.Error)!void {
256 const tracy = trace(@src());
257 defer tracy.end();
258 tracy.addTextFmt("{t}, {f}", .{ lazy_sym.kind, Type.fromInterned(lazy_sym.ty).fmt(pt) });
259
260 const comp = bin_file.comp;
261 const zcu = pt.zcu;
262 const ip = &zcu.intern_pool;
263 const target = &comp.root_mod.resolved_target.result;
264 const endian = target.cpu.arch.endian();
265
266 log.debug("generateLazySymbol: kind = {s}, ty = {f}", .{
267 @tagName(lazy_sym.kind),
268 Type.fromInterned(lazy_sym.ty).fmt(pt),
269 });
270
271 if (lazy_sym.kind == .code) {
272 alignment.* = target_util.defaultFunctionAlignment(target);
273 return generateLazyFunction(bin_file, pt, lazy_sym, reloc_parent.atom_index, w, debug_output);
274 }
275
276 if (lazy_sym.ty == .anyerror_type) {
277 alignment.* = .@"4";
278 const err_names = ip.global_error_set.getNamesFromMainThread();
279 const strings_start: u32 = @intCast(4 * (1 + err_names.len + @intFromBool(err_names.len > 0)));
280 var string_index = strings_start;
281 try w.rebase(w.end, string_index);
282 w.writeInt(u32, @intCast(err_names.len), endian) catch unreachable;
283 if (err_names.len == 0) return;
284 for (err_names) |err_name_nts| {
285 w.writeInt(u32, string_index, endian) catch unreachable;
286 string_index += @intCast(err_name_nts.toSlice(ip).len + 1);
287 }
288 w.writeInt(u32, string_index, endian) catch unreachable;
289 try w.rebase(w.end, string_index - strings_start);
290 for (err_names) |err_name_nts| {
291 w.writeAll(err_name_nts.toSlice(ip)) catch unreachable;
292 w.writeByte(0) catch unreachable;
293 }
294 } else if (Type.fromInterned(lazy_sym.ty).zigTypeTag(zcu) == .@"enum") {
295 alignment.* = .@"1";
296 const enum_ty = Type.fromInterned(lazy_sym.ty);
297 const tag_names = enum_ty.enumFields(zcu);
298 for (0..tag_names.len) |tag_index| {
299 const tag_name = tag_names.get(ip)[tag_index].toSlice(ip);
300 try w.rebase(w.end, tag_name.len + 1);
301 w.writeAll(tag_name) catch unreachable;
302 w.writeByte(0) catch unreachable;
303 }
304 } else {
305 return zcu.codegenFailType(lazy_sym.ty, "TODO implement generateLazySymbol for {s} {f}", .{
306 @tagName(lazy_sym.kind), Type.fromInterned(lazy_sym.ty).fmt(pt),
307 });
308 }
309}
310
311pub fn generateSymbol(
312 bin_file: *link.File,
313 pt: Zcu.PerThread,
314 val: Value,
315 w: *std.Io.Writer,
316 reloc_parent: link.File.RelocInfo.Parent,
317) (Error || std.Io.Writer.Error)!void {
318 const tracy = trace(@src());
319 defer tracy.end();
320
321 const zcu = pt.zcu;
322 const ip = &zcu.intern_pool;
323 const ty = val.typeOf(zcu);
324
325 const target = zcu.getTarget();
326 const endian = target.cpu.arch.endian();
327
328 log.debug("generateSymbol: val = {f}", .{val.fmtValue(pt)});
329
330 const abi_size = math.cast(usize, ty.abiSize(zcu)) orelse {
331 return zcu.comp.link_diags.fail("failed to generate symbol: type size overflow", .{});
332 };
333
334 if (val.isUndef(zcu)) {
335 try w.splatByteAll(0xaa, abi_size);
336 return;
337 }
338
339 switch (ip.indexToKey(val.toIntern())) {
340 .int_type,
341 .ptr_type,
342 .array_type,
343 .vector_type,
344 .opt_type,
345 .anyframe_type,
346 .error_union_type,
347 .simple_type,
348 .struct_type,
349 .tuple_type,
350 .union_type,
351 .opaque_type,
352 .spirv_type,
353 .enum_type,
354 .func_type,
355 .error_set_type,
356 .inferred_error_set_type,
357 => unreachable, // types, not values
358
359 .undef => unreachable, // handled above
360 .simple_value => |simple_value| switch (simple_value) {
361 .void => unreachable, // non-runtime value
362 .null => unreachable, // non-runtime value
363 .@"unreachable" => unreachable, // non-runtime value
364 .false, .true => try w.writeByte(switch (simple_value) {
365 .false => 0,
366 .true => 1,
367 else => unreachable,
368 }),
369 },
370 .@"extern",
371 .func,
372 .enum_literal,
373 => unreachable, // non-runtime values
374 .int => {
375 var space: Value.BigIntSpace = undefined;
376 const int_val = val.toBigInt(&space, zcu);
377 int_val.writeTwosComplement(try w.writableSlice(abi_size), endian);
378 },
379 .err => |err| {
380 const int = try pt.getErrorValue(err.name);
381 try w.writeInt(u16, @intCast(int), endian);
382 },
383 .error_union => |error_union| {
384 const payload_ty = ty.errorUnionPayload(zcu);
385 const err_val: u16 = switch (error_union.val) {
386 .err_name => |err_name| @intCast(try pt.getErrorValue(err_name)),
387 .payload => 0,
388 };
389
390 if (!payload_ty.hasRuntimeBits(zcu)) {
391 try w.writeInt(u16, err_val, endian);
392 return;
393 }
394
395 const payload_align = payload_ty.abiAlignment(zcu);
396 const error_align = Type.anyerror.abiAlignment(zcu);
397 const abi_align = ty.abiAlignment(zcu);
398
399 // error value first when its type is larger than the error union's payload
400 if (error_align.order(payload_align) == .gt) {
401 try w.writeInt(u16, err_val, endian);
402 }
403
404 // emit payload part of the error union
405 {
406 const begin = w.end;
407 try generateSymbol(bin_file, pt, Value.fromInterned(switch (error_union.val) {
408 .err_name => try pt.intern(.{ .undef = payload_ty.toIntern() }),
409 .payload => |payload| payload,
410 }), w, reloc_parent);
411 const unpadded_end = w.end - begin;
412 const padded_end = abi_align.forward(unpadded_end);
413 const padding: usize = @intCast(padded_end - unpadded_end);
414
415 if (padding > 0) {
416 try w.splatByteAll(0, padding);
417 }
418 }
419
420 // Payload size is larger than error set, so emit our error set last
421 if (error_align.compare(.lte, payload_align)) {
422 const begin = w.end;
423 try w.writeInt(u16, err_val, endian);
424 const unpadded_end = w.end - begin;
425 const padded_end = abi_align.forward(unpadded_end);
426 const padding: usize = @intCast(padded_end - unpadded_end);
427
428 if (padding > 0) {
429 try w.splatByteAll(0, padding);
430 }
431 }
432 },
433 .enum_tag => |enum_tag| {
434 const int_tag_ty = ty.backingIntType(zcu);
435 try generateSymbol(bin_file, pt, try pt.getCoerced(Value.fromInterned(enum_tag.int), int_tag_ty), w, reloc_parent);
436 },
437 .float => |float| storage: switch (float.storage) {
438 .f16 => |f16_val| try w.writeInt(u16, @bitCast(f16_val), endian),
439 .f32 => |f32_val| try w.writeInt(u32, @bitCast(f32_val), endian),
440 .f64 => |f64_val| try w.writeInt(u64, @bitCast(f64_val), endian),
441 .f80 => |f80_val| {
442 try w.writeInt(u80, @bitCast(f80_val), endian);
443 try w.splatByteAll(0, abi_size - 10);
444 },
445 .f128 => |f128_val| switch (Type.fromInterned(float.ty).floatBits(target)) {
446 else => unreachable,
447 16 => continue :storage .{ .f16 = @floatCast(f128_val) },
448 32 => continue :storage .{ .f32 = @floatCast(f128_val) },
449 64 => continue :storage .{ .f64 = @floatCast(f128_val) },
450 128 => try w.writeInt(u128, @bitCast(f128_val), endian),
451 },
452 },
453 .ptr => try lowerPtr(bin_file, pt, val.toIntern(), w, reloc_parent, 0),
454 .slice => |slice| {
455 try generateSymbol(bin_file, pt, Value.fromInterned(slice.ptr), w, reloc_parent);
456 try generateSymbol(bin_file, pt, Value.fromInterned(slice.len), w, reloc_parent);
457 },
458 .opt => {
459 const payload_type = ty.optionalChild(zcu);
460 const payload_val = val.optionalValue(zcu);
461
462 if (ty.optionalReprIsPayload(zcu)) {
463 if (payload_val) |value| {
464 try generateSymbol(bin_file, pt, value, w, reloc_parent);
465 } else {
466 try w.splatByteAll(0, abi_size);
467 }
468 } else {
469 const padding = abi_size - @as(usize, @intCast(payload_type.abiSize(zcu))) - 1;
470 if (payload_type.hasRuntimeBits(zcu)) {
471 const value = payload_val orelse Value.fromInterned(try pt.intern(.{
472 .undef = payload_type.toIntern(),
473 }));
474 try generateSymbol(bin_file, pt, value, w, reloc_parent);
475 }
476 try w.writeByte(@intFromBool(payload_val != null));
477 try w.splatByteAll(0, padding);
478 }
479 },
480 .aggregate => |aggregate| switch (ip.indexToKey(ty.toIntern())) {
481 .array_type => |array_type| switch (aggregate.storage) {
482 .bytes => |bytes| try w.writeAll(bytes.toSlice(array_type.lenIncludingSentinel(), ip)),
483 .elems, .repeated_elem => {
484 var index: u64 = 0;
485 while (index < array_type.lenIncludingSentinel()) : (index += 1) {
486 try generateSymbol(bin_file, pt, Value.fromInterned(switch (aggregate.storage) {
487 .bytes => unreachable,
488 .elems => |elems| elems[@intCast(index)],
489 .repeated_elem => |elem| if (index < array_type.len)
490 elem
491 else
492 array_type.sentinel,
493 }), w, reloc_parent);
494 }
495 },
496 },
497 .vector_type => |vector_type| {
498 const vector_bool_bitpacked = switch (zcu.comp.getZigBackend()) {
499 .stage2_wasm => false,
500 else => true,
501 };
502 if (vector_type.child == .bool_type and vector_bool_bitpacked) {
503 const bytes = try w.writableSlice(abi_size);
504 @memset(bytes, 0xaa);
505 var index: usize = 0;
506 const len = math.cast(usize, vector_type.len) orelse {
507 return zcu.comp.link_diags.fail("failed to generate symbol: vector length overflow", .{});
508 };
509 while (index < len) : (index += 1) {
510 const bit_index = switch (endian) {
511 .big => len - 1 - index,
512 .little => index,
513 };
514 const byte = &bytes[bit_index / 8];
515 const mask = @as(u8, 1) << @truncate(bit_index);
516 if (switch (switch (aggregate.storage) {
517 .bytes => unreachable,
518 .elems => |elems| elems[index],
519 .repeated_elem => |elem| elem,
520 }) {
521 .bool_true => true,
522 .bool_false => false,
523 else => |elem| switch (ip.indexToKey(elem)) {
524 .undef => continue,
525 .int => |int| switch (int.storage) {
526 .u64 => |x| switch (x) {
527 0 => false,
528 1 => true,
529 else => unreachable,
530 },
531 .i64 => |x| switch (x) {
532 -1 => true,
533 0 => false,
534 else => unreachable,
535 },
536 else => unreachable,
537 },
538 else => unreachable,
539 },
540 }) byte.* |= mask else byte.* &= ~mask;
541 }
542 } else {
543 switch (aggregate.storage) {
544 .bytes => |bytes| try w.writeAll(bytes.toSlice(vector_type.len, ip)),
545 .elems, .repeated_elem => {
546 var index: u64 = 0;
547 while (index < vector_type.len) : (index += 1) {
548 try generateSymbol(bin_file, pt, Value.fromInterned(switch (aggregate.storage) {
549 .bytes => unreachable,
550 .elems => |elems| elems[@intCast(index)],
551 .repeated_elem => |elem| elem,
552 }), w, reloc_parent);
553 }
554 },
555 }
556
557 const padding = abi_size - @as(usize, @intCast(Type.fromInterned(vector_type.child).abiSize(zcu) * vector_type.len));
558 if (padding > 0) try w.splatByteAll(0, padding);
559 }
560 },
561 .tuple_type => |tuple| {
562 const struct_begin = w.end;
563 for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, field_val, field_index| {
564 if (field_val != .none) continue;
565 if (!Type.fromInterned(field_ty).hasRuntimeBits(zcu)) continue;
566
567 try w.splatByteAll(0, @intCast(struct_begin +
568 Type.fromInterned(field_ty).abiAlignment(zcu).forward(w.end - struct_begin) - w.end));
569 try generateSymbol(bin_file, pt, .fromInterned(switch (aggregate.storage) {
570 .bytes => |bytes| try pt.intern(.{ .int = .{
571 .ty = field_ty,
572 .storage = .{ .u64 = bytes.at(field_index, ip) },
573 } }),
574 .elems => |elems| elems[field_index],
575 .repeated_elem => |elem| elem,
576 }), w, reloc_parent);
577 }
578 try w.splatByteAll(0, @intCast(struct_begin + ty.abiSize(zcu) - w.end));
579 },
580 .struct_type => {
581 const struct_type = ip.loadStructType(ty.toIntern());
582 switch (struct_type.layout) {
583 .@"packed" => unreachable,
584 .auto, .@"extern" => {
585 const struct_begin = w.end;
586 const field_types = struct_type.field_types.get(ip);
587 const offsets = struct_type.field_offsets.get(ip);
588
589 var it = struct_type.iterateRuntimeOrder(ip);
590 while (it.next()) |field_index| {
591 const field_ty = field_types[field_index];
592 if (!Type.fromInterned(field_ty).hasRuntimeBits(zcu)) continue;
593
594 const field_val = switch (ip.indexToKey(val.toIntern()).aggregate.storage) {
595 .bytes => |bytes| try pt.intern(.{ .int = .{
596 .ty = field_ty,
597 .storage = .{ .u64 = bytes.at(field_index, ip) },
598 } }),
599 .elems => |elems| elems[field_index],
600 .repeated_elem => |elem| elem,
601 };
602
603 const padding: usize = @intCast(offsets[field_index] - (w.end - struct_begin));
604 if (padding > 0) try w.splatByteAll(0, padding);
605
606 try generateSymbol(bin_file, pt, Value.fromInterned(field_val), w, reloc_parent);
607 }
608
609 assert(struct_type.alignment.check(struct_type.size));
610
611 const padding: usize = @intCast(struct_type.size - (w.end - struct_begin));
612 if (padding > 0) try w.splatByteAll(0, padding);
613 },
614 }
615 },
616 else => unreachable,
617 },
618 .un => |un| {
619 const layout = ty.unionGetLayout(zcu);
620
621 if (layout.payload_size == 0) {
622 return generateSymbol(bin_file, pt, Value.fromInterned(un.tag), w, reloc_parent);
623 }
624
625 // Check if we should store the tag first.
626 if (layout.tag_size > 0 and layout.tag_align.compare(.gte, layout.payload_align)) {
627 try generateSymbol(bin_file, pt, Value.fromInterned(un.tag), w, reloc_parent);
628 }
629
630 const union_obj = zcu.typeToUnion(ty).?;
631 if (un.tag != .none) {
632 const field_index = ty.unionTagFieldIndex(Value.fromInterned(un.tag), zcu).?;
633 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_index]);
634 if (!field_ty.hasRuntimeBits(zcu)) {
635 try w.splatByteAll(0xaa, @intCast(layout.payload_size));
636 } else {
637 try generateSymbol(bin_file, pt, Value.fromInterned(un.val), w, reloc_parent);
638
639 const padding: usize = @intCast(layout.payload_size - field_ty.abiSize(zcu));
640 if (padding > 0) {
641 try w.splatByteAll(0, padding);
642 }
643 }
644 } else {
645 try generateSymbol(bin_file, pt, Value.fromInterned(un.val), w, reloc_parent);
646 }
647
648 if (layout.tag_size > 0 and layout.tag_align.compare(.lt, layout.payload_align)) {
649 try generateSymbol(bin_file, pt, Value.fromInterned(un.tag), w, reloc_parent);
650
651 if (layout.padding > 0) {
652 try w.splatByteAll(0, layout.padding);
653 }
654 }
655 },
656 .bitpack => |bitpack| try generateSymbol(bin_file, pt, .fromInterned(bitpack.backing_int_val), w, reloc_parent),
657 .memoized_call => unreachable,
658 }
659}
660
661fn lowerPtr(
662 bin_file: *link.File,
663 pt: Zcu.PerThread,
664 ptr_val: InternPool.Index,
665 w: *std.Io.Writer,
666 reloc_parent: link.File.RelocInfo.Parent,
667 prev_offset: u64,
668) (Error || std.Io.Writer.Error)!void {
669 const zcu = pt.zcu;
670 const ptr = zcu.intern_pool.indexToKey(ptr_val).ptr;
671 const offset: u64 = prev_offset + ptr.byte_offset;
672 return switch (ptr.base_addr) {
673 .nav => |nav| try lowerNavRef(bin_file, pt, nav, w, reloc_parent, offset),
674 .uav => |uav| try lowerUavRef(bin_file, pt, uav, w, reloc_parent, offset),
675 .int => try generateSymbol(bin_file, pt, try pt.intValue(Type.usize, offset), w, reloc_parent),
676 .eu_payload => |eu_ptr| try lowerPtr(
677 bin_file,
678 pt,
679 eu_ptr,
680 w,
681 reloc_parent,
682 offset + errUnionPayloadOffset(
683 Value.fromInterned(eu_ptr).typeOf(zcu).childType(zcu).errorUnionPayload(zcu),
684 zcu,
685 ),
686 ),
687 .opt_payload => |opt_ptr| try lowerPtr(bin_file, pt, opt_ptr, w, reloc_parent, offset),
688 .field => |field| {
689 const base_ptr = Value.fromInterned(field.base);
690 const base_ty = base_ptr.typeOf(zcu).childType(zcu);
691 const field_off: u64 = switch (base_ty.zigTypeTag(zcu)) {
692 .pointer => off: {
693 assert(base_ty.isSlice(zcu));
694 break :off switch (field.index) {
695 Value.slice_ptr_index => 0,
696 Value.slice_len_index => @divExact(zcu.getTarget().ptrBitWidth(), 8),
697 else => unreachable,
698 };
699 },
700 .@"struct", .@"union" => switch (base_ty.containerLayout(zcu)) {
701 .auto => base_ty.structFieldOffset(@intCast(field.index), zcu),
702 .@"extern", .@"packed" => unreachable,
703 },
704 else => unreachable,
705 };
706 return lowerPtr(bin_file, pt, field.base, w, reloc_parent, offset + field_off);
707 },
708 .arr_elem => |arr_elem| {
709 const base_ptr_ty = Value.fromInterned(arr_elem.base).typeOf(zcu);
710 assert(base_ptr_ty.ptrSize(zcu) == .many);
711 const elem_size = base_ptr_ty.childType(zcu).abiSize(zcu);
712 return lowerPtr(bin_file, pt, arr_elem.base, w, reloc_parent, offset + elem_size * arr_elem.index);
713 },
714 .comptime_alloc => unreachable,
715 .comptime_field => unreachable,
716 };
717}
718
719fn lowerUavRef(
720 lf: *link.File,
721 pt: Zcu.PerThread,
722 uav: InternPool.Key.Ptr.BaseAddr.Uav,
723 w: *std.Io.Writer,
724 reloc_parent: link.File.RelocInfo.Parent,
725 offset: u64,
726) (Error || std.Io.Writer.Error)!void {
727 const zcu = pt.zcu;
728 const ip = &zcu.intern_pool;
729 const comp = lf.comp;
730 const target = &comp.root_mod.resolved_target.result;
731 const ptr_width_bytes = @divExact(target.ptrBitWidth(), 8);
732 const uav_val = uav.val;
733 const uav_ty = Type.fromInterned(ip.typeOf(uav_val));
734 const is_fn_body = uav_ty.zigTypeTag(zcu) == .@"fn";
735
736 log.debug("lowerUavRef: ty = {f}", .{uav_ty.fmt(pt)});
737
738 if (!is_fn_body and !uav_ty.hasRuntimeBits(zcu)) {
739 try w.splatByteAll(0xaa, ptr_width_bytes);
740 return;
741 }
742
743 switch (lf.tag) {
744 .c => unreachable,
745 .spirv => unreachable,
746 .wasm => {
747 dev.check(link.File.Tag.wasm.devFeature());
748 const wasm = lf.cast(.wasm).?;
749 assert(reloc_parent == .none);
750 try wasm.addUavReloc(w.end, uav.val, uav.orig_ty, @intCast(offset));
751 try w.splatByteAll(0, ptr_width_bytes);
752 return;
753 },
754 else => {},
755 }
756
757 const uav_align = Type.fromInterned(uav.orig_ty).ptrAlignment(zcu);
758 _ = try lf.lowerUav(pt, uav_val, uav_align);
759
760 const vaddr = lf.getUavVAddr(uav_val, .{
761 .parent = reloc_parent,
762 .offset = w.end,
763 .addend = @intCast(offset),
764 }) catch |err| switch (err) {
765 error.OutOfMemory => |e| return e,
766 else => |e| std.debug.panic("TODO rework lowerUav. internal error: {t}", .{e}),
767 };
768 const endian = target.cpu.arch.endian();
769 switch (ptr_width_bytes) {
770 2 => try w.writeInt(u16, @intCast(vaddr), endian),
771 4 => try w.writeInt(u32, @intCast(vaddr), endian),
772 8 => try w.writeInt(u64, vaddr, endian),
773 else => unreachable,
774 }
775}
776
777fn lowerNavRef(
778 lf: *link.File,
779 pt: Zcu.PerThread,
780 nav_index: InternPool.Nav.Index,
781 w: *std.Io.Writer,
782 reloc_parent: link.File.RelocInfo.Parent,
783 offset: u64,
784) (Error || std.Io.Writer.Error)!void {
785 const zcu = pt.zcu;
786 const ip = &zcu.intern_pool;
787 const target = &zcu.navFileScope(nav_index).mod.?.resolved_target.result;
788 const ptr_width_bytes = @divExact(target.ptrBitWidth(), 8);
789 const nav_ty = Type.fromInterned(ip.getNav(nav_index).resolved.?.type);
790
791 if (!nav_ty.isRuntimeFnOrHasRuntimeBits(zcu) and ip.getNav(nav_index).getExtern(ip) == null) {
792 try w.splatByteAll(0xaa, ptr_width_bytes);
793 return;
794 }
795
796 switch (lf.tag) {
797 .c => unreachable,
798 .spirv => unreachable,
799 .wasm => {
800 dev.check(link.File.Tag.wasm.devFeature());
801 const wasm = lf.cast(.wasm).?;
802 assert(reloc_parent == .none);
803 try wasm.addNavReloc(w.end, nav_index, nav_ty, @intCast(offset));
804 try w.splatByteAll(0, ptr_width_bytes);
805 return;
806 },
807 else => {},
808 }
809
810 const vaddr = lf.getNavVAddr(pt, nav_index, .{
811 .parent = reloc_parent,
812 .offset = w.end,
813 .addend = @intCast(offset),
814 }) catch @panic("TODO rework getNavVAddr");
815 const endian = target.cpu.arch.endian();
816 switch (ptr_width_bytes) {
817 2 => try w.writeInt(u16, @intCast(vaddr), endian),
818 4 => try w.writeInt(u32, @intCast(vaddr), endian),
819 8 => try w.writeInt(u64, vaddr, endian),
820 else => unreachable,
821 }
822}
823
824pub fn genNavRef(
825 lf: *link.File,
826 pt: Zcu.PerThread,
827 nav_index: InternPool.Nav.Index,
828) Error!link.File.SymbolId {
829 const zcu = pt.zcu;
830 const ip = &zcu.intern_pool;
831 const nav = ip.getNav(nav_index);
832 log.debug("genNavRef({f})", .{nav.fqn.fmt(ip)});
833
834 const is_threadlocal = nav.resolved.?.@"threadlocal" and zcu.comp.config.any_non_single_threaded;
835 const lib_name, const linkage = if (nav.getExtern(ip)) |e|
836 .{ e.lib_name, e.linkage }
837 else
838 .{ .none, .internal };
839 if (lf.cast(.elf)) |elf_file| {
840 const zo = elf_file.zigObjectPtr().?;
841 switch (linkage) {
842 .internal => {
843 const sym_index = try zo.getOrCreateMetadataForNav(zcu, nav_index);
844 if (is_threadlocal) zo.symbol(sym_index).flags.is_tls = true;
845 return @fromBackingInt(@intCast(sym_index));
846 },
847 .strong, .weak => {
848 const sym_index = try elf_file.getGlobalSymbol(nav.name.toSlice(ip), lib_name.toSlice(ip));
849 switch (linkage) {
850 .internal => unreachable,
851 .strong => {},
852 .weak => zo.symbol(sym_index).flags.weak = true,
853 .link_once => unreachable,
854 }
855 if (is_threadlocal) zo.symbol(sym_index).flags.is_tls = true;
856 return @fromBackingInt(@intCast(sym_index));
857 },
858 .link_once => unreachable,
859 }
860 } else if (lf.cast(.elf2)) |elf| {
861 return elf.navSymbol(nav_index);
862 } else if (lf.cast(.macho)) |macho_file| {
863 const zo = macho_file.getZigObject().?;
864 switch (linkage) {
865 .internal => {
866 const sym_index = try zo.getOrCreateMetadataForNav(macho_file, nav_index);
867 if (is_threadlocal) zo.symbols.items[sym_index].flags.tlv = true;
868 return @fromBackingInt(@intCast(sym_index));
869 },
870 .strong, .weak => {
871 const sym_index = try macho_file.getGlobalSymbol(nav.name.toSlice(ip), lib_name.toSlice(ip));
872 switch (linkage) {
873 .internal => unreachable,
874 .strong => {},
875 .weak => zo.symbols.items[sym_index].flags.weak = true,
876 .link_once => unreachable,
877 }
878 if (is_threadlocal) zo.symbols.items[sym_index].flags.tlv = true;
879 return @fromBackingInt(@intCast(sym_index));
880 },
881 .link_once => unreachable,
882 }
883 } else if (lf.cast(.coff2)) |coff| {
884 return @fromBackingInt(@intCast(@backingInt(try coff.navSymbol(zcu, nav_index))));
885 } else {
886 std.debug.panic("TODO genNavRef for '{t}'", .{lf.tag});
887 }
888}
889
890/// deprecated legacy type
891pub const MCValue = union(enum) {
892 none,
893 undef,
894 /// The bit-width of the immediate may be smaller than `u64`. For example, on 32-bit targets
895 /// such as ARM, the immediate will never exceed 32-bits.
896 immediate: u64,
897 /// Decl with address deferred until the linker allocates everything in virtual memory.
898 /// Payload is a symbol index.
899 load_direct: link.File.SymbolId,
900 /// Decl with address deferred until the linker allocates everything in virtual memory.
901 /// Payload is a symbol index.
902 lea_direct: link.File.SymbolId,
903 /// Decl referenced via GOT with address deferred until the linker allocates
904 /// everything in virtual memory.
905 /// Payload is a symbol index.
906 load_got: link.File.SymbolId,
907 /// Direct by-address reference to memory location.
908 memory: u64,
909 /// Reference to memory location but deferred until linker allocated the Decl in memory.
910 /// Traditionally, this corresponds to emitting a relocation in a relocatable object file.
911 load_symbol: link.File.SymbolId,
912 /// Reference to memory location but deferred until linker allocated the Decl in memory.
913 /// Traditionally, this corresponds to emitting a relocation in a relocatable object file.
914 lea_symbol: link.File.SymbolId,
915};
916
917/// deprecated legacy code path
918pub fn genTypedValue(
919 lf: *link.File,
920 pt: Zcu.PerThread,
921 val: Value,
922 target: *const std.Target,
923) Error!MCValue {
924 const res = try lowerValue(pt, val, target);
925 return switch (res) {
926 .none => .none,
927 .undef => .undef,
928 .immediate => |imm| .{ .immediate = imm },
929 .lea_nav => |nav| .{ .lea_symbol = try genNavRef(lf, pt, nav) },
930 .load_uav => |uav| .{ .load_symbol = try lf.lowerUav(
931 pt,
932 uav.val,
933 Type.fromInterned(uav.orig_ty).ptrAlignment(pt.zcu),
934 ) },
935 .lea_uav => |uav| .{ .lea_symbol = try lf.lowerUav(
936 pt,
937 uav.val,
938 Type.fromInterned(uav.orig_ty).ptrAlignment(pt.zcu),
939 ) },
940 };
941}
942
943const LowerResult = union(enum) {
944 none,
945 undef,
946 /// The bit-width of the immediate may be smaller than `u64`. For example, on 32-bit targets
947 /// such as ARM, the immediate will never exceed 32-bits.
948 immediate: u64,
949 lea_nav: InternPool.Nav.Index,
950 load_uav: InternPool.Key.Ptr.BaseAddr.Uav,
951 lea_uav: InternPool.Key.Ptr.BaseAddr.Uav,
952};
953
954pub fn lowerValue(pt: Zcu.PerThread, val: Value, target: *const std.Target) Allocator.Error!LowerResult {
955 const zcu = pt.zcu;
956 const ip = &zcu.intern_pool;
957 const ty = val.typeOf(zcu);
958
959 log.debug("lowerValue(@as({f}, {f}))", .{ ty.fmt(pt), val.fmtValue(pt) });
960
961 if (val.isUndef(zcu)) return .undef;
962
963 switch (ty.zigTypeTag(zcu)) {
964 .void => return .none,
965 .bool => return .{ .immediate = @intFromBool(val.toBool()) },
966 .pointer => switch (ty.ptrSize(zcu)) {
967 .slice => {},
968 .one, .many, .c => {
969 const ptr = ip.indexToKey(val.toIntern()).ptr;
970 if (ptr.base_addr == .int) return .{ .immediate = ptr.byte_offset };
971 if (ptr.byte_offset == 0) switch (ptr.base_addr) {
972 .int => unreachable, // handled above
973
974 .nav => |nav_index| {
975 const nav = ip.getNav(nav_index);
976 const nav_ty: Type = .fromInterned(nav.resolved.?.type);
977 if (nav_ty.isRuntimeFnOrHasRuntimeBits(zcu) or nav.getExtern(ip) != null) {
978 return .{ .lea_nav = nav_index };
979 } else {
980 // Create the 0xaa bit pattern...
981 const undef_ptr_bits: u64 = @intCast((@as(u66, 1) << @intCast(target.ptrBitWidth() + 1)) / 3);
982 // ...but align the pointer
983 const alignment = zcu.navAlignment(nav_index);
984 return .{ .immediate = alignment.forward(undef_ptr_bits) };
985 }
986 },
987
988 .uav => |uav| if (Value.fromInterned(uav.val).typeOf(zcu).isRuntimeFnOrHasRuntimeBits(zcu)) {
989 return .{ .lea_uav = uav };
990 } else {
991 // Create the 0xaa bit pattern...
992 const undef_ptr_bits: u64 = @intCast((@as(u66, 1) << @intCast(target.ptrBitWidth() + 1)) / 3);
993 // ...but align the pointer
994 const alignment = Type.fromInterned(uav.orig_ty).ptrAlignment(zcu);
995 return .{ .immediate = alignment.forward(undef_ptr_bits) };
996 },
997
998 else => {},
999 };
1000 },
1001 },
1002 .int => {
1003 const info = ty.intInfo(zcu);
1004 if (info.bits <= target.ptrBitWidth()) {
1005 const unsigned: u64 = switch (info.signedness) {
1006 .signed => @bitCast(val.toSignedInt(zcu)),
1007 .unsigned => val.toUnsignedInt(zcu),
1008 };
1009 return .{ .immediate = unsigned };
1010 }
1011 },
1012 .optional => {
1013 if (ty.isPtrLikeOptional(zcu)) {
1014 return lowerValue(
1015 pt,
1016 val.optionalValue(zcu) orelse return .{ .immediate = 0 },
1017 target,
1018 );
1019 } else if (ty.abiSize(zcu) == 1) {
1020 return .{ .immediate = @intFromBool(!val.isNull(zcu)) };
1021 }
1022 },
1023 .@"enum" => {
1024 const enum_tag = ip.indexToKey(val.toIntern()).enum_tag;
1025 return lowerValue(
1026 pt,
1027 Value.fromInterned(enum_tag.int),
1028 target,
1029 );
1030 },
1031 .@"struct", .@"union" => if (ty.containerLayout(zcu) == .@"packed") {
1032 const bitpack = ip.indexToKey(val.toIntern()).bitpack;
1033 return lowerValue(pt, .fromInterned(bitpack.backing_int_val), target);
1034 },
1035 .error_set => {
1036 const err_name = ip.indexToKey(val.toIntern()).err.name;
1037 const error_index = ip.getErrorValueIfExists(err_name).?;
1038 return .{ .immediate = error_index };
1039 },
1040 .error_union => {
1041 const err_type = ty.errorUnionSet(zcu);
1042 const payload_type = ty.errorUnionPayload(zcu);
1043 if (!payload_type.hasRuntimeBits(zcu)) {
1044 // We use the error type directly as the type.
1045 const err_int_ty = try pt.errorIntType();
1046 switch (ip.indexToKey(val.toIntern()).error_union.val) {
1047 .err_name => |err_name| return lowerValue(
1048 pt,
1049 Value.fromInterned(try pt.intern(.{ .err = .{
1050 .ty = err_type.toIntern(),
1051 .name = err_name,
1052 } })),
1053 target,
1054 ),
1055 .payload => return lowerValue(
1056 pt,
1057 try pt.intValue(err_int_ty, 0),
1058 target,
1059 ),
1060 }
1061 }
1062 },
1063
1064 .comptime_int => unreachable,
1065 .comptime_float => unreachable,
1066 .type => unreachable,
1067 .enum_literal => unreachable,
1068 .noreturn => unreachable,
1069 .undefined => unreachable,
1070 .null => unreachable,
1071 .@"opaque" => unreachable,
1072
1073 else => {},
1074 }
1075
1076 return .{ .load_uav = .{
1077 .val = val.toIntern(),
1078 .orig_ty = (try pt.singleConstPtrType(ty)).toIntern(),
1079 } };
1080}
1081
1082pub fn errUnionPayloadOffset(payload_ty: Type, zcu: *Zcu) u64 {
1083 if (!payload_ty.hasRuntimeBits(zcu)) return 0;
1084 const payload_align = payload_ty.abiAlignment(zcu);
1085 const error_align = Type.anyerror.abiAlignment(zcu);
1086 if (payload_align.compare(.gte, error_align) or !payload_ty.hasRuntimeBits(zcu)) {
1087 return 0;
1088 } else {
1089 return payload_align.forward(Type.anyerror.abiSize(zcu));
1090 }
1091}
1092
1093pub fn errUnionErrorOffset(payload_ty: Type, zcu: *Zcu) u64 {
1094 if (!payload_ty.hasRuntimeBits(zcu)) return 0;
1095 const payload_align = payload_ty.abiAlignment(zcu);
1096 const error_align = Type.anyerror.abiAlignment(zcu);
1097 if (payload_align.compare(.gte, error_align) and payload_ty.hasRuntimeBits(zcu)) {
1098 return error_align.forward(payload_ty.abiSize(zcu));
1099 } else {
1100 return 0;
1101 }
1102}
1103
1104pub fn fieldOffset(ptr_agg_ty: Type, ptr_field_ty: Type, field_index: u32, zcu: *Zcu) u64 {
1105 const agg_ty = ptr_agg_ty.childType(zcu);
1106 return switch (agg_ty.containerLayout(zcu)) {
1107 .auto, .@"extern" => agg_ty.structFieldOffset(field_index, zcu),
1108 .@"packed" => @divExact(@as(u64, ptr_agg_ty.ptrInfo(zcu).packed_offset.bit_offset) +
1109 (if (zcu.typeToPackedStruct(agg_ty)) |loaded_struct| zcu.structPackedFieldBitOffset(loaded_struct, field_index) else 0) -
1110 ptr_field_ty.ptrInfo(zcu).packed_offset.bit_offset, 8),
1111 };
1112}
1113
1114pub const FlattenedItem = struct { offset: u64, type: ?Type };
1115pub fn flattenType(items_buf: []FlattenedItem, ty: Type, zcu: *Zcu, opts: struct {
1116 offset: u64 = 0,
1117 allow_arrays: bool = true,
1118 fn increaseOffset(opts: @This(), offset: u64) @This() {
1119 return .{
1120 .offset = opts.offset + offset,
1121 .allow_arrays = opts.allow_arrays,
1122 };
1123 }
1124}) ?[]FlattenedItem {
1125 const ip = &zcu.intern_pool;
1126 switch (ip.indexToKey(ty.toIntern())) {
1127 .int_type => |int_type| {
1128 if (int_type.bits == 0) return items_buf[0..0];
1129 if (items_buf.len < 1) return null;
1130 const items = items_buf[0..1];
1131 items.* = .{.{ .offset = opts.offset, .type = ty }};
1132 return items;
1133 },
1134 .ptr_type => |ptr_type| switch (ptr_type.flags.size) {
1135 .one, .many, .c => {
1136 if (items_buf.len < 1) return null;
1137 const items = items_buf[0..1];
1138 items.* = .{.{ .offset = opts.offset, .type = ty }};
1139 return items;
1140 },
1141 .slice => {
1142 if (items_buf.len < 2) return null;
1143 const items = items_buf[0..2];
1144 const ptr_field_ty = ty.slicePtrFieldType(zcu);
1145 items.* = .{
1146 .{ .offset = opts.offset, .type = ptr_field_ty },
1147 .{ .offset = opts.offset + ptr_field_ty.abiSize(zcu), .type = .usize },
1148 };
1149 return items;
1150 },
1151 },
1152 .array_type => |array_type| {
1153 const len = array_type.lenIncludingSentinel();
1154 if (len == 0) return items_buf[0..0];
1155 const elem_ty: Type = .fromInterned(array_type.child);
1156 const elem_items = flattenType(items_buf, elem_ty, zcu, opts) orelse return null;
1157 if (elem_items.len == 0) return items_buf[0..0];
1158 if (!opts.allow_arrays) return null;
1159 const items_len, const items_overflow = @mulWithOverflow(elem_items.len, len);
1160 if (items_overflow != 0 or items_buf.len < items_len) return null;
1161 var items_index = elem_items.len;
1162 const elem_size = elem_ty.abiSize(zcu);
1163 var elem_offset: u64 = elem_size;
1164 while (items_index != items_len) : ({
1165 items_index += elem_items.len;
1166 elem_offset += elem_size;
1167 }) for (items_buf[items_index..][0..elem_items.len], elem_items) |*item, elem_item| {
1168 item.* = .{ .offset = elem_offset + elem_item.offset, .type = elem_item.type };
1169 };
1170 return items_buf[0..@intCast(items_len)];
1171 },
1172 .vector_type => |vector_type| {
1173 if (vector_type.len == 0) return items_buf[0..0];
1174 if (items_buf.len < 1) return null;
1175 const items = items_buf[0..1];
1176 items.* = .{.{ .offset = opts.offset, .type = ty }};
1177 return items;
1178 },
1179 .opt_type, .error_union_type => return null,
1180 .simple_type => |simple_type| switch (simple_type) {
1181 .f16,
1182 .f32,
1183 .f64,
1184 .f80,
1185 .f128,
1186 .usize,
1187 .isize,
1188 .c_char,
1189 .c_short,
1190 .c_ushort,
1191 .c_int,
1192 .c_uint,
1193 .c_long,
1194 .c_ulong,
1195 .c_longlong,
1196 .c_ulonglong,
1197 .c_longdouble,
1198 .bool,
1199 .anyerror,
1200 => {
1201 if (items_buf.len < 1) return null;
1202 const items = items_buf[0..1];
1203 items.* = .{.{ .offset = opts.offset, .type = ty }};
1204 return items;
1205 },
1206 .anyopaque, .noreturn => return null,
1207 .void,
1208 .type,
1209 .comptime_int,
1210 .comptime_float,
1211 .null,
1212 .undefined,
1213 .enum_literal,
1214 => return items_buf[0..0],
1215 .adhoc_inferred_error_set, .generic_poison => unreachable,
1216 },
1217 .struct_type => {
1218 const loaded_struct = ip.loadStructType(ty.toIntern());
1219 switch (loaded_struct.layout) {
1220 .auto, .@"extern" => {},
1221 .@"packed" => return flattenType(items_buf, .fromInterned(
1222 loaded_struct.packed_backing_int_type,
1223 ), zcu, opts),
1224 }
1225 var items_len: usize = 0;
1226 var offset: u64 = 0;
1227 var field_it = loaded_struct.iterateRuntimeOrder(ip);
1228 while (field_it.next()) |field_index| {
1229 const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
1230 const field_offset = loaded_struct.field_offsets.get(ip)[field_index];
1231 if (field_offset - offset > 0 and
1232 (items_len == 0 or items_buf[items_len - 1].type != null))
1233 {
1234 if (items_len == items_buf.len) return null;
1235 items_buf[items_len] = .{ .offset = offset, .type = null };
1236 items_len += 1;
1237 }
1238 items_len += (flattenType(items_buf[items_len..], field_ty, zcu, opts.increaseOffset(
1239 field_offset,
1240 )) orelse return null).len;
1241 offset = field_offset + field_ty.abiSize(zcu);
1242 }
1243 if (ty.abiSize(zcu) - offset > 0 and
1244 (items_len == 0 or items_buf[items_len - 1].type != null))
1245 {
1246 if (items_len == items_buf.len) return null;
1247 items_buf[items_len] = .{ .offset = offset, .type = null };
1248 items_len += 1;
1249 }
1250 return items_buf[0..items_len];
1251 },
1252 .tuple_type => |tuple_type| {
1253 if (items_buf.len < tuple_type.types.len) return null;
1254 var items_len: usize = 0;
1255 var offset: u64 = 0;
1256 for (tuple_type.types.get(ip)) |field_ty_ip| {
1257 const field_ty: Type = .fromInterned(field_ty_ip);
1258 offset = field_ty.abiAlignment(zcu).forward(offset);
1259 items_len += (flattenType(items_buf[items_len..], field_ty, zcu, opts.increaseOffset(
1260 offset,
1261 )) orelse return null).len;
1262 offset += field_ty.abiSize(zcu);
1263 }
1264 return items_buf[0..items_len];
1265 },
1266 .union_type => {
1267 const loaded_union = ip.loadUnionType(ty.toIntern());
1268 return switch (loaded_union.layout) {
1269 .auto, .@"extern" => return null,
1270 .@"packed" => return flattenType(items_buf, .fromInterned(
1271 loaded_union.packed_backing_int_type,
1272 ), zcu, opts),
1273 };
1274 },
1275 .opaque_type, .spirv_type, .func_type => return null,
1276 .enum_type => return flattenType(items_buf, .fromInterned(
1277 ip.loadEnumType(ty.toIntern()).int_tag_type,
1278 ), zcu, opts),
1279 .error_set_type, .inferred_error_set_type => {
1280 if (items_buf.len < 1) return null;
1281 const items = items_buf[0..1];
1282 items.* = .{.{ .offset = opts.offset, .type = ty }};
1283 return items;
1284 },
1285 .anyframe_type,
1286 // values, not types
1287 .undef,
1288 .simple_value,
1289 .@"extern",
1290 .func,
1291 .int,
1292 .err,
1293 .error_union,
1294 .enum_literal,
1295 .enum_tag,
1296 .float,
1297 .ptr,
1298 .slice,
1299 .opt,
1300 .aggregate,
1301 .un,
1302 .bitpack,
1303 // memoization, not types
1304 .memoized_call,
1305 => unreachable,
1306 }
1307}
1308
1309test {
1310 _ = aarch64;
1311 _ = loongarch;
1312}