1const std = @import("std");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const mem = std.mem;
5const log = std.log.scoped(.c);
6const Allocator = mem.Allocator;
7const Writer = std.Io.Writer;
8
9const dev = @import("../dev.zig");
10const link = @import("../link.zig");
11const Zcu = @import("../Zcu.zig");
12const Module = @import("../Module.zig");
13const Compilation = @import("../Compilation.zig");
14const Value = @import("../Value.zig");
15const Type = @import("../Type.zig");
16const C = link.File.C;
17const Decl = Zcu.Decl;
18const trace = @import("../tracy.zig").trace;
19const Air = @import("../Air.zig");
20const InternPool = @import("../InternPool.zig");
21const Alignment = InternPool.Alignment;
22
23const BigIntLimb = std.math.big.Limb;
24const BigInt = std.math.big.int;
25
26pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
27 return comptime switch (dev.env.supports(.legalize)) {
28 inline false, true => |supports_legalize| &.init(.{
29 // we don't currently ask zig1 to use safe optimization modes
30 .expand_bit_cast_safe = supports_legalize,
31 .expand_int_cast_safe = supports_legalize,
32 .expand_int_from_float_safe = supports_legalize,
33 .expand_int_from_float_optimized_safe = supports_legalize,
34 .expand_add_safe = supports_legalize,
35 .expand_sub_safe = supports_legalize,
36 .expand_mul_safe = supports_legalize,
37
38 .expand_packed_load = true,
39 .expand_packed_store = true,
40 .expand_packed_agg_field_val = true,
41 .expand_packed_aggregate_init = true,
42 .expand_array_splat = true,
43 .expand_array_to_vector = true,
44
45 .scalarize_bit_cast_array = true,
46 .scalarize_bit_cast_vector_non_elementwise = true,
47 }),
48 };
49}
50
51/// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some
52/// "pseudo instructions" thrown in. For the C backend, it is instead the generated C code for a
53/// single function. We also need to track some information to get merged into the global `link.C`
54/// state, including:
55/// * The UAVs used, so declarations can be emitted in `flush`
56/// * The types used, so declarations can be emitted in `flush`
57/// * The lazy functions used, so definitions can be emitted in `flush`
58pub const Mir = struct {
59 // These remaining fields are essentially just an owned version of `link.C.AvBlock`.
60 fwd_decl: []u8,
61 code_header: []u8,
62 code: []u8,
63 /// This map contains all the UAVs we saw generating this function.
64 /// `link.C` will merge them into its `uavs`/`aligned_uavs` fields.
65 /// Key is the value of the UAV; value is the UAV's alignment, or
66 /// `.none` for natural alignment. The specified alignment is never
67 /// less than the natural alignment.
68 need_uavs: std.array_hash_map.Auto(InternPool.Index, Alignment),
69 ctype_deps: CType.Dependencies,
70 /// Key is an enum type for which we need a generated `@tagName` function.
71 need_tag_name_funcs: std.array_hash_map.Auto(InternPool.Index, void),
72 /// Key is a function Nav for which we need a generated `zig_never_tail` wrapper.
73 need_never_tail_funcs: std.array_hash_map.Auto(InternPool.Nav.Index, void),
74 /// Key is a function Nav for which we need a generated `zig_never_inline` wrapper.
75 need_never_inline_funcs: std.array_hash_map.Auto(InternPool.Nav.Index, void),
76
77 pub fn deinit(mir: *Mir, gpa: Allocator) void {
78 gpa.free(mir.fwd_decl);
79 gpa.free(mir.code_header);
80 gpa.free(mir.code);
81 mir.need_uavs.deinit(gpa);
82 mir.ctype_deps.deinit(gpa);
83 mir.need_tag_name_funcs.deinit(gpa);
84 mir.need_never_tail_funcs.deinit(gpa);
85 mir.need_never_inline_funcs.deinit(gpa);
86 }
87};
88
89pub const Error = Writer.Error || Allocator.Error || error{AlreadyReported};
90
91pub const CType = @import("c/type.zig").CType;
92
93pub const CValue = union(enum) {
94 none: void,
95 new_local: LocalIndex,
96 local: LocalIndex,
97 /// Address of a local.
98 local_ref: LocalIndex,
99 /// A constant instruction, to be rendered inline.
100 constant: Value,
101 /// Index into the parameters
102 arg: usize,
103 /// Index into a tuple's fields
104 field: usize,
105 /// By-value
106 nav: InternPool.Nav.Index,
107 nav_ref: InternPool.Nav.Index,
108 /// An undefined value (cannot be dereferenced)
109 undef: Type,
110 /// Rendered as an identifier (using fmtIdent)
111 identifier: []const u8,
112 /// Rendered as "payload." followed by as identifier (using fmtIdent)
113 payload_identifier: []const u8,
114
115 fn eql(lhs: CValue, rhs: CValue) bool {
116 return switch (lhs) {
117 .none => rhs == .none,
118 .new_local, .local => |lhs_local| switch (rhs) {
119 .new_local, .local => |rhs_local| lhs_local == rhs_local,
120 else => false,
121 },
122 .local_ref => |lhs_local| switch (rhs) {
123 .local_ref => |rhs_local| lhs_local == rhs_local,
124 else => false,
125 },
126 .constant => |lhs_val| switch (rhs) {
127 .constant => |rhs_val| lhs_val.toIntern() == rhs_val.toIntern(),
128 else => false,
129 },
130 .arg => |lhs_arg_index| switch (rhs) {
131 .arg => |rhs_arg_index| lhs_arg_index == rhs_arg_index,
132 else => false,
133 },
134 .field => |lhs_field_index| switch (rhs) {
135 .field => |rhs_field_index| lhs_field_index == rhs_field_index,
136 else => false,
137 },
138 .nav => |lhs_nav| switch (rhs) {
139 .nav => |rhs_nav| lhs_nav == rhs_nav,
140 else => false,
141 },
142 .nav_ref => |lhs_nav| switch (rhs) {
143 .nav_ref => |rhs_nav| lhs_nav == rhs_nav,
144 else => false,
145 },
146 .undef => |lhs_ty| switch (rhs) {
147 .undef => |rhs_ty| lhs_ty.toIntern() == rhs_ty.toIntern(),
148 else => false,
149 },
150 .identifier => |lhs_id| switch (rhs) {
151 .identifier => |rhs_id| std.mem.eql(u8, lhs_id, rhs_id),
152 else => false,
153 },
154 .payload_identifier => |lhs_id| switch (rhs) {
155 .payload_identifier => |rhs_id| std.mem.eql(u8, lhs_id, rhs_id),
156 else => false,
157 },
158 };
159 }
160};
161
162const BlockData = struct {
163 block_id: u32,
164 result: CValue,
165};
166
167const LocalType = struct {
168 type: Type,
169 alignment: Alignment = .none,
170 array_len: u2 = 1,
171};
172
173const LocalIndex = u16;
174const LocalsList = std.array_hash_map.Auto(LocalIndex, void);
175const LocalsMap = std.array_hash_map.Auto(LocalType, LocalsList);
176
177const ValueRenderLocation = enum {
178 initializer,
179 static_initializer,
180 other,
181
182 fn isInitializer(loc: ValueRenderLocation) bool {
183 return switch (loc) {
184 .initializer, .static_initializer => true,
185 .other => false,
186 };
187 }
188};
189
190const BuiltinInfo = enum { none, bits, bits_none, big_temp_bits };
191
192const reserved_idents = std.StaticStringMap(void).initComptime(.{
193 // C language
194 .{ "alignas", {} },
195 .{ "alignof", {} },
196 .{ "asm", {} },
197 .{ "atomic_bool", {} },
198 .{ "atomic_char", {} },
199 .{ "atomic_char16_t", {} },
200 .{ "atomic_char32_t", {} },
201 .{ "atomic_int", {} },
202 .{ "atomic_int_fast16_t", {} },
203 .{ "atomic_int_fast32_t", {} },
204 .{ "atomic_int_fast64_t", {} },
205 .{ "atomic_int_fast8_t", {} },
206 .{ "atomic_int_least16_t", {} },
207 .{ "atomic_int_least32_t", {} },
208 .{ "atomic_int_least64_t", {} },
209 .{ "atomic_int_least8_t", {} },
210 .{ "atomic_intmax_t", {} },
211 .{ "atomic_intptr_t", {} },
212 .{ "atomic_llong", {} },
213 .{ "atomic_long", {} },
214 .{ "atomic_ptrdiff_t", {} },
215 .{ "atomic_schar", {} },
216 .{ "atomic_short", {} },
217 .{ "atomic_size_t", {} },
218 .{ "atomic_uchar", {} },
219 .{ "atomic_uint", {} },
220 .{ "atomic_uint_fast16_t", {} },
221 .{ "atomic_uint_fast32_t", {} },
222 .{ "atomic_uint_fast64_t", {} },
223 .{ "atomic_uint_fast8_t", {} },
224 .{ "atomic_uint_least16_t", {} },
225 .{ "atomic_uint_least32_t", {} },
226 .{ "atomic_uint_least64_t", {} },
227 .{ "atomic_uint_least8_t", {} },
228 .{ "atomic_uintmax_t", {} },
229 .{ "atomic_uintptr_t", {} },
230 .{ "atomic_ullong", {} },
231 .{ "atomic_ulong", {} },
232 .{ "atomic_ushort", {} },
233 .{ "atomic_wchar_t", {} },
234 .{ "auto", {} },
235 .{ "break", {} },
236 .{ "case", {} },
237 .{ "char", {} },
238 .{ "complex", {} },
239 .{ "const", {} },
240 .{ "continue", {} },
241 .{ "default", {} },
242 .{ "do", {} },
243 .{ "double", {} },
244 .{ "else", {} },
245 .{ "enum", {} },
246 .{ "extern", {} },
247 .{ "float", {} },
248 .{ "for", {} },
249 .{ "fortran", {} },
250 .{ "goto", {} },
251 .{ "if", {} },
252 .{ "imaginary", {} },
253 .{ "inline", {} },
254 .{ "int", {} },
255 .{ "int16_t", {} },
256 .{ "int24_t", {} },
257 .{ "int32_t", {} },
258 .{ "int48_t", {} },
259 .{ "int64_t", {} },
260 .{ "int8_t", {} },
261 .{ "intptr_t", {} },
262 .{ "long", {} },
263 .{ "noreturn", {} },
264 .{ "register", {} },
265 .{ "restrict", {} },
266 .{ "return", {} },
267 .{ "short", {} },
268 .{ "signed", {} },
269 .{ "size_t", {} },
270 .{ "sizeof", {} },
271 .{ "ssize_t", {} },
272 .{ "static", {} },
273 .{ "static_assert", {} },
274 .{ "struct", {} },
275 .{ "switch", {} },
276 .{ "thread_local", {} },
277 .{ "typedef", {} },
278 .{ "typeof", {} },
279 .{ "uint16_t", {} },
280 .{ "uint24_t", {} },
281 .{ "uint32_t", {} },
282 .{ "uint48_t", {} },
283 .{ "uint64_t", {} },
284 .{ "uint8_t", {} },
285 .{ "uintptr_t", {} },
286 .{ "union", {} },
287 .{ "unsigned", {} },
288 .{ "void", {} },
289 .{ "volatile", {} },
290 .{ "while", {} },
291
292 // stdarg.h
293 .{ "va_start", {} },
294 .{ "va_arg", {} },
295 .{ "va_end", {} },
296 .{ "va_copy", {} },
297
298 // stdbool.h
299 .{ "bool", {} },
300 .{ "false", {} },
301 .{ "true", {} },
302
303 // stddef.h
304 .{ "offsetof", {} },
305
306 // math.h (only symbols exported by compiler-rt)
307 .{ "ceil", {} },
308 .{ "ceilf", {} },
309 .{ "ceilf128", {} },
310 .{ "ceill", {} },
311 .{ "cos", {} },
312 .{ "cosf", {} },
313 .{ "cosf128", {} },
314 .{ "cosl", {} },
315 .{ "exp", {} },
316 .{ "exp2", {} },
317 .{ "exp2f", {} },
318 .{ "exp2f128", {} },
319 .{ "exp2l", {} },
320 .{ "expf", {} },
321 .{ "expf128", {} },
322 .{ "expl", {} },
323 .{ "fabs", {} },
324 .{ "fabsf", {} },
325 .{ "fabsf128", {} },
326 .{ "fabsl", {} },
327 .{ "floor", {} },
328 .{ "floorf", {} },
329 .{ "floorf128", {} },
330 .{ "floorl", {} },
331 .{ "fma", {} },
332 .{ "fmaf", {} },
333 .{ "fmaf128", {} },
334 .{ "fmal", {} },
335 .{ "fmax", {} },
336 .{ "fmaxf", {} },
337 .{ "fmaxf128", {} },
338 .{ "fmaxl", {} },
339 .{ "fmin", {} },
340 .{ "fminf", {} },
341 .{ "fminf128", {} },
342 .{ "fminl", {} },
343 .{ "fmod", {} },
344 .{ "fmodf", {} },
345 .{ "fmodf128", {} },
346 .{ "fmodl", {} },
347 .{ "log", {} },
348 .{ "log10", {} },
349 .{ "log10f", {} },
350 .{ "log10f128", {} },
351 .{ "log10l", {} },
352 .{ "log2", {} },
353 .{ "log2f", {} },
354 .{ "log2f128", {} },
355 .{ "log2l", {} },
356 .{ "logf", {} },
357 .{ "logf128", {} },
358 .{ "logl", {} },
359 .{ "round", {} },
360 .{ "roundf", {} },
361 .{ "roundf128", {} },
362 .{ "roundl", {} },
363 .{ "sin", {} },
364 .{ "sincos", {} },
365 .{ "sincosf", {} },
366 .{ "sincosf128", {} },
367 .{ "sincosl", {} },
368 .{ "sinf", {} },
369 .{ "sinf128", {} },
370 .{ "sinl", {} },
371 .{ "sqrt", {} },
372 .{ "sqrtf", {} },
373 .{ "sqrtf128", {} },
374 .{ "sqrtl", {} },
375 .{ "tan", {} },
376 .{ "tanf", {} },
377 .{ "tanf128", {} },
378 .{ "tanl", {} },
379 .{ "trunc", {} },
380 .{ "truncf", {} },
381 .{ "truncf128", {} },
382 .{ "truncl", {} },
383
384 // windows.h
385 .{"DUMMYSTRUCTNAME"},
386 .{"DUMMYSTRUCTNAME2"},
387 .{"DUMMYSTRUCTNAME3"},
388 .{"DUMMYSTRUCTNAME4"},
389 .{"DUMMYSTRUCTNAME5"},
390 .{"DUMMYSTRUCTNAME6"},
391 .{"DUMMYUNIONNAME"},
392 .{"DUMMYUNIONNAME2"},
393 .{"DUMMYUNIONNAME3"},
394 .{"DUMMYUNIONNAME4"},
395 .{"DUMMYUNIONNAME5"},
396 .{"DUMMYUNIONNAME6"},
397 .{"DUMMYUNIONNAME7"},
398 .{"DUMMYUNIONNAME8"},
399 .{"DUMMYUNIONNAME9"},
400 .{ "max", {} },
401 .{ "min", {} },
402});
403
404fn isReservedIdent(ident: []const u8) bool {
405 // C language
406 if (ident.len >= 2 and ident[0] == '_') {
407 switch (ident[1]) {
408 'A'...'Z', '_' => return true,
409 else => {},
410 }
411 }
412
413 // CType
414 if (mem.startsWith(u8, ident, "enum__") or
415 mem.startsWith(u8, ident, "bitpack__") or
416 mem.startsWith(u8, ident, "aligned__") or
417 mem.startsWith(u8, ident, "fn__"))
418 {
419 return true;
420 }
421
422 // zig.h
423 if (mem.startsWith(u8, ident, "zig_")) return true;
424
425 return reserved_idents.has(ident);
426}
427
428fn formatIdentSolo(ident: []const u8, w: *Writer) Writer.Error!void {
429 return formatIdentOptions(ident, w, true);
430}
431
432fn formatIdentUnsolo(ident: []const u8, w: *Writer) Writer.Error!void {
433 return formatIdentOptions(ident, w, false);
434}
435
436fn formatIdentOptions(ident: []const u8, w: *Writer, solo: bool) Writer.Error!void {
437 if (solo and isReservedIdent(ident)) {
438 try w.writeAll("zig_e_");
439 }
440 for (ident, 0..) |c, i| {
441 switch (c) {
442 'a'...'z', 'A'...'Z', '_' => try w.writeByte(c),
443 '.', ' ' => try w.writeByte('_'),
444 '0'...'9' => if (i == 0) {
445 try w.print("_{x:2}", .{c});
446 } else {
447 try w.writeByte(c);
448 },
449 else => try w.print("_{x:2}", .{c}),
450 }
451 }
452}
453
454pub fn fmtIdentSolo(ident: []const u8) std.fmt.Alt([]const u8, formatIdentSolo) {
455 return .{ .data = ident };
456}
457
458pub fn fmtIdentUnsolo(ident: []const u8) std.fmt.Alt([]const u8, formatIdentUnsolo) {
459 return .{ .data = ident };
460}
461
462// Returns true if `formatIdent` would make any edits to ident.
463// This must be kept in sync with `formatIdent`.
464pub fn isMangledIdent(ident: []const u8, solo: bool) bool {
465 if (solo and isReservedIdent(ident)) return true;
466 for (ident, 0..) |c, i| {
467 switch (c) {
468 'a'...'z', 'A'...'Z', '_' => {},
469 '0'...'9' => if (i == 0) return true,
470 else => return true,
471 }
472 }
473 return false;
474}
475
476/// This data is available when rendering C source code for an interned function.
477pub const Function = struct {
478 air: Air,
479 liveness: Air.Liveness,
480 value_map: std.AutoHashMap(Air.Inst.Ref, CValue),
481 blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .empty,
482 next_arg_index: u32 = 0,
483 next_block_index: u32 = 0,
484 dg: DeclGen,
485 code: Writer.Allocating,
486 indent_counter: usize,
487 /// Key is an enum type for which we need a generated `@tagName` function.
488 need_tag_name_funcs: std.array_hash_map.Auto(InternPool.Index, void),
489 /// Key is a function Nav for which we need a generated `zig_never_tail` wrapper.
490 need_never_tail_funcs: std.array_hash_map.Auto(InternPool.Nav.Index, void),
491 /// Key is a function Nav for which we need a generated `zig_never_inline` wrapper.
492 need_never_inline_funcs: std.array_hash_map.Auto(InternPool.Nav.Index, void),
493 func_index: InternPool.Index,
494 /// All the locals, to be emitted at the top of the function.
495 locals: std.ArrayList(LocalType) = .empty,
496 /// Which locals are available for reuse, based on Type.
497 free_locals_map: LocalsMap = .{},
498 /// Locals which will not be freed by Liveness. This is used after a
499 /// Function body is lowered in order to make `free_locals_map` have
500 /// 100% of the locals within so that it can be used to render the block
501 /// of variable declarations at the top of a function, sorted descending
502 /// by type alignment.
503 /// The value is whether the alloc needs to be emitted in the header.
504 allocs: std.array_hash_map.Auto(LocalIndex, bool) = .empty,
505 /// Maps from `loop_switch_br` instructions to the allocated local used
506 /// for the switch cond. Dispatches should set this local to the new cond.
507 loop_switch_conds: std.AutoHashMapUnmanaged(Air.Inst.Index, LocalIndex) = .empty,
508
509 const indent_width = 1;
510 const indent_char = ' ';
511
512 fn newline(f: *Function) !void {
513 const w = &f.code.writer;
514 try w.writeByte('\n');
515 try w.splatByteAll(indent_char, f.indent_counter);
516 }
517 fn indent(f: *Function) void {
518 f.indent_counter += indent_width;
519 }
520 fn outdent(f: *Function) !void {
521 f.indent_counter -= indent_width;
522 const written = f.code.written();
523 switch (written[written.len - 1]) {
524 indent_char => f.code.shrinkRetainingCapacity(written.len - indent_width),
525 '\n' => try f.code.writer.splatByteAll(indent_char, f.indent_counter),
526 else => {
527 std.debug.print("\"{f}\"\n", .{std.zig.fmtString(written[written.len -| 100..])});
528 unreachable;
529 },
530 }
531 }
532
533 fn resolveInst(f: *Function, ref: Air.Inst.Ref) !CValue {
534 const gop = try f.value_map.getOrPut(ref);
535 if (!gop.found_existing) {
536 gop.value_ptr.* = .{ .constant = .fromInterned(ref.toInterned().?) };
537 }
538 return gop.value_ptr.*;
539 }
540
541 fn wantSafety(f: *Function) bool {
542 return switch (f.dg.mod.optimize_mode) {
543 .debug, .safe => true,
544 .fast, .small => false,
545 };
546 }
547
548 /// Skips the reuse logic. This function should be used for any persistent allocation, i.e.
549 /// those which go into `allocs`. This function does not add the resulting local into `allocs`;
550 /// that responsibility lies with the caller.
551 fn allocLocalValue(f: *Function, local_type: LocalType) !CValue {
552 try f.locals.ensureUnusedCapacity(f.dg.gpa, 1);
553 const index = f.locals.items.len;
554 f.locals.appendAssumeCapacity(local_type);
555 return .{ .new_local = @intCast(index) };
556 }
557
558 fn allocLocal(f: *Function, inst: ?Air.Inst.Index, ty: Type) !CValue {
559 return f.allocAlignedLocal(inst, .{ .type = ty });
560 }
561
562 /// Only allocates the local; does not print anything. Will attempt to re-use locals, so should
563 /// not be used for persistent locals (i.e. those in `allocs`).
564 fn allocAlignedLocal(f: *Function, inst: ?Air.Inst.Index, local_type: LocalType) !CValue {
565 const result: CValue = result: {
566 if (f.free_locals_map.getPtr(local_type)) |locals_list| {
567 if (locals_list.pop()) |local_entry| {
568 break :result .{ .new_local = local_entry.key };
569 }
570 }
571 break :result try f.allocLocalValue(local_type);
572 };
573 if (inst) |i| {
574 log.debug("%{d}: allocating t{d}", .{ i, result.new_local });
575 } else {
576 log.debug("allocating t{d}", .{result.new_local});
577 }
578 return result;
579 }
580
581 fn writeCValue(f: *Function, w: *Writer, c_value: CValue, location: ValueRenderLocation) !void {
582 switch (c_value) {
583 .none => unreachable,
584 .new_local, .local => |i| try w.print("t{d}", .{i}),
585 .local_ref => |i| try w.print("&t{d}", .{i}),
586 .constant => |val| try f.dg.renderValue(w, val, location),
587 .arg => |i| try w.print("a{d}", .{i}),
588 .undef => |ty| try f.dg.renderUndefValue(w, ty, location),
589 else => try f.dg.writeCValue(w, c_value),
590 }
591 }
592
593 fn writeCValueDeref(f: *Function, w: *Writer, c_value: CValue) !void {
594 switch (c_value) {
595 .none => unreachable,
596 .new_local, .local, .constant => {
597 try w.writeAll("(*");
598 try f.writeCValue(w, c_value, .other);
599 try w.writeByte(')');
600 },
601 .local_ref => |i| try w.print("t{d}", .{i}),
602 .arg => |i| try w.print("(*a{d})", .{i}),
603 else => try f.dg.writeCValueDeref(w, c_value),
604 }
605 }
606
607 fn writeCValueMember(
608 f: *Function,
609 w: *Writer,
610 c_value: CValue,
611 member: CValue,
612 ) Error!void {
613 switch (c_value) {
614 .new_local, .local, .local_ref, .constant, .arg => {
615 try f.writeCValue(w, c_value, .other);
616 try w.writeByte('.');
617 try f.writeCValue(w, member, .other);
618 },
619 else => return f.dg.writeCValueMember(w, c_value, member),
620 }
621 }
622
623 fn writeCValueDerefMember(f: *Function, w: *Writer, c_value: CValue, member: CValue) !void {
624 switch (c_value) {
625 .new_local, .local, .arg => {
626 try f.writeCValue(w, c_value, .other);
627 try w.writeAll("->");
628 },
629 .constant => {
630 try w.writeByte('(');
631 try f.writeCValue(w, c_value, .other);
632 try w.writeAll(")->");
633 },
634 .local_ref => {
635 try f.writeCValueDeref(w, c_value);
636 try w.writeByte('.');
637 },
638 else => return f.dg.writeCValueDerefMember(w, c_value, member),
639 }
640 try f.writeCValue(w, member, .other);
641 }
642
643 fn fail(f: *Function, comptime format: []const u8, args: anytype) Error {
644 return f.dg.fail(format, args);
645 }
646
647 fn renderType(f: *Function, w: *Writer, ty: Type) !void {
648 return f.dg.renderType(w, ty);
649 }
650
651 fn fmtIntLiteralDec(f: *Function, val: Value) !std.fmt.Alt(FormatIntLiteralContext, formatIntLiteral) {
652 return f.dg.fmtIntLiteralDec(val, .other);
653 }
654
655 fn fmtIntLiteralHex(f: *Function, val: Value) !std.fmt.Alt(FormatIntLiteralContext, formatIntLiteral) {
656 return f.dg.fmtIntLiteralHex(val, .other);
657 }
658
659 pub fn deinit(f: *Function) void {
660 const gpa = f.dg.gpa;
661 f.allocs.deinit(gpa);
662 f.locals.deinit(gpa);
663 deinitFreeLocalsMap(gpa, &f.free_locals_map);
664 f.blocks.deinit(gpa);
665 f.value_map.deinit();
666 f.need_tag_name_funcs.deinit(gpa);
667 f.need_never_tail_funcs.deinit(gpa);
668 f.need_never_inline_funcs.deinit(gpa);
669 f.loop_switch_conds.deinit(gpa);
670 }
671
672 fn typeOf(f: *Function, inst: Air.Inst.Ref) Type {
673 return f.air.typeOf(inst, &f.dg.pt.zcu.intern_pool);
674 }
675
676 fn typeOfIndex(f: *Function, inst: Air.Inst.Index) Type {
677 return f.air.typeOfIndex(inst, &f.dg.pt.zcu.intern_pool);
678 }
679
680 fn copyCValue(f: *Function, dst: CValue, src: CValue) !void {
681 switch (dst) {
682 .new_local, .local => |dst_local_index| switch (src) {
683 .new_local, .local => |src_local_index| if (dst_local_index == src_local_index) return,
684 else => {},
685 },
686 else => {},
687 }
688 const w = &f.code.writer;
689 try f.writeCValue(w, dst, .other);
690 try w.writeAll(" = ");
691 try f.writeCValue(w, src, .other);
692 try w.writeByte(';');
693 try f.newline();
694 }
695
696 fn moveCValue(f: *Function, inst: Air.Inst.Index, ty: Type, src: CValue) !CValue {
697 switch (src) {
698 // Move the freshly allocated local to be owned by this instruction,
699 // by returning it here instead of freeing it.
700 .new_local => return src,
701 else => {
702 try freeCValue(f, inst, src);
703 const dst = try f.allocLocal(inst, ty);
704 try f.copyCValue(dst, src);
705 return dst;
706 },
707 }
708 }
709
710 fn freeCValue(f: *Function, inst: ?Air.Inst.Index, val: CValue) !void {
711 switch (val) {
712 .new_local => |local_index| try freeLocal(f, inst, local_index, null),
713 else => {},
714 }
715 }
716};
717
718/// This data is available when rendering *any* C source code (function or otherwise).
719pub const DeclGen = struct {
720 gpa: Allocator,
721 arena: Allocator,
722 pt: Zcu.PerThread,
723 mod: *Module,
724 owner_nav: InternPool.Nav.Index.Optional,
725 is_naked_fn: bool,
726 expected_block: ?u32,
727 ctype_deps: CType.Dependencies,
728 /// This map contains all the UAVs we saw generating this function.
729 /// `link.C` will merge them into its `uavs`/`aligned_uavs` fields.
730 /// Key is the value of the UAV; value is the UAV's alignment, or
731 /// `.none` for natural alignment. The specified alignment is never
732 /// less than the natural alignment.
733 uavs: std.array_hash_map.Auto(InternPool.Index, Alignment),
734
735 fn fail(dg: *DeclGen, comptime format: []const u8, args: anytype) Error {
736 @branchHint(.cold);
737 return dg.pt.zcu.codegenFail(dg.owner_nav.unwrap().?, format, args);
738 }
739
740 fn renderUav(
741 dg: *DeclGen,
742 w: *Writer,
743 uav: InternPool.Key.Ptr.BaseAddr.Uav,
744 location: ValueRenderLocation,
745 ) Error!void {
746 const pt = dg.pt;
747 const zcu = pt.zcu;
748 const ip = &zcu.intern_pool;
749 const uav_val = Value.fromInterned(uav.val);
750 const uav_ty = uav_val.typeOf(zcu);
751
752 // Render an undefined pointer if we have a pointer to a zero-bit or comptime type.
753 const ptr_ty: Type = .fromInterned(uav.orig_ty);
754 if (ptr_ty.isPtrAtRuntime(zcu) and !uav_ty.isRuntimeFnOrHasRuntimeBits(zcu)) {
755 try w.writeByte('(');
756 try dg.renderOpvPointer(w, ptr_ty, location);
757 return w.writeByte(')');
758 }
759
760 switch (ip.indexToKey(uav.val)) {
761 .func => unreachable,
762 .@"extern" => unreachable,
763 else => {},
764 }
765
766 // We shouldn't cast C function pointers as this is UB (when you call
767 // them). The analysis until now should ensure that the C function
768 // pointers are compatible. If they are not, then there is a bug
769 // somewhere and we should let the C compiler tell us about it.
770 const elem_ty = ptr_ty.childType(zcu);
771 const need_cast = elem_ty.toIntern() != uav_ty.toIntern() and
772 elem_ty.zigTypeTag(zcu) != .@"fn" or uav_ty.zigTypeTag(zcu) != .@"fn";
773 if (need_cast) {
774 try w.writeAll("((");
775 try dg.renderType(w, ptr_ty);
776 try w.writeByte(')');
777 }
778 try w.writeByte('&');
779 try renderUavName(w, uav_val);
780 if (need_cast) try w.writeByte(')');
781
782 // Indicate that the anon decl should be rendered to the output so that
783 // our reference above is not undefined.
784 const ptr_type = ip.indexToKey(uav.orig_ty).ptr_type;
785 const gop = try dg.uavs.getOrPut(dg.gpa, uav.val);
786 if (!gop.found_existing) gop.value_ptr.* = .none;
787 // If there is an explicit alignment, greater than the current one, use it.
788 // Note that we intentionally start at `.none`, so `gop.value_ptr.*` is never
789 // underaligned, so we don't need to worry about the `.none` case here.
790 if (ptr_type.flags.alignment != .none) {
791 // Resolve the current alignment so we can choose the bigger one.
792 const cur_alignment: Alignment = if (gop.value_ptr.* == .none) abi: {
793 break :abi Type.fromInterned(ptr_type.child).abiAlignment(zcu);
794 } else gop.value_ptr.*;
795 gop.value_ptr.* = cur_alignment.maxStrict(ptr_type.flags.alignment);
796 }
797 }
798
799 fn renderNav(
800 dg: *DeclGen,
801 w: *Writer,
802 nav_index: InternPool.Nav.Index,
803 location: ValueRenderLocation,
804 ) Error!void {
805 const pt = dg.pt;
806 const zcu = pt.zcu;
807 const ip = &zcu.intern_pool;
808
809 // Chase function values in order to be able to reference the original function.
810 const owner_nav = switch (ip.getNav(nav_index).resolved.?.value) {
811 .none => nav_index, // this can't be an extern or a function
812 else => |value| switch (ip.indexToKey(value)) {
813 .func => |f| f.owner_nav,
814 .@"extern" => |e| e.owner_nav,
815 else => nav_index,
816 },
817 };
818
819 // Render an undefined pointer if we have a pointer to a zero-bit or comptime type.
820 const nav_ty: Type = .fromInterned(ip.getNav(owner_nav).resolved.?.type);
821 const ptr_ty = try pt.navPtrType(owner_nav);
822 if (nav_ty.zigTypeTag(zcu) != .@"opaque" and !nav_ty.isRuntimeFnOrHasRuntimeBits(zcu)) {
823 try w.writeByte('(');
824 try dg.renderOpvPointer(w, ptr_ty, location);
825 return w.writeByte(')');
826 }
827
828 // We shouldn't cast C function pointers as this is UB (when you call
829 // them). The analysis until now should ensure that the C function
830 // pointers are compatible. If they are not, then there is a bug
831 // somewhere and we should let the C compiler tell us about it.
832 const elem_ty = ptr_ty.childType(zcu);
833 const need_cast = elem_ty.toIntern() != nav_ty.toIntern() and
834 elem_ty.zigTypeTag(zcu) != .@"fn" or nav_ty.zigTypeTag(zcu) != .@"fn";
835 if (need_cast) {
836 try w.writeAll("((");
837 try dg.renderType(w, ptr_ty);
838 try w.writeByte(')');
839 }
840 try w.writeByte('&');
841 try renderNavName(w, owner_nav, ip);
842 if (need_cast) try w.writeByte(')');
843 }
844
845 fn renderOpvPointer(
846 dg: *DeclGen,
847 w: *Writer,
848 ptr_ty: Type,
849 location: ValueRenderLocation,
850 ) Error!void {
851 const zcu = dg.pt.zcu;
852 const target = zcu.getTarget();
853 try w.writeByte('(');
854 try dg.renderType(w, ptr_ty);
855 return w.print("){f}", .{fmtUnsignedIntLiteralSmall(
856 target,
857 .uintptr_t,
858 ptr_ty.ptrAlignment(zcu).forward(undefPattern(u64) >> @intCast(64 - target.ptrBitWidth())),
859 location == .static_initializer,
860 16,
861 .lower,
862 )});
863 }
864
865 fn renderPointer(
866 dg: *DeclGen,
867 w: *Writer,
868 derivation: Value.PointerDeriveStep,
869 location: ValueRenderLocation,
870 ) Error!void {
871 const pt = dg.pt;
872 const zcu = pt.zcu;
873 switch (derivation) {
874 .comptime_alloc_ptr, .comptime_field_ptr => unreachable,
875 .int => |int| {
876 const addr_val = try pt.intValue(.usize, int.addr);
877 try w.writeByte('(');
878 try dg.renderType(w, int.ptr_ty);
879 try w.print("){f}", .{try dg.fmtIntLiteralHex(addr_val, .other)});
880 },
881
882 .nav_ptr => |nav| try dg.renderNav(w, nav, location),
883 .uav_ptr => |uav| try dg.renderUav(w, uav, location),
884
885 inline .eu_payload_ptr, .opt_payload_ptr => |info| {
886 try w.writeAll("&(");
887 try dg.renderPointer(w, info.parent.*, location);
888 try w.writeAll(")->payload");
889 },
890
891 .field_ptr => |field| {
892 const parent_ptr_ty = try field.parent.ptrType(pt);
893
894 switch (fieldLocation(parent_ptr_ty, field.result_ptr_ty, field.field_idx, zcu)) {
895 .begin => {
896 try w.writeByte('(');
897 try dg.renderType(w, field.result_ptr_ty);
898 try w.writeByte(')');
899 try dg.renderPointer(w, field.parent.*, location);
900 },
901 .field => |name| {
902 try w.writeAll("&(");
903 try dg.renderPointer(w, field.parent.*, location);
904 try w.writeAll(")->");
905 try dg.writeCValue(w, name);
906 },
907 .byte_offset => |byte_offset| {
908 try w.writeByte('(');
909 try dg.renderType(w, field.result_ptr_ty);
910 try w.writeByte(')');
911 const offset_val = try pt.intValue(.usize, byte_offset);
912 try w.writeAll("((char *)");
913 try dg.renderPointer(w, field.parent.*, location);
914 try w.print(" + {f})", .{try dg.fmtIntLiteralDec(offset_val, .other)});
915 },
916 }
917 },
918
919 .elem_ptr => |elem| if (!(try elem.parent.ptrType(pt)).childType(zcu).hasRuntimeBits(zcu)) {
920 // Element type is zero-bit, so lowers to `void`. The index is irrelevant; just cast the pointer.
921 try w.writeByte('(');
922 try dg.renderType(w, elem.result_ptr_ty);
923 try w.writeByte(')');
924 try dg.renderPointer(w, elem.parent.*, location);
925 } else {
926 const index_val = try pt.intValue(.usize, elem.elem_idx);
927 try w.writeByte('(');
928 // We want to do pointer arithmetic on a pointer to the element type, but the parent
929 // might be a pointer-to-array, in which case we must cast it.
930 if (elem.result_ptr_ty.toIntern() != (try elem.parent.ptrType(pt)).toIntern()) {
931 try w.writeByte('(');
932 try dg.renderType(w, elem.result_ptr_ty);
933 try w.writeByte(')');
934 }
935 try dg.renderPointer(w, elem.parent.*, location);
936 try w.print(" + {f})", .{try dg.fmtIntLiteralDec(index_val, .other)});
937 },
938
939 .offset_and_cast => |oac| {
940 try w.writeByte('(');
941 try dg.renderType(w, oac.new_ptr_ty);
942 try w.writeByte(')');
943 if (oac.byte_offset == 0) {
944 try dg.renderPointer(w, oac.parent.*, location);
945 } else {
946 const offset_val = try pt.intValue(.usize, oac.byte_offset);
947 try w.writeAll("((char *)");
948 try dg.renderPointer(w, oac.parent.*, location);
949 try w.print(" + {f})", .{try dg.fmtIntLiteralDec(offset_val, .other)});
950 }
951 },
952 }
953 }
954
955 fn renderValueAsLvalue(
956 dg: *DeclGen,
957 w: *Writer,
958 val: Value,
959 ) Error!void {
960 const zcu = dg.pt.zcu;
961
962 // If the type of `val` lowers to a C struct or union type, then `renderValue` will render
963 // it as a compound literal, and compound literals are already lvalues.
964 const ty = val.typeOf(zcu);
965 const is_aggregate: bool = switch (ty.zigTypeTag(zcu)) {
966 .@"struct", .@"union" => switch (ty.containerLayout(zcu)) {
967 .auto, .@"extern" => true,
968 .@"packed" => false,
969 },
970 .array,
971 .vector,
972 .error_union,
973 .optional,
974 => true,
975 else => false,
976 };
977 if (is_aggregate) return renderValue(dg, w, val, .other);
978
979 // Otherwise, use a UAV.
980 const gop = try dg.uavs.getOrPut(dg.gpa, val.toIntern());
981 if (!gop.found_existing) gop.value_ptr.* = .none;
982 try renderUavName(w, val);
983 }
984
985 fn renderValue(
986 dg: *DeclGen,
987 w: *Writer,
988 val: Value,
989 location: ValueRenderLocation,
990 ) Error!void {
991 const pt = dg.pt;
992 const zcu = pt.zcu;
993 const ip = &zcu.intern_pool;
994 const target = &dg.mod.resolved_target.result;
995
996 const initializer_type: ValueRenderLocation = switch (location) {
997 .static_initializer => .static_initializer,
998 else => .initializer,
999 };
1000
1001 const ty = val.typeOf(zcu);
1002 switch (ip.indexToKey(val.toIntern())) {
1003 // types, not values
1004 .int_type,
1005 .ptr_type,
1006 .array_type,
1007 .vector_type,
1008 .opt_type,
1009 .anyframe_type,
1010 .error_union_type,
1011 .simple_type,
1012 .struct_type,
1013 .tuple_type,
1014 .union_type,
1015 .opaque_type,
1016 .spirv_type,
1017 .enum_type,
1018 .func_type,
1019 .error_set_type,
1020 .inferred_error_set_type,
1021 // memoization, not values
1022 .memoized_call,
1023 => unreachable,
1024
1025 .undef => try dg.renderUndefValue(w, ty, location),
1026 .simple_value => |simple_value| switch (simple_value) {
1027 // non-runtime values
1028 .void => unreachable,
1029 .null => unreachable,
1030 .@"unreachable" => unreachable,
1031
1032 .false => try w.writeAll("false"),
1033 .true => try w.writeAll("true"),
1034 },
1035 .@"extern",
1036 .func,
1037 .enum_literal,
1038 => unreachable, // non-runtime values
1039 .int => try w.print("{f}", .{try dg.fmtIntLiteralDec(val, location)}),
1040 .err => |err| try renderErrorName(w, err.name.toSlice(ip)),
1041 .error_union => |error_union| {
1042 if (!location.isInitializer()) {
1043 try w.writeByte('(');
1044 try dg.renderType(w, ty);
1045 try w.writeByte(')');
1046 }
1047 try w.writeAll("{ .error = ");
1048 switch (error_union.val) {
1049 .err_name => |err_name| try renderErrorName(w, err_name.toSlice(ip)),
1050 .payload => try w.writeByte('0'),
1051 }
1052 if (ty.errorUnionPayload(zcu).hasRuntimeBits(zcu)) {
1053 try w.writeAll(", .payload = ");
1054 switch (error_union.val) {
1055 .err_name => try dg.renderUndefValue(w, ty.errorUnionPayload(zcu), initializer_type),
1056 .payload => |payload| try dg.renderValue(w, .fromInterned(payload), initializer_type),
1057 }
1058 }
1059 try w.writeAll(" }");
1060 },
1061 .enum_tag => |enum_tag| try dg.renderValue(w, .fromInterned(enum_tag.int), location),
1062 .float => {
1063 const bits = ty.floatBits(target);
1064 const f128_val = val.toFloat(f128, zcu);
1065
1066 assert(bits <= 128);
1067 var repr_val_limbs: [BigInt.calcTwosCompLimbCount(128)]BigIntLimb = undefined;
1068 var repr_val_big = BigInt.Mutable{
1069 .limbs = &repr_val_limbs,
1070 .len = undefined,
1071 .positive = undefined,
1072 };
1073
1074 switch (bits) {
1075 else => unreachable,
1076 16 => repr_val_big.set(@as(u16, @bitCast(val.toFloat(f16, zcu)))),
1077 32 => repr_val_big.set(@as(u32, @bitCast(val.toFloat(f32, zcu)))),
1078 64 => repr_val_big.set(@as(u64, @bitCast(val.toFloat(f64, zcu)))),
1079 80 => repr_val_big.set(@as(u80, @bitCast(val.toFloat(f80, zcu)))),
1080 128 => repr_val_big.set(@as(u128, @bitCast(f128_val))),
1081 }
1082
1083 if (std.math.isFinite(f128_val)) {
1084 try w.writeAll("zig_make_");
1085 try dg.renderTypeForBuiltinFnName(w, ty);
1086 try w.writeByte('(');
1087 switch (bits) {
1088 else => unreachable,
1089 16 => try w.print("{x}", .{val.toFloat(f16, zcu)}),
1090 32 => try w.print("{x}", .{val.toFloat(f32, zcu)}),
1091 64 => try w.print("{x}", .{val.toFloat(f64, zcu)}),
1092 80 => try w.print("{x}", .{val.toFloat(f80, zcu)}),
1093 128 => try w.print("{x}", .{f128_val}),
1094 }
1095 try w.writeAll(", ");
1096 } else {
1097 // isSignalNan is equivalent to isNan currently, and MSVC doesn't have nans, so prefer nan
1098 const operation = if (std.math.isNan(f128_val))
1099 "nan"
1100 else if (std.math.isSignalNan(f128_val))
1101 "nans"
1102 else if (std.math.isInf(f128_val))
1103 "inf"
1104 else
1105 unreachable;
1106
1107 if (location == .static_initializer) {
1108 if (!std.math.isNan(f128_val) and std.math.isSignalNan(f128_val))
1109 return dg.fail("TODO: C backend: implement nans rendering in static initializers", .{});
1110
1111 // MSVC doesn't have a way to define a custom or signaling NaN value in a constant expression
1112
1113 // TODO: Re-enable this check, otherwise we're writing qnan bit patterns on msvc incorrectly
1114 // if (std.math.isNan(f128_val) and f128_val != std.math.nan(f128))
1115 // return dg.fail("Only quiet nans are supported in global variable initializers", .{});
1116 }
1117
1118 if (location == .static_initializer) {
1119 try w.writeAll("zig_init_special_");
1120 } else {
1121 try w.writeAll("zig_make_special_");
1122 }
1123 try dg.renderTypeForBuiltinFnName(w, ty);
1124 try w.writeByte('(');
1125 if (std.math.signbit(f128_val)) try w.writeByte('-');
1126 try w.writeAll(", ");
1127 try w.writeAll(operation);
1128 try w.writeAll(", ");
1129 if (std.math.isNan(f128_val)) switch (bits) {
1130 else => unreachable,
1131 // We only actually need to pass the significand, but it will get
1132 // properly masked anyway, so just pass the whole value.
1133 16 => try w.print("\"0x{x}\"", .{@as(u16, @bitCast(val.toFloat(f16, zcu)))}),
1134 32 => try w.print("\"0x{x}\"", .{@as(u32, @bitCast(val.toFloat(f32, zcu)))}),
1135 64 => try w.print("\"0x{x}\"", .{@as(u64, @bitCast(val.toFloat(f64, zcu)))}),
1136 80 => try w.print("\"0x{x}\"", .{@as(u80, @bitCast(val.toFloat(f80, zcu)))}),
1137 128 => try w.print("\"0x{x}\"", .{@as(u128, @bitCast(f128_val))}),
1138 };
1139 try w.writeAll(", ");
1140 }
1141 switch (bits) {
1142 else => unreachable,
1143 16, 32, 64 => {
1144 // All unsigned ints matching float types are pre-allocated.
1145 const repr_ty = pt.intType(.unsigned, bits) catch unreachable;
1146 try w.print("{f}", .{try dg.fmtIntLiteralHex(
1147 try pt.intValue_big(repr_ty, repr_val_big.toConst()),
1148 location,
1149 )});
1150 },
1151 80 => try F80Repr.write(@bitCast(val.toFloat(f80, zcu)), w, target, location == .static_initializer),
1152 128 => try F128Repr.write(@bitCast(f128_val), w, target, location == .static_initializer),
1153 }
1154 try w.writeByte(')');
1155 },
1156 .slice => |slice| {
1157 if (!location.isInitializer()) {
1158 try w.writeByte('(');
1159 try dg.renderType(w, ty);
1160 try w.writeByte(')');
1161 }
1162 try w.writeByte('{');
1163 try dg.renderValue(w, .fromInterned(slice.ptr), initializer_type);
1164 try w.writeByte(',');
1165 try dg.renderValue(w, .fromInterned(slice.len), initializer_type);
1166 try w.writeByte('}');
1167 },
1168 .ptr => {
1169 const derivation = try val.pointerDerivation(dg.arena, pt, null);
1170 try w.writeByte('(');
1171 try dg.renderPointer(w, derivation, location);
1172 try w.writeByte(')');
1173 },
1174 .opt => |opt| switch (CType.classifyOptional(ty, zcu)) {
1175 .npv_payload => unreachable, // opv optional
1176 .opv_payload => {
1177 if (!location.isInitializer()) {
1178 try w.writeByte('(');
1179 try dg.renderType(w, ty);
1180 try w.writeByte(')');
1181 }
1182 try w.writeAll(switch (opt.val) {
1183 .none => "{.is_null = true}",
1184 else => "{.is_null = false}",
1185 });
1186 },
1187 .error_set => switch (opt.val) {
1188 .none => try w.writeByte('0'),
1189 else => |payload_val| try dg.renderValue(w, .fromInterned(payload_val), location),
1190 },
1191 .ptr_like => switch (opt.val) {
1192 .none => try w.writeAll("NULL"),
1193 else => |payload_val| try dg.renderValue(w, .fromInterned(payload_val), location),
1194 },
1195 .slice_like => switch (opt.val) {
1196 .none => {
1197 if (!location.isInitializer()) {
1198 try w.writeByte('(');
1199 try dg.renderType(w, ty);
1200 try w.writeByte(')');
1201 }
1202 try w.writeAll("{NULL,");
1203 try dg.renderUndefValue(w, .usize, initializer_type);
1204 try w.writeByte('}');
1205 },
1206 else => |payload_val| try dg.renderValue(w, .fromInterned(payload_val), location),
1207 },
1208 .@"struct" => {
1209 if (!location.isInitializer()) {
1210 try w.writeByte('(');
1211 try dg.renderType(w, ty);
1212 try w.writeByte(')');
1213 }
1214 switch (opt.val) {
1215 .none => {
1216 try w.writeAll("{ .is_null = true, .payload = ");
1217 try dg.renderUndefValue(w, ty.optionalChild(zcu), initializer_type);
1218 try w.writeAll(" }");
1219 },
1220 else => |payload_val| {
1221 try w.writeAll("{ .is_null = false, .payload = ");
1222 try dg.renderValue(w, .fromInterned(payload_val), initializer_type);
1223 try w.writeAll(" }");
1224 },
1225 }
1226 },
1227 },
1228 .aggregate => switch (ip.indexToKey(ty.toIntern())) {
1229 .array_type, .vector_type => {
1230 if (!location.isInitializer()) {
1231 try w.writeByte('(');
1232 try dg.renderType(w, ty);
1233 try w.writeByte(')');
1234 }
1235 try w.writeByte('{');
1236 const ai = ty.arrayInfo(zcu);
1237 if (ai.elem_type.eql(.u8)) {
1238 var literal: StringLiteral = .init(w, @intCast(ty.arrayLenIncludingSentinel(zcu)));
1239 try literal.start();
1240 var index: usize = 0;
1241 while (index < ai.len) : (index += 1) {
1242 const elem_val = try val.elemValue(pt, index);
1243 const elem_val_u8: u8 = if (elem_val.isUndef(zcu))
1244 undefPattern(u8)
1245 else
1246 @intCast(elem_val.toUnsignedInt(zcu));
1247 try literal.writeChar(elem_val_u8);
1248 }
1249 if (ai.sentinel) |s| {
1250 const s_u8: u8 = @intCast(s.toUnsignedInt(zcu));
1251 if (s_u8 != 0) try literal.writeChar(s_u8);
1252 }
1253 try literal.end();
1254 } else {
1255 try w.writeByte('{');
1256 var index: usize = 0;
1257 while (index < ai.len) : (index += 1) {
1258 if (index > 0) try w.writeByte(',');
1259 const elem_val = try val.elemValue(pt, index);
1260 try dg.renderValue(w, elem_val, initializer_type);
1261 }
1262 if (ai.sentinel) |s| {
1263 if (index > 0) try w.writeByte(',');
1264 try dg.renderValue(w, s, initializer_type);
1265 }
1266 try w.writeByte('}');
1267 }
1268 try w.writeByte('}');
1269 },
1270 .tuple_type => |tuple| {
1271 if (!location.isInitializer()) {
1272 try w.writeByte('(');
1273 try dg.renderType(w, ty);
1274 try w.writeByte(')');
1275 }
1276
1277 try w.writeByte('{');
1278 var empty = true;
1279 for (0..tuple.types.len) |field_index| {
1280 const comptime_val = tuple.values.get(ip)[field_index];
1281 if (comptime_val != .none) continue;
1282 const field_ty: Type = .fromInterned(tuple.types.get(ip)[field_index]);
1283 if (!field_ty.hasRuntimeBits(zcu)) continue;
1284
1285 if (!empty) try w.writeByte(',');
1286
1287 const field_val = Value.fromInterned(
1288 switch (ip.indexToKey(val.toIntern()).aggregate.storage) {
1289 .bytes => |bytes| try pt.intern(.{ .int = .{
1290 .ty = field_ty.toIntern(),
1291 .storage = .{ .u64 = bytes.at(field_index, ip) },
1292 } }),
1293 .elems => |elems| elems[field_index],
1294 .repeated_elem => |elem| elem,
1295 },
1296 );
1297 try dg.renderValue(w, field_val, initializer_type);
1298
1299 empty = false;
1300 }
1301 try w.writeByte('}');
1302 },
1303 .struct_type => {
1304 const loaded_struct = ip.loadStructType(ty.toIntern());
1305 assert(loaded_struct.layout != .@"packed");
1306
1307 if (!location.isInitializer()) {
1308 try w.writeByte('(');
1309 try dg.renderType(w, ty);
1310 try w.writeByte(')');
1311 }
1312
1313 try w.writeByte('{');
1314 var field_it = loaded_struct.iterateRuntimeOrder(ip);
1315 var need_comma = false;
1316 while (field_it.next()) |field_index| {
1317 const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
1318 if (!field_ty.hasRuntimeBits(zcu)) continue;
1319
1320 if (need_comma) try w.writeByte(',');
1321 need_comma = true;
1322 const field_val = switch (ip.indexToKey(val.toIntern()).aggregate.storage) {
1323 .bytes => |bytes| try pt.intern(.{ .int = .{
1324 .ty = field_ty.toIntern(),
1325 .storage = .{ .u64 = bytes.at(field_index, ip) },
1326 } }),
1327 .elems => |elems| elems[field_index],
1328 .repeated_elem => |elem| elem,
1329 };
1330 try dg.renderValue(w, Value.fromInterned(field_val), initializer_type);
1331 }
1332 try w.writeByte('}');
1333 },
1334 else => unreachable,
1335 },
1336 .bitpack => |bitpack| return dg.renderValue(w, .fromInterned(bitpack.backing_int_val), location),
1337 .un => |un| {
1338 const loaded_union = ip.loadUnionType(ty.toIntern());
1339 if (un.tag == .none) {
1340 assert(loaded_union.layout == .@"extern");
1341 if (location == .static_initializer) {
1342 return dg.fail("TODO: C backend: implement extern union backing type rendering in static initializers", .{});
1343 }
1344
1345 const ptr_ty = try pt.singleConstPtrType(ty);
1346 try w.writeAll("*(");
1347 try dg.renderType(w, ptr_ty);
1348 try w.writeAll(")&");
1349 // We need an lvalue for '&'.
1350 try dg.renderValueAsLvalue(w, .fromInterned(un.val));
1351 } else {
1352 if (!location.isInitializer()) {
1353 try w.writeByte('(');
1354 try dg.renderType(w, ty);
1355 try w.writeByte(')');
1356 }
1357 if (ty.unionHasAllZeroBitFieldTypes(zcu)) {
1358 assert(loaded_union.has_runtime_tag); // otherwise it does not have runtime bits
1359 try w.writeAll("{ .tag = ");
1360 try dg.renderValue(w, .fromInterned(un.tag), initializer_type);
1361 try w.writeAll(" }");
1362 return;
1363 }
1364
1365 if (loaded_union.layout == .auto) try w.writeByte('{');
1366
1367 if (loaded_union.has_runtime_tag) {
1368 try w.writeAll(" .tag = ");
1369 try dg.renderValue(w, .fromInterned(un.tag), initializer_type);
1370 try w.writeAll(", .payload = ");
1371 }
1372
1373 const enum_tag_ty: Type = .fromInterned(loaded_union.enum_tag_type);
1374 const active_field_index = enum_tag_ty.enumTagFieldIndex(.fromInterned(un.tag), zcu).?;
1375 const active_field_ty: Type = .fromInterned(loaded_union.field_types.get(ip)[active_field_index]);
1376 if (active_field_ty.hasRuntimeBits(zcu)) {
1377 const active_field_name = enum_tag_ty.enumFieldName(active_field_index, zcu);
1378 try w.print("{{ .{f} = ", .{fmtIdentSolo(active_field_name.toSlice(ip))});
1379 try dg.renderValue(w, .fromInterned(un.val), initializer_type);
1380 try w.writeAll(" }");
1381 } else {
1382 const first_field_ty: Type = for (loaded_union.field_types.get(ip)) |field_ty_ip| {
1383 const field_ty: Type = .fromInterned(field_ty_ip);
1384 if (!field_ty.hasRuntimeBits(pt.zcu)) continue;
1385 break field_ty;
1386 } else unreachable;
1387 try w.writeByte('{');
1388 try dg.renderUndefValue(w, first_field_ty, initializer_type);
1389 try w.writeByte('}');
1390 }
1391
1392 if (loaded_union.has_runtime_tag) try w.writeByte(' ');
1393 if (loaded_union.layout == .auto) try w.writeByte('}');
1394 }
1395 },
1396 }
1397 }
1398
1399 fn renderUndefValue(
1400 dg: *DeclGen,
1401 w: *Writer,
1402 ty: Type,
1403 location: ValueRenderLocation,
1404 ) Error!void {
1405 const pt = dg.pt;
1406 const zcu = pt.zcu;
1407 const ip = &zcu.intern_pool;
1408 const target = &dg.mod.resolved_target.result;
1409
1410 const initializer_type: ValueRenderLocation = switch (location) {
1411 .static_initializer => .static_initializer,
1412 else => .initializer,
1413 };
1414
1415 const safety_on = switch (dg.mod.optimize_mode) {
1416 .debug, .safe => true,
1417 .fast, .small => false,
1418 };
1419
1420 switch (ty.toIntern()) {
1421 .c_longdouble_type,
1422 .f16_type,
1423 .f32_type,
1424 .f64_type,
1425 .f80_type,
1426 .f128_type,
1427 => {
1428 const bits = ty.floatBits(target);
1429
1430 try w.writeAll("zig_make_");
1431 try dg.renderTypeForBuiltinFnName(w, ty);
1432 try w.writeByte('(');
1433 switch (bits) {
1434 else => unreachable,
1435 16 => try w.print("{x}", .{undefPattern(f16)}),
1436 32 => try w.print("{x}", .{undefPattern(f32)}),
1437 64 => try w.print("{x}", .{undefPattern(f64)}),
1438 80 => try w.print("{x}", .{undefPattern(f80)}),
1439 128 => try w.print("{x}", .{undefPattern(f128)}),
1440 }
1441 try w.writeAll(", ");
1442 switch (bits) {
1443 else => unreachable,
1444 16, 32, 64 => {
1445 // All unsigned ints matching float types are pre-allocated.
1446 const repr_ty = dg.pt.intType(.unsigned, bits) catch unreachable;
1447 try dg.renderUndefValue(w, repr_ty, .other);
1448 },
1449 80 => try undefPattern(F80Repr).write(w, target, location == .static_initializer),
1450 128 => try undefPattern(F128Repr).write(w, target, location == .static_initializer),
1451 }
1452 return w.writeByte(')');
1453 },
1454 .bool_type => try w.writeAll(if (safety_on) "0xaa" else "false"),
1455 else => switch (ip.indexToKey(ty.toIntern())) {
1456 .simple_type, // anyerror, c_char (etc), usize, isize
1457 .int_type,
1458 .enum_type,
1459 .error_set_type,
1460 .inferred_error_set_type,
1461 => switch (CType.classifyInt(ty, zcu)) {
1462 .void => unreachable, // opv
1463 .small => |s| {
1464 const int = ty.intInfo(zcu);
1465 var buf: [std.math.big.int.calcTwosCompLimbCount(128)]std.math.big.Limb = undefined;
1466 var bigint: std.math.big.int.Mutable = .init(&buf, undefPattern(u128));
1467 bigint.truncate(bigint.toConst(), int.signedness, int.bits);
1468 const fmt_undef: FormatInt128 = .{
1469 .target = zcu.getTarget(),
1470 .int_cty = s,
1471 .val = bigint.toConst(),
1472 .is_global = location == .static_initializer,
1473 .base = 16,
1474 .case = .lower,
1475 };
1476 try w.print("{f}", .{fmt_undef});
1477 },
1478 .big => |big| {
1479 var buf: [std.math.big.int.calcTwosCompLimbCount(128)]std.math.big.Limb = undefined;
1480 var limb_bigint: std.math.big.int.Mutable = .init(&buf, undefPattern(u128));
1481 limb_bigint.truncate(limb_bigint.toConst(), .unsigned, big.limb_size.bits());
1482 const fmt_undef_limb: FormatInt128 = .{
1483 .target = zcu.getTarget(),
1484 .int_cty = big.limb_size.unsigned(),
1485 .val = limb_bigint.toConst(),
1486 .is_global = location == .static_initializer,
1487 .base = 16,
1488 .case = .lower,
1489 };
1490
1491 if (!location.isInitializer()) {
1492 try w.writeByte('(');
1493 try dg.renderType(w, ty);
1494 try w.writeByte(')');
1495 }
1496 try w.writeAll("{{");
1497 try w.print("{f}", .{fmt_undef_limb});
1498 for (1..big.limbs_len) |_| {
1499 try w.print(",{f}", .{fmt_undef_limb});
1500 }
1501 try w.writeAll("}}");
1502 },
1503 },
1504 .ptr_type => |ptr_type| switch (ptr_type.flags.size) {
1505 .one, .many, .c => {
1506 try w.writeAll("((");
1507 try dg.renderType(w, ty);
1508 try w.writeByte(')');
1509 try dg.renderUndefValue(w, .usize, location);
1510 try w.writeByte(')');
1511 },
1512 .slice => {
1513 if (!location.isInitializer()) {
1514 try w.writeByte('(');
1515 try dg.renderType(w, ty);
1516 try w.writeByte(')');
1517 }
1518
1519 try w.writeByte('{');
1520 try dg.renderUndefValue(w, ty.slicePtrFieldType(zcu), initializer_type);
1521 try w.writeByte(',');
1522 try dg.renderUndefValue(w, .usize, initializer_type);
1523 try w.writeByte('}');
1524 },
1525 },
1526 .opt_type => |child_type| switch (CType.classifyOptional(ty, zcu)) {
1527 .npv_payload => unreachable, // opv optional
1528
1529 .error_set,
1530 .ptr_like,
1531 .slice_like,
1532 => try dg.renderUndefValue(w, .fromInterned(child_type), location),
1533
1534 .opv_payload => {
1535 if (!location.isInitializer()) {
1536 try w.writeByte('(');
1537 try dg.renderType(w, ty);
1538 try w.writeByte(')');
1539 }
1540 try w.writeAll(if (safety_on) "{.is_null=0xaa}" else "{.is_null=false}");
1541 },
1542
1543 .@"struct" => {
1544 if (!location.isInitializer()) {
1545 try w.writeByte('(');
1546 try dg.renderType(w, ty);
1547 try w.writeByte(')');
1548 }
1549 try w.writeAll("{ .is_null = ");
1550 try dg.renderUndefValue(w, .bool, initializer_type);
1551 try w.writeAll(", .payload = ");
1552 try dg.renderUndefValue(w, .fromInterned(child_type), initializer_type);
1553 try w.writeAll(" }");
1554 },
1555 },
1556 .struct_type => {
1557 const loaded_struct = ip.loadStructType(ty.toIntern());
1558 switch (loaded_struct.layout) {
1559 .auto, .@"extern" => {
1560 if (!location.isInitializer()) {
1561 try w.writeByte('(');
1562 try dg.renderType(w, ty);
1563 try w.writeByte(')');
1564 }
1565 try w.writeByte('{');
1566 var field_it = loaded_struct.iterateRuntimeOrder(ip);
1567 var need_comma = false;
1568 while (field_it.next()) |field_index| {
1569 const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
1570 if (!field_ty.hasRuntimeBits(zcu)) continue;
1571
1572 if (need_comma) try w.writeByte(',');
1573 need_comma = true;
1574 try dg.renderUndefValue(w, field_ty, initializer_type);
1575 }
1576 return w.writeByte('}');
1577 },
1578 .@"packed" => return dg.renderUndefValue(w, ty.backingIntType(zcu), location),
1579 }
1580 },
1581 .tuple_type => |tuple_info| {
1582 if (!location.isInitializer()) {
1583 try w.writeByte('(');
1584 try dg.renderType(w, ty);
1585 try w.writeByte(')');
1586 }
1587
1588 try w.writeByte('{');
1589 var need_comma = false;
1590 for (0..tuple_info.types.len) |field_index| {
1591 if (tuple_info.values.get(ip)[field_index] != .none) continue;
1592 const field_ty: Type = .fromInterned(tuple_info.types.get(ip)[field_index]);
1593 if (!field_ty.hasRuntimeBits(zcu)) continue;
1594
1595 if (need_comma) try w.writeByte(',');
1596 need_comma = true;
1597 try dg.renderUndefValue(w, field_ty, initializer_type);
1598 }
1599 return w.writeByte('}');
1600 },
1601 .union_type => {
1602 const loaded_union = ip.loadUnionType(ty.toIntern());
1603 switch (loaded_union.layout) {
1604 .auto, .@"extern" => {
1605 if (!location.isInitializer()) {
1606 try w.writeByte('(');
1607 try dg.renderType(w, ty);
1608 try w.writeByte(')');
1609 }
1610
1611 const first_field_ty: Type = for (loaded_union.field_types.get(ip)) |field_ty_ip| {
1612 const field_ty: Type = .fromInterned(field_ty_ip);
1613 if (!field_ty.hasRuntimeBits(pt.zcu)) continue;
1614 break field_ty;
1615 } else {
1616 assert(loaded_union.has_runtime_tag); // otherwise it does not have runtime bits
1617 try w.writeAll("{ .tag = ");
1618 try dg.renderUndefValue(w, .fromInterned(loaded_union.enum_tag_type), initializer_type);
1619 try w.writeAll(" }");
1620 return;
1621 };
1622
1623 if (loaded_union.layout == .auto) try w.writeByte('{');
1624
1625 if (loaded_union.has_runtime_tag) {
1626 try w.writeAll(" .tag = ");
1627 try dg.renderUndefValue(w, .fromInterned(loaded_union.enum_tag_type), initializer_type);
1628 try w.writeAll(", .payload = ");
1629 }
1630
1631 try w.writeByte('{');
1632 try dg.renderUndefValue(w, first_field_ty, initializer_type);
1633 try w.writeByte('}');
1634
1635 if (loaded_union.has_runtime_tag) try w.writeByte(' ');
1636 if (loaded_union.layout == .auto) try w.writeByte('}');
1637 },
1638 .@"packed" => return dg.renderUndefValue(w, ty.backingIntType(zcu), location),
1639 }
1640 },
1641 .error_union_type => |error_union| {
1642 if (!location.isInitializer()) {
1643 try w.writeByte('(');
1644 try dg.renderType(w, ty);
1645 try w.writeByte(')');
1646 }
1647 try w.writeAll("{ .error = ");
1648 try dg.renderUndefValue(w, .fromInterned(error_union.error_set_type), initializer_type);
1649 if (Type.fromInterned(error_union.payload_type).hasRuntimeBits(zcu)) {
1650 try w.writeAll(", .payload = ");
1651 try dg.renderUndefValue(w, .fromInterned(error_union.payload_type), initializer_type);
1652 }
1653 try w.writeAll(" }");
1654 },
1655 .array_type, .vector_type => {
1656 if (!location.isInitializer()) {
1657 try w.writeByte('(');
1658 try dg.renderType(w, ty);
1659 try w.writeByte(')');
1660 }
1661 try w.writeByte('{');
1662 const ai = ty.arrayInfo(zcu);
1663 if (ai.elem_type.eql(.u8)) {
1664 var literal: StringLiteral = .init(w, @intCast(ty.arrayLenIncludingSentinel(zcu)));
1665 try literal.start();
1666 var index: u64 = 0;
1667 while (index < ai.len) : (index += 1) try literal.writeChar(0xaa);
1668 if (ai.sentinel) |s| {
1669 const s_u8: u8 = @intCast(s.toUnsignedInt(zcu));
1670 if (s_u8 != 0) try literal.writeChar(s_u8);
1671 }
1672 try literal.end();
1673 } else {
1674 try w.writeByte('{');
1675 var index: u64 = 0;
1676 while (index < ai.len) : (index += 1) {
1677 if (index > 0) try w.writeAll(", ");
1678 try dg.renderUndefValue(w, ty.childType(zcu), initializer_type);
1679 }
1680 if (ai.sentinel) |s| {
1681 if (index > 0) try w.writeAll(", ");
1682 try dg.renderValue(w, s, location);
1683 }
1684 try w.writeByte('}');
1685 }
1686 try w.writeByte('}');
1687 },
1688 .anyframe_type,
1689 .opaque_type,
1690 .spirv_type,
1691 .func_type,
1692 => unreachable,
1693
1694 .undef,
1695 .simple_value,
1696 .@"extern",
1697 .func,
1698 .int,
1699 .err,
1700 .error_union,
1701 .enum_literal,
1702 .enum_tag,
1703 .float,
1704 .ptr,
1705 .slice,
1706 .opt,
1707 .aggregate,
1708 .un,
1709 .bitpack,
1710 .memoized_call,
1711 => unreachable, // values, not types
1712 },
1713 }
1714 }
1715
1716 fn renderFunctionSignature(
1717 dg: *DeclGen,
1718 w: *Writer,
1719 fn_val: Value,
1720 fn_align: InternPool.Alignment,
1721 kind: enum { forward_decl, definition },
1722 name: union(enum) {
1723 nav: InternPool.Nav.Index,
1724 nav_never_tail: InternPool.Nav.Index,
1725 nav_never_inline: InternPool.Nav.Index,
1726 @"export": struct {
1727 main_name: InternPool.NullTerminatedString,
1728 extern_name: InternPool.NullTerminatedString,
1729 },
1730 },
1731 ) !void {
1732 const zcu = dg.pt.zcu;
1733 const ip = &zcu.intern_pool;
1734
1735 const fn_ty = fn_val.typeOf(zcu);
1736
1737 const fn_info = zcu.typeToFunc(fn_ty).?;
1738 if (fn_info.cc == .naked) {
1739 switch (kind) {
1740 .forward_decl => try w.writeAll("zig_naked_decl "),
1741 .definition => try w.writeAll("zig_naked "),
1742 }
1743 }
1744
1745 if (fn_val.getFunction(zcu)) |func| {
1746 const func_analysis = func.analysisUnordered(ip);
1747
1748 if (func_analysis.branch_hint == .cold)
1749 try w.writeAll("zig_cold ");
1750
1751 if (kind == .definition and (func_analysis.disable_intrinsics or dg.mod.no_builtin))
1752 try w.writeAll("zig_no_builtin ");
1753 }
1754
1755 // While incomplete types are usually an acceptable substitute for "void", this is not true
1756 // in function return types, where "void" is the only incomplete type permitted.
1757 const actual_return_type: Type = .fromInterned(fn_info.return_type);
1758 const effective_return_type: Type = switch (actual_return_type.classify(zcu)) {
1759 .no_possible_value => .noreturn,
1760 .one_possible_value, .fully_comptime => .void, // no runtime bits
1761 .partially_comptime, .runtime => actual_return_type, // yes runtime bits
1762 };
1763
1764 const ret_cty: CType = try .lower(effective_return_type, &dg.ctype_deps, dg.arena, zcu);
1765 try w.print("{f}", .{ret_cty.fmtDeclaratorPrefix(zcu)});
1766 switch (CType.CallingConvention.fromLang(fn_info.cc, zcu.getTarget())) {
1767 .c => {},
1768 else => |cc| try w.print("zig_callconv({t}) ", .{cc}),
1769 }
1770 switch (name) {
1771 .nav => |nav| try renderNavName(w, nav, ip),
1772 .nav_never_tail => |nav| try w.print("zig_never_tail_{f}__{d}", .{
1773 fmtIdentUnsolo(ip.getNav(nav).name.toSlice(ip)), @backingInt(nav),
1774 }),
1775 .nav_never_inline => |nav| try w.print("zig_never_inline_{f}__{d}", .{
1776 fmtIdentUnsolo(ip.getNav(nav).name.toSlice(ip)), @backingInt(nav),
1777 }),
1778 .@"export" => |@"export"| try w.print("{f}", .{fmtIdentSolo(@"export".extern_name.toSlice(ip))}),
1779 }
1780 {
1781 try w.writeByte('(');
1782 var c_param_index: u32 = 0;
1783 for (fn_info.param_types.get(ip)) |param_ty_ip| {
1784 const param_ty: Type = .fromInterned(param_ty_ip);
1785 if (!param_ty.hasRuntimeBits(zcu)) continue;
1786 if (c_param_index != 0) try w.writeAll(", ");
1787 try dg.renderTypeAndName(w, param_ty, .{ .arg = c_param_index }, .{
1788 .@"const" = kind == .definition,
1789 }, .none);
1790 c_param_index += 1;
1791 }
1792 if (fn_info.is_var_args) {
1793 if (c_param_index != 0) try w.writeAll(", ");
1794 try w.writeAll("...");
1795 } else if (c_param_index == 0) {
1796 try w.writeAll("void");
1797 }
1798 try w.writeByte(')');
1799 }
1800 try w.print("{f}", .{ret_cty.fmtDeclaratorSuffixIgnoreNonstring(zcu)});
1801
1802 switch (kind) {
1803 .forward_decl => {
1804 if (fn_align.toByteUnits()) |a| try w.print(" zig_align_fn({})", .{a});
1805 switch (name) {
1806 .nav, .nav_never_tail, .nav_never_inline => {},
1807 .@"export" => |@"export"| {
1808 const extern_name = @"export".extern_name.toSlice(ip);
1809 const is_mangled = isMangledIdent(extern_name, true);
1810 const is_export = @"export".extern_name != @"export".main_name;
1811 if (is_mangled and is_export) {
1812 try w.print(" zig_mangled_export({f}, {f}, {f})", .{
1813 fmtIdentSolo(extern_name),
1814 fmtStringLiteral(extern_name, null),
1815 fmtStringLiteral(@"export".main_name.toSlice(ip), null),
1816 });
1817 } else if (is_mangled) {
1818 try w.print(" zig_mangled({f}, {f})", .{
1819 fmtIdentSolo(extern_name), fmtStringLiteral(extern_name, null),
1820 });
1821 } else if (is_export) {
1822 try w.print(" zig_export({f}, {f})", .{
1823 fmtStringLiteral(@"export".main_name.toSlice(ip), null),
1824 fmtStringLiteral(extern_name, null),
1825 });
1826 }
1827 },
1828 }
1829 },
1830 .definition => {},
1831 }
1832 }
1833
1834 /// Renders the C lowering of the given Zig type to `w`. This renders the type name---to render
1835 /// a declarator with this type, see instead `renderTypeAndName`.
1836 fn renderType(dg: *DeclGen, w: *Writer, ty: Type) (Writer.Error || Allocator.Error)!void {
1837 const zcu = dg.pt.zcu;
1838 const cty: CType = try .lower(ty, &dg.ctype_deps, dg.arena, zcu);
1839 try w.print("{f}", .{cty.fmtTypeName(zcu)});
1840 }
1841
1842 /// Renders to `w` a C declarator whose type is the C lowering of the given Zig type.
1843 fn renderTypeAndName(
1844 dg: *DeclGen,
1845 w: *Writer,
1846 ty: Type,
1847 name: CValue,
1848 qualifiers: CQualifiers,
1849 alignment: Alignment,
1850 ) !void {
1851 const zcu = dg.pt.zcu;
1852 const ip = &zcu.intern_pool;
1853 const cty: CType = try .lower(ty, &dg.ctype_deps, dg.arena, zcu);
1854 try w.print("{f}", .{cty.fmtDeclaratorPrefix(zcu)});
1855 if (alignment != .none) switch (alignment.order(ty.abiAlignment(zcu))) {
1856 .lt => try w.print("zig_under_align({d}) ", .{alignment.toByteUnits().?}),
1857 .eq => {},
1858 .gt => try w.print("zig_align({d}) ", .{alignment.toByteUnits().?}),
1859 };
1860 if (qualifiers.@"const") try w.writeAll("const ");
1861 if (qualifiers.@"volatile") try w.writeAll("volatile ");
1862 if (qualifiers.restrict) try w.writeAll("restrict ");
1863 switch (name) {
1864 .new_local, .local => |i| try w.print("t{d}", .{i}),
1865 .arg => |i| try w.print("a{d}", .{i}),
1866 .constant => |uav| try renderUavName(w, uav),
1867 .nav => |nav| try renderNavName(w, nav, ip),
1868 .identifier => |ident| try w.print("{f}", .{fmtIdentSolo(ident)}),
1869 else => unreachable,
1870 }
1871 try w.print("{f}", .{cty.fmtDeclaratorSuffix(zcu)});
1872 }
1873
1874 fn writeCValue(dg: *DeclGen, w: *Writer, c_value: CValue) Error!void {
1875 switch (c_value) {
1876 .none, .new_local, .local, .local_ref => unreachable,
1877 .constant => |uav| try renderUavName(w, uav),
1878 .arg => unreachable,
1879 .field => |i| try w.print("f{d}", .{i}),
1880 .nav => |nav| try renderNavName(w, nav, &dg.pt.zcu.intern_pool),
1881 .nav_ref => |nav| {
1882 try w.writeByte('&');
1883 try renderNavName(w, nav, &dg.pt.zcu.intern_pool);
1884 },
1885 .undef => |ty| try dg.renderUndefValue(w, ty, .other),
1886 .identifier => |ident| try w.print("{f}", .{fmtIdentSolo(ident)}),
1887 .payload_identifier => |ident| try w.print("{f}.{f}", .{
1888 fmtIdentSolo("payload"),
1889 fmtIdentSolo(ident),
1890 }),
1891 }
1892 }
1893
1894 fn writeCValueDeref(dg: *DeclGen, w: *Writer, c_value: CValue) !void {
1895 switch (c_value) {
1896 .none,
1897 .new_local,
1898 .local,
1899 .local_ref,
1900 .constant,
1901 .arg,
1902 => unreachable,
1903 .field => |i| try w.print("f{d}", .{i}),
1904 .nav => |nav| {
1905 try w.writeAll("(*");
1906 try renderNavName(w, nav, &dg.pt.zcu.intern_pool);
1907 try w.writeByte(')');
1908 },
1909 .nav_ref => |nav| try renderNavName(w, nav, &dg.pt.zcu.intern_pool),
1910 .undef => unreachable,
1911 .identifier => |ident| try w.print("(*{f})", .{fmtIdentSolo(ident)}),
1912 .payload_identifier => |ident| try w.print("(*{f}.{f})", .{
1913 fmtIdentSolo("payload"),
1914 fmtIdentSolo(ident),
1915 }),
1916 }
1917 }
1918
1919 fn writeCValueMember(
1920 dg: *DeclGen,
1921 w: *Writer,
1922 c_value: CValue,
1923 member: CValue,
1924 ) Error!void {
1925 try dg.writeCValue(w, c_value);
1926 try w.writeByte('.');
1927 try dg.writeCValue(w, member);
1928 }
1929
1930 fn writeCValueDerefMember(
1931 dg: *DeclGen,
1932 w: *Writer,
1933 c_value: CValue,
1934 member: CValue,
1935 ) !void {
1936 switch (c_value) {
1937 .none,
1938 .new_local,
1939 .local,
1940 .local_ref,
1941 .constant,
1942 .field,
1943 .undef,
1944 .arg,
1945 => unreachable,
1946 .nav, .identifier, .payload_identifier => {
1947 try dg.writeCValue(w, c_value);
1948 try w.writeAll("->");
1949 },
1950 .nav_ref => {
1951 try dg.writeCValueDeref(w, c_value);
1952 try w.writeByte('.');
1953 },
1954 }
1955 try dg.writeCValue(w, member);
1956 }
1957
1958 fn renderTypeForBuiltinFnName(dg: *DeclGen, w: *Writer, ty: Type) !void {
1959 const zcu = dg.pt.zcu;
1960 switch (ty.zigTypeTag(zcu)) {
1961 .bool => return w.writeAll("u8"),
1962 .float => return w.print("f{d}", .{ty.floatBits(zcu.getTarget())}),
1963 else => {},
1964 }
1965 if (ty.isPtrAtRuntime(zcu)) {
1966 return w.print("p{d}", .{zcu.getTarget().ptrBitWidth()});
1967 }
1968 switch (CType.classifyInt(ty, zcu)) {
1969 .void => unreachable, // opv
1970 .small => |s| try w.print("{c}{d}", .{
1971 signAbbrev(ty.intInfo(zcu).signedness),
1972 s.bits(zcu.getTarget()),
1973 }),
1974 .big => try w.writeAll("big"),
1975 }
1976 }
1977
1978 fn renderBuiltinInfo(dg: *DeclGen, w: *Writer, ty: Type, info: BuiltinInfo) !void {
1979 const pt = dg.pt;
1980 const zcu = pt.zcu;
1981
1982 const is_big = lowersToBigInt(ty, zcu);
1983 switch (info) {
1984 .none => if (!is_big) return,
1985 .bits => {},
1986 .bits_none, .big_temp_bits => unreachable,
1987 }
1988
1989 const int_info: std.lang.Type.Int = if (ty.isAbiInt(zcu)) ty.intInfo(zcu) else .{
1990 .signedness = .unsigned,
1991 .bits = @intCast(ty.bitSize(zcu)),
1992 };
1993
1994 if (is_big) try w.print(", {}", .{int_info.signedness == .signed});
1995 try w.print(", {f}", .{try dg.fmtIntLiteralDec(
1996 try pt.intValue(if (is_big) .u16 else .u8, int_info.bits),
1997 .other,
1998 )});
1999 }
2000
2001 fn fmtIntLiteral(
2002 dg: *DeclGen,
2003 val: Value,
2004 loc: ValueRenderLocation,
2005 base: u8,
2006 case: std.fmt.Case,
2007 ) !std.fmt.Alt(FormatIntLiteralContext, formatIntLiteral) {
2008 // If there's a bigint type involved, mark a dependency on it.
2009 const cty: CType = try .lower(val.typeOf(dg.pt.zcu), &dg.ctype_deps, dg.arena, dg.pt.zcu);
2010 return .{ .data = .{
2011 .dg = dg,
2012 .loc = loc,
2013 .val = val,
2014 .cty = cty,
2015 .base = base,
2016 .case = case,
2017 } };
2018 }
2019
2020 fn fmtIntLiteralDec(
2021 dg: *DeclGen,
2022 val: Value,
2023 loc: ValueRenderLocation,
2024 ) !std.fmt.Alt(FormatIntLiteralContext, formatIntLiteral) {
2025 return fmtIntLiteral(dg, val, loc, 10, .lower);
2026 }
2027
2028 fn fmtIntLiteralHex(
2029 dg: *DeclGen,
2030 val: Value,
2031 loc: ValueRenderLocation,
2032 ) !std.fmt.Alt(FormatIntLiteralContext, formatIntLiteral) {
2033 return fmtIntLiteral(dg, val, loc, 16, .lower);
2034 }
2035};
2036
2037const CQualifiers = packed struct {
2038 @"const": bool = false,
2039 @"volatile": bool = false,
2040 restrict: bool = false,
2041};
2042
2043pub fn genHeader(zcu: *Zcu, w: *Writer) !void {
2044 const gpa = zcu.comp.gpa;
2045
2046 var arena: std.heap.ArenaAllocator = .init(gpa);
2047 defer arena.deinit();
2048 var ctype_deps: CType.Dependencies = .empty;
2049 defer ctype_deps.deinit(gpa);
2050
2051 const target = zcu.getTarget();
2052 switch (target.abi) {
2053 .msvc, .itanium => try w.writeAll("#define ZIG_TARGET_ABI_MSVC\n"),
2054 else => {},
2055 }
2056 for ([_]u16{ 16, 32, 64, 80, 128 }) |bits| switch (std.zig.target.compilerRtFloatAbi(target, bits)) {
2057 .hard => {},
2058 .soft => try w.print("#define ZIG_TARGET_SOFT_COMPILER_RT_F{d}_ABI\n", .{bits}),
2059 };
2060 try w.print(
2061 \\#define ZIG_TARGET_MAX_INT_ALIGNMENT {d}
2062 \\#include "zig.h"
2063 \\
2064 \\
2065 ,
2066 .{target.cMaxIntAlignment()},
2067 );
2068
2069 var basic_ty: Type = .fromInterned(.first_type);
2070 while (true) : ({
2071 basic_ty = .fromInterned(@fromBackingInt(@intCast(@backingInt(basic_ty.toIntern()) + 1)));
2072 if (basic_ty.toIntern() == InternPool.Index.last_type) break;
2073 }) {
2074 switch (basic_ty.toIntern()) {
2075 else => {},
2076 .anyframe_type,
2077 .adhoc_inferred_error_set_type,
2078 .generic_poison_type,
2079 => continue, // skip unsupported types
2080 }
2081 const basic_cty: CType = try .lower(basic_ty, &ctype_deps, arena.allocator(), zcu);
2082 switch (basic_cty) {
2083 .void => {}, // no layout to check
2084 .bool,
2085 .int,
2086 .float,
2087 => try CType.render_defs.writeStaticAssertTypeLayout(basic_ty, basic_cty, w, zcu),
2088 .@"fn",
2089 .@"enum",
2090 .bitpack,
2091 .@"struct",
2092 .union_auto,
2093 .union_extern,
2094 .slice,
2095 .opt,
2096 .arr,
2097 .vec,
2098 .errunion,
2099 .aligned,
2100 .bigint,
2101 .pointer,
2102 .array,
2103 .function,
2104 => {},
2105 }
2106 }
2107}
2108
2109pub fn genGlobalAsm(zcu: *Zcu, w: *Writer) !void {
2110 for (zcu.global_assembly.values()) |asm_source| {
2111 try w.print("__asm({f});\n", .{fmtStringLiteral(asm_source, null)});
2112 }
2113}
2114
2115pub fn genErrDecls(
2116 zcu: *const Zcu,
2117 w: *Writer,
2118 slice_const_u8_sentinel_0_type_name: []const u8,
2119) Writer.Error!void {
2120 const ip = &zcu.intern_pool;
2121
2122 const names = ip.global_error_set.getNamesFromMainThread();
2123 // Don't generate an invalid empty enum/array if the global error set is empty!
2124 if (names.len == 0) return;
2125
2126 try w.writeAll("enum {\n");
2127 for (names, 1..) |name_nts, value| {
2128 try w.writeByte(' ');
2129 try renderErrorName(w, name_nts.toSlice(ip));
2130 try w.print(" = {d}u,\n", .{value});
2131 }
2132 try w.writeAll("};\n");
2133
2134 for (names) |name_nts| {
2135 const name = name_nts.toSlice(ip);
2136 try w.print(
2137 "static uint8_t const zig_errorName_{f}[] = {f};\n",
2138 .{ fmtIdentUnsolo(name), fmtStringLiteral(name, 0) },
2139 );
2140 }
2141
2142 try w.print(
2143 "static {s} const zig_errorName[{d}] = {{",
2144 .{ slice_const_u8_sentinel_0_type_name, names.len },
2145 );
2146 try w.writeByte('\n');
2147 for (names) |name_nts| {
2148 const name = name_nts.toSlice(ip);
2149 try w.print(
2150 " {{zig_errorName_{f},{d}}},\n",
2151 .{ fmtIdentUnsolo(name), name.len },
2152 );
2153 }
2154 try w.writeAll("};\n");
2155}
2156
2157pub fn genTagNameFn(
2158 zcu: *const Zcu,
2159 w: *Writer,
2160 slice_const_u8_sentinel_0_type_name: []const u8,
2161 enum_ty: Type,
2162 enum_type_name: []const u8,
2163) Writer.Error!void {
2164 const ip = &zcu.intern_pool;
2165 const loaded_enum = ip.loadEnumType(enum_ty.toIntern());
2166 assert(loaded_enum.field_names.len > 0);
2167 switch (CType.classifyInt(enum_ty, zcu)) {
2168 .void => unreachable,
2169 .small => |int| switch (int) {
2170 else => {},
2171 .zig_u128, .zig_i128 => @panic("TODO CBE: tagName for 128-bit enums"),
2172 },
2173 .big => @panic("TODO CBE: tagName for bigint enums"),
2174 }
2175
2176 if (!zcu.comp.config.root_strip) try w.print("/* @tagName({f}) */\n", .{
2177 loaded_enum.name.fmt(ip),
2178 });
2179 try w.print("static {s} zig_tagName_{f}__{d}({s} tag) {{\n", .{
2180 slice_const_u8_sentinel_0_type_name,
2181 fmtIdentUnsolo(loaded_enum.name.toSlice(ip)),
2182 @backingInt(enum_ty.toIntern()),
2183 enum_type_name,
2184 });
2185 for (loaded_enum.field_names.get(ip), 0..) |field_name, field_index| {
2186 try w.print(" static uint8_t const name{d}[] = {f};\n", .{
2187 field_index, fmtStringLiteral(field_name.toSlice(ip), 0),
2188 });
2189 }
2190
2191 try w.writeAll(" switch (tag) {\n");
2192 const field_values = loaded_enum.field_values.get(ip);
2193 for (loaded_enum.field_names.get(ip), 0..) |field_name, field_index| {
2194 const field_int: i65 = int: {
2195 if (field_values.len == 0) break :int field_index;
2196 const field_val: Value = .fromInterned(field_values[field_index]);
2197 break :int field_val.getUnsignedInt(zcu) orelse field_val.toSignedInt(zcu);
2198 };
2199 try w.print(" case {d}: return ({s}){{name{d},{d}}};\n", .{
2200 field_int,
2201 slice_const_u8_sentinel_0_type_name,
2202 field_index,
2203 field_name.toSlice(ip).len,
2204 });
2205 }
2206 try w.writeAll(
2207 \\ }
2208 \\ zig_unreachable();
2209 \\}
2210 \\
2211 );
2212}
2213
2214pub fn genLazyCallModifierFn(
2215 dg: *DeclGen,
2216 fn_nav: InternPool.Nav.Index,
2217 kind: enum { never_tail, never_inline },
2218 w: *Writer,
2219) Error!void {
2220 const zcu = dg.pt.zcu;
2221 const ip = &zcu.intern_pool;
2222
2223 const fn_val = zcu.navValue(fn_nav);
2224
2225 if (fn_val.typeOf(zcu).fnReturnType(zcu).isNoReturn(zcu)) try w.writeAll("zig_noreturn ");
2226 try w.print("static zig_{t} ", .{kind});
2227 try dg.renderFunctionSignature(w, fn_val, .none, .definition, switch (kind) {
2228 .never_tail => .{ .nav_never_tail = fn_nav },
2229 .never_inline => .{ .nav_never_inline = fn_nav },
2230 });
2231 try w.writeAll(" {\n return ");
2232 try renderNavName(w, fn_nav, ip);
2233 try w.writeByte('(');
2234 {
2235 const func_type = ip.indexToKey(fn_val.typeOf(zcu).toIntern()).func_type;
2236 var c_param_index: u32 = 0;
2237 for (func_type.param_types.get(ip)) |param_ty_ip| {
2238 const param_ty: Type = .fromInterned(param_ty_ip);
2239 if (!param_ty.hasRuntimeBits(zcu)) continue;
2240 if (c_param_index != 0) try w.writeAll(", ");
2241 try w.print("a{d}", .{c_param_index});
2242 c_param_index += 1;
2243 }
2244 }
2245 try w.writeAll(");\n}\n");
2246}
2247
2248pub fn generate(
2249 lf: *link.File,
2250 pt: Zcu.PerThread,
2251 func_index: InternPool.Index,
2252 air: *const Air,
2253 liveness: *const ?Air.Liveness,
2254) @import("../codegen.zig").Error!Mir {
2255 const zcu = pt.zcu;
2256 const gpa = zcu.gpa;
2257
2258 assert(lf.tag == .c);
2259
2260 const func = zcu.funcInfo(func_index);
2261
2262 var arena: std.heap.ArenaAllocator = .init(gpa);
2263 defer arena.deinit();
2264
2265 var function: Function = .{
2266 .value_map = .init(gpa),
2267 .air = air.*,
2268 .liveness = liveness.*.?,
2269 .func_index = func_index,
2270 .dg = .{
2271 .gpa = gpa,
2272 .arena = arena.allocator(),
2273 .pt = pt,
2274 .mod = zcu.navFileScope(func.owner_nav).mod.?,
2275 .owner_nav = func.owner_nav.toOptional(),
2276 .is_naked_fn = Type.fromInterned(func.ty).fnCallingConvention(zcu) == .naked,
2277 .expected_block = null,
2278 .ctype_deps = .empty,
2279 .uavs = .empty,
2280 },
2281 .code = .init(gpa),
2282 .indent_counter = 0,
2283 .need_tag_name_funcs = .empty,
2284 .need_never_tail_funcs = .empty,
2285 .need_never_inline_funcs = .empty,
2286 };
2287 defer {
2288 function.code.deinit();
2289 function.dg.ctype_deps.deinit(gpa);
2290 function.dg.uavs.deinit(gpa);
2291 function.deinit();
2292 }
2293
2294 var fwd_decl: Writer.Allocating = .init(gpa);
2295 defer fwd_decl.deinit();
2296
2297 var code_header: Writer.Allocating = .init(gpa);
2298 defer code_header.deinit();
2299
2300 genFunc(&function, &fwd_decl.writer, &code_header.writer) catch |err| switch (err) {
2301 error.WriteFailed => return error.OutOfMemory,
2302 else => |e| return e,
2303 };
2304
2305 var mir: Mir = .{
2306 .fwd_decl = &.{},
2307 .code_header = &.{},
2308 .code = &.{},
2309 .ctype_deps = function.dg.ctype_deps.move(),
2310 .need_uavs = function.dg.uavs.move(),
2311 .need_tag_name_funcs = function.need_tag_name_funcs.move(),
2312 .need_never_tail_funcs = function.need_never_tail_funcs.move(),
2313 .need_never_inline_funcs = function.need_never_inline_funcs.move(),
2314 };
2315 errdefer mir.deinit(gpa);
2316 mir.fwd_decl = try fwd_decl.toOwnedSlice();
2317 mir.code_header = try code_header.toOwnedSlice();
2318 mir.code = try function.code.toOwnedSlice();
2319 return mir;
2320}
2321
2322pub fn genFunc(f: *Function, fwd_decl_writer: *Writer, header_writer: *Writer) Error!void {
2323 const tracy = trace(@src());
2324 defer tracy.end();
2325
2326 const zcu = f.dg.pt.zcu;
2327 const ip = &zcu.intern_pool;
2328 const gpa = f.dg.gpa;
2329 const nav_index = f.dg.owner_nav.unwrap().?;
2330 const nav_val = zcu.navValue(nav_index);
2331 const fn_info = zcu.typeToFunc(nav_val.typeOf(zcu)).?;
2332 const nav = ip.getNav(nav_index);
2333
2334 if (Type.fromInterned(fn_info.return_type).isNoReturn(zcu)) try fwd_decl_writer.writeAll("zig_noreturn ");
2335 try fwd_decl_writer.writeAll("static ");
2336 try f.dg.renderFunctionSignature(
2337 fwd_decl_writer,
2338 nav_val,
2339 nav.resolved.?.@"align",
2340 .forward_decl,
2341 .{ .nav = nav_index },
2342 );
2343 try fwd_decl_writer.writeAll(";\n");
2344
2345 if (nav.resolved.?.@"linksection".toSlice(ip)) |s|
2346 try header_writer.print("zig_linksection_fn({f}) ", .{fmtStringLiteral(s, null)});
2347 try f.dg.renderFunctionSignature(
2348 header_writer,
2349 nav_val,
2350 .none,
2351 .definition,
2352 .{ .nav = nav_index },
2353 );
2354 try header_writer.writeAll(" {\n ");
2355 if (!f.dg.mod.strip) try header_writer.print("/* {f} */\n ", .{nav.fqn.fmt(ip)});
2356
2357 f.free_locals_map.clearRetainingCapacity();
2358
2359 const main_body = f.air.getMainBody();
2360 f.indent();
2361 if (switch (fn_info.cc) {
2362 inline else => |pl| switch (@TypeOf(pl)) {
2363 void,
2364 std.lang.CallingConvention.SpirvKernelOptions,
2365 std.lang.CallingConvention.SpirvFragmentOptions,
2366 std.lang.CallingConvention.SpirvMeshOptions,
2367 => null,
2368 std.lang.CallingConvention.ArcInterruptOptions,
2369 std.lang.CallingConvention.ArmInterruptOptions,
2370 std.lang.CallingConvention.RiscvInterruptOptions,
2371 std.lang.CallingConvention.ShInterruptOptions,
2372 std.lang.CallingConvention.MicroblazeInterruptOptions,
2373 std.lang.CallingConvention.MipsInterruptOptions,
2374 std.lang.CallingConvention.CommonOptions,
2375 std.lang.CallingConvention.X86RegparmOptions,
2376 => pl.incoming_stack_alignment,
2377 else => @compileError(@tagName(pl)),
2378 },
2379 }) |incoming_stack_alignment| realign_stack: {
2380 const normal_stack_align = zcu.getTarget().stackAlignment();
2381 if (incoming_stack_alignment >= normal_stack_align) break :realign_stack;
2382 try header_writer.print("char zig_align({d}) zig_realign_stack;\n ", .{
2383 normal_stack_align << 1,
2384 });
2385 try f.code.writer.writeAll(
2386 \\__asm volatile("" :: [zig_realign_stack] "m" (zig_realign_stack));
2387 );
2388 try f.newline();
2389 }
2390 try genBodyResolveState(f, undefined, &.{}, main_body, true);
2391 try f.outdent();
2392 try f.code.writer.writeByte('}');
2393 try f.newline();
2394 if (f.dg.expected_block) |_|
2395 return f.fail("runtime code not allowed in naked function", .{});
2396
2397 // Take advantage of the free_locals map to bucket locals per type. All
2398 // locals corresponding to AIR instructions should be in there due to
2399 // Liveness analysis, however, locals from alloc instructions will be
2400 // missing. These are added now to complete the map. Then we can sort by
2401 // alignment, descending.
2402 const free_locals = &f.free_locals_map;
2403 assert(f.value_map.count() == 0); // there must not be any unfreed locals
2404 for (f.allocs.keys(), f.allocs.values()) |local_index, should_emit| {
2405 if (!should_emit) continue;
2406 const local = f.locals.items[local_index];
2407 log.debug("inserting local {d} into free_locals", .{local_index});
2408 const gop = try free_locals.getOrPut(gpa, local);
2409 if (!gop.found_existing) gop.value_ptr.* = .{};
2410 try gop.value_ptr.putNoClobber(gpa, local_index, {});
2411 }
2412
2413 const SortContext = struct {
2414 zcu: *const Zcu,
2415 keys: []const LocalType,
2416
2417 pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool {
2418 const lhs = ctx.keys[lhs_index];
2419 const rhs = ctx.keys[rhs_index];
2420 const lhs_align = switch (lhs.alignment) {
2421 .none => lhs.type.abiAlignment(ctx.zcu),
2422 else => |a| a,
2423 };
2424 const rhs_align = switch (rhs.alignment) {
2425 .none => rhs.type.abiAlignment(ctx.zcu),
2426 else => |a| a,
2427 };
2428 return Alignment.compareStrict(lhs_align, .gt, rhs_align);
2429 }
2430 };
2431 free_locals.sort(SortContext{
2432 .zcu = zcu,
2433 .keys = free_locals.keys(),
2434 });
2435
2436 for (free_locals.values()) |list| {
2437 for (list.keys()) |local_index| {
2438 const local = f.locals.items[local_index];
2439 try f.dg.renderTypeAndName(header_writer, local.type, .{ .local = local_index }, .{}, local.alignment);
2440 if (local.array_len != 1) try header_writer.print("[{d}]", .{local.array_len});
2441 try header_writer.writeAll(";\n ");
2442 }
2443 }
2444}
2445
2446pub fn genDecl(dg: *DeclGen, w: *Writer) Error!void {
2447 const tracy = trace(@src());
2448 defer tracy.end();
2449
2450 const pt = dg.pt;
2451 const zcu = pt.zcu;
2452 const ip = &zcu.intern_pool;
2453 const nav = ip.getNav(dg.owner_nav.unwrap().?);
2454 const nav_ty: Type = .fromInterned(nav.resolved.?.type);
2455
2456 if (ip.indexToKey(nav.resolved.?.value) == .@"extern") return;
2457
2458 const init_val: Value = .fromInterned(nav.resolved.?.value);
2459
2460 if (nav.resolved.?.@"linksection".toSlice(ip)) |s| {
2461 try w.print("zig_linksection({f}) ", .{fmtStringLiteral(s, null)});
2462 }
2463
2464 // We don't bother underaligning---it's unnecessary and hurts compatibility.
2465 const a = nav.resolved.?.@"align";
2466 if (a != .none and a.compareStrict(.gt, nav_ty.abiAlignment(zcu))) {
2467 try w.print("zig_align({d}) ", .{a.toByteUnits().?});
2468 }
2469
2470 try genDeclValue(dg, w, .{
2471 .name = .{ .nav = dg.owner_nav.unwrap().? },
2472 .@"const" = nav.resolved.?.@"const",
2473 .@"threadlocal" = nav.resolved.?.@"threadlocal",
2474 .init_val = init_val,
2475 });
2476}
2477pub fn genDeclFwd(dg: *DeclGen, w: *Writer) Error!void {
2478 const tracy = trace(@src());
2479 defer tracy.end();
2480
2481 const pt = dg.pt;
2482 const zcu = pt.zcu;
2483 const ip = &zcu.intern_pool;
2484 const nav = ip.getNav(dg.owner_nav.unwrap().?);
2485 const nav_ty: Type = .fromInterned(nav.resolved.?.type);
2486
2487 const init_val: Value = switch (ip.indexToKey(nav.resolved.?.value)) {
2488 else => .fromInterned(nav.resolved.?.value),
2489
2490 .@"extern" => |@"extern"| switch (nav_ty.zigTypeTag(zcu)) {
2491 .@"fn" => {
2492 const fn_val: Value = .fromInterned(nav.resolved.?.value);
2493 if (fn_val.typeOf(zcu).fnReturnType(zcu).isNoReturn(zcu)) try w.writeAll("zig_noreturn ");
2494 try w.writeAll("zig_extern ");
2495 try dg.renderFunctionSignature(
2496 w,
2497 fn_val,
2498 nav.resolved.?.@"align",
2499 .forward_decl,
2500 .{ .@"export" = .{
2501 .main_name = nav.name,
2502 .extern_name = nav.name,
2503 } },
2504 );
2505 try w.writeAll(";\n");
2506 return;
2507 },
2508 else => {
2509 switch (@"extern".linkage) {
2510 .internal => try w.writeAll("static "),
2511 .strong => try w.print("zig_extern zig_visibility({t}) ", .{@"extern".visibility}),
2512 .weak => try w.print("zig_extern zig_weak_linkage zig_visibility({t}) ", .{@"extern".visibility}),
2513 .link_once => return dg.fail("TODO: CBE: implement linkonce linkage?", .{}),
2514 }
2515 if (nav.resolved.?.@"threadlocal" and !dg.mod.single_threaded) {
2516 try w.writeAll("zig_threadlocal ");
2517 }
2518 try dg.renderTypeAndName(
2519 w,
2520 .fromInterned(nav.resolved.?.type),
2521 .{ .nav = dg.owner_nav.unwrap().? },
2522 .{ .@"const" = nav.resolved.?.@"const" },
2523 nav.resolved.?.@"align",
2524 );
2525 try w.writeAll(";\n");
2526 return;
2527 },
2528 },
2529 };
2530
2531 // We don't bother underaligning---it's unnecessary and hurts compatibility.
2532 const a = nav.resolved.?.@"align";
2533 if (a != .none and a.compareStrict(.gt, nav_ty.abiAlignment(zcu))) {
2534 try w.print("zig_align({d}) ", .{a.toByteUnits().?});
2535 }
2536
2537 try genDeclValueFwd(dg, w, .{
2538 .name = .{ .nav = dg.owner_nav.unwrap().? },
2539 .@"const" = nav.resolved.?.@"const",
2540 .@"threadlocal" = nav.resolved.?.@"threadlocal",
2541 .init_val = init_val,
2542 });
2543}
2544pub fn genDeclValue(dg: *DeclGen, w: *Writer, options: struct {
2545 name: CValue,
2546 @"const": bool,
2547 @"threadlocal": bool,
2548 init_val: Value,
2549}) Error!void {
2550 const zcu = dg.pt.zcu;
2551 const ty = options.init_val.typeOf(zcu);
2552 if (options.@"threadlocal" and !dg.mod.single_threaded) {
2553 try w.writeAll("zig_threadlocal ");
2554 }
2555 try dg.renderTypeAndName(w, ty, options.name, .{ .@"const" = options.@"const" }, .none);
2556 try w.writeAll(" = ");
2557 try dg.renderValue(w, options.init_val, .static_initializer);
2558 try w.writeByte(';');
2559 if (dg.owner_nav.unwrap()) |nav_index| {
2560 const ip = &zcu.intern_pool;
2561 if (!dg.mod.strip) try w.print(" /* {f} */", .{ip.getNav(nav_index).fqn.fmt(ip)});
2562 }
2563 try w.writeByte('\n');
2564}
2565pub fn genDeclValueFwd(dg: *DeclGen, w: *Writer, options: struct {
2566 name: CValue,
2567 @"const": bool,
2568 @"threadlocal": bool,
2569 init_val: Value,
2570}) Error!void {
2571 const zcu = dg.pt.zcu;
2572 const ty = options.init_val.typeOf(zcu);
2573 try w.writeAll("static ");
2574 if (options.@"threadlocal" and !dg.mod.single_threaded) {
2575 try w.writeAll("zig_threadlocal ");
2576 }
2577 try dg.renderTypeAndName(w, ty, options.name, .{ .@"const" = options.@"const" }, .none);
2578 try w.writeAll(";\n");
2579}
2580
2581pub fn genExports(dg: *DeclGen, w: *Writer, exported: Zcu.Exported, export_indices: []const Zcu.Export.Index) !void {
2582 const zcu = dg.pt.zcu;
2583 const ip = &zcu.intern_pool;
2584
2585 const main_name = export_indices[0].ptr(zcu).opts.name;
2586 try w.writeAll("#define ");
2587 switch (exported) {
2588 .nav => |nav| try renderNavName(w, nav, ip),
2589 .uav => |uav| try renderUavName(w, Value.fromInterned(uav)),
2590 }
2591 try w.writeByte(' ');
2592 try w.print("{f}", .{fmtIdentSolo(main_name.toSlice(ip))});
2593 try w.writeByte('\n');
2594
2595 const exported_val = exported.getValue(zcu);
2596 if (ip.isFunctionType(exported_val.typeOf(zcu).toIntern())) return for (export_indices) |export_index| {
2597 const @"export" = export_index.ptr(zcu);
2598 const fn_val = exported.getValue(zcu);
2599 if (fn_val.typeOf(zcu).fnReturnType(zcu).isNoReturn(zcu)) try w.writeAll("zig_noreturn ");
2600 try w.writeAll("zig_extern ");
2601 if (@"export".opts.linkage == .weak) try w.writeAll("zig_weak_linkage_fn ");
2602 try dg.renderFunctionSignature(
2603 w,
2604 fn_val,
2605 exported.getAlign(zcu),
2606 .forward_decl,
2607 .{ .@"export" = .{
2608 .main_name = main_name,
2609 .extern_name = @"export".opts.name,
2610 } },
2611 );
2612 try w.writeAll(";\n");
2613 };
2614 const is_const = switch (exported) {
2615 .nav => |nav| ip.getNav(nav).resolved.?.@"const",
2616 .uav => true,
2617 };
2618 for (export_indices) |export_index| {
2619 const @"export" = export_index.ptr(zcu);
2620 try w.writeAll("zig_extern ");
2621 if (@"export".opts.linkage == .weak) try w.writeAll("zig_weak_linkage ");
2622 if (@"export".opts.section.toSlice(ip)) |s| try w.print("zig_linksection({f}) ", .{
2623 fmtStringLiteral(s, null),
2624 });
2625 const extern_name = @"export".opts.name.toSlice(ip);
2626 const is_mangled = isMangledIdent(extern_name, true);
2627 const is_export = @"export".opts.name != main_name;
2628 try dg.renderTypeAndName(
2629 w,
2630 exported.getValue(zcu).typeOf(zcu),
2631 .{ .identifier = extern_name },
2632 .{ .@"const" = is_const },
2633 exported.getAlign(zcu),
2634 );
2635 if (is_mangled and is_export) {
2636 try w.print(" zig_mangled_export({f}, {f}, {f})", .{
2637 fmtIdentSolo(extern_name),
2638 fmtStringLiteral(extern_name, null),
2639 fmtStringLiteral(main_name.toSlice(ip), null),
2640 });
2641 } else if (is_mangled) {
2642 try w.print(" zig_mangled({f}, {f})", .{
2643 fmtIdentSolo(extern_name), fmtStringLiteral(extern_name, null),
2644 });
2645 } else if (is_export) {
2646 try w.print(" zig_export({f}, {f})", .{
2647 fmtStringLiteral(main_name.toSlice(ip), null),
2648 fmtStringLiteral(extern_name, null),
2649 });
2650 }
2651 try w.writeAll(";\n");
2652 }
2653}
2654
2655/// Generate code for an entire body which ends with a `noreturn` instruction. The states of
2656/// `value_map` and `free_locals_map` are undefined after the generation, and new locals may not
2657/// have been added to `free_locals_map`. For a version of this function that restores this state,
2658/// see `genBodyResolveState`.
2659fn genBody(f: *Function, body: []const Air.Inst.Index) Error!void {
2660 const w = &f.code.writer;
2661 if (body.len == 0) {
2662 try w.writeAll("{}");
2663 } else {
2664 try w.writeByte('{');
2665 f.indent();
2666 try f.newline();
2667 try genBodyInner(f, body);
2668 try f.outdent();
2669 try w.writeByte('}');
2670 }
2671}
2672
2673/// Generate code for an entire body which ends with a `noreturn` instruction. The states of
2674/// `value_map` and `free_locals_map` are restored to their original values, and any non-allocated
2675/// locals introduced within the body are correctly added to `free_locals_map`. Operands in
2676/// `leading_deaths` have their deaths processed before the body is generated.
2677/// A scope is introduced (using braces) only if `inner` is `false`.
2678/// If `leading_deaths` is empty, `inst` may be `undefined`.
2679fn genBodyResolveState(f: *Function, inst: Air.Inst.Index, leading_deaths: []const Air.Inst.Index, body: []const Air.Inst.Index, inner: bool) Error!void {
2680 if (body.len == 0) {
2681 // Don't go to the expense of cloning everything!
2682 if (!inner) try f.code.writer.writeAll("{}");
2683 return;
2684 }
2685
2686 // TODO: we can probably avoid the copies in some other common cases too.
2687
2688 const gpa = f.dg.gpa;
2689
2690 // Save the original value_map and free_locals_map so that we can restore them after the body.
2691 var old_value_map = try f.value_map.clone();
2692 defer old_value_map.deinit();
2693 var old_free_locals = try cloneFreeLocalsMap(gpa, &f.free_locals_map);
2694 defer deinitFreeLocalsMap(gpa, &old_free_locals);
2695
2696 // Remember how many locals there were before entering the body so that we can free any that
2697 // were newly introduced. Any new locals must necessarily be logically free after the then
2698 // branch is complete.
2699 const pre_locals_len: LocalIndex = @intCast(f.locals.items.len);
2700
2701 for (leading_deaths) |death| {
2702 try die(f, inst, death.toRef());
2703 }
2704
2705 if (inner) {
2706 try genBodyInner(f, body);
2707 } else {
2708 try genBody(f, body);
2709 }
2710
2711 f.value_map.deinit();
2712 f.value_map = old_value_map.move();
2713 deinitFreeLocalsMap(gpa, &f.free_locals_map);
2714 f.free_locals_map = old_free_locals.move();
2715
2716 // Now, use the lengths we stored earlier to detect any locals the body generated, and free
2717 // them, unless they were used to store allocs.
2718
2719 for (pre_locals_len..f.locals.items.len) |local_i| {
2720 const local_index: LocalIndex = @intCast(local_i);
2721 if (f.allocs.contains(local_index)) {
2722 continue;
2723 }
2724 try freeLocal(f, inst, local_index, null);
2725 }
2726}
2727
2728fn genBodyInner(f: *Function, body: []const Air.Inst.Index) Error!void {
2729 const zcu = f.dg.pt.zcu;
2730 const ip = &zcu.intern_pool;
2731 const air_tags = f.air.instructions.items(.tag);
2732 const air_datas = f.air.instructions.items(.data);
2733
2734 for (body) |inst| {
2735 if (f.dg.expected_block) |_|
2736 return f.fail("runtime code not allowed in naked function", .{});
2737 if (f.liveness.isUnused(inst) and !f.air.mustLower(inst, ip))
2738 continue;
2739
2740 const result_value = switch (air_tags[@backingInt(inst)]) {
2741 // zig fmt: off
2742 .inferred_alloc, .inferred_alloc_comptime => unreachable,
2743
2744 // Possible because `Air.Legalize.scalarize_bit_cast_vector_non_elementwise` is enabled.
2745 .legalize_vec_elem_val => try airArrayElemVal(f, inst),
2746 .legalize_vec_store_elem => try airLegalizeVecStoreElem(f, inst),
2747 // No soft float legalizations are enabled.
2748 .legalize_compiler_rt_call => unreachable,
2749
2750 .arg => try airArg(f, inst),
2751
2752 .breakpoint => try airBreakpoint(f),
2753 .ret_addr => try airRetAddr(f, inst),
2754 .frame_addr => try airFrameAddress(f, inst),
2755
2756 .ptr_add => try airPtrAddSub(f, inst, '+'),
2757 .ptr_sub => try airPtrAddSub(f, inst, '-'),
2758
2759 // TODO use a different strategy for add, sub, mul, div
2760 // that communicates to the optimizer that wrapping is UB.
2761 .add => try airBinOp(f, inst, "+", "add", .none),
2762 .sub => try airBinOp(f, inst, "-", "sub", .none),
2763 .mul => try airBinOp(f, inst, "*", "mul", .none),
2764
2765 .neg => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "neg", .none),
2766 .div_float => try airBinBuiltinCall(f, inst, "div", .big_temp_bits),
2767
2768 .div_trunc, .div_exact => try airBinOp(f, inst, "/", "divTrunc", .big_temp_bits),
2769 .rem => blk: {
2770 const bin_op = air_datas[@intFromEnum(inst)].bin_op;
2771 const lhs_scalar_ty = f.typeOf(bin_op.lhs).scalarType(zcu);
2772 // For binary operations @TypeOf(lhs)==@TypeOf(rhs),
2773 // so we only check one.
2774 break :blk if (lhs_scalar_ty.isInt(zcu))
2775 try airBinOp(f, inst, "%", "rem", .big_temp_bits)
2776 else
2777 try airBinBuiltinCall(f, inst, "fmod", .none);
2778 },
2779 .div_floor => try airBinBuiltinCall(f, inst, "divFloor", .big_temp_bits),
2780 .div_ceil => try airBinBuiltinCall(f, inst, "divCeil", .big_temp_bits),
2781 .mod => try airBinBuiltinCall(f, inst, "mod", .big_temp_bits),
2782 .abs => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "abs", .none),
2783
2784 .add_wrap => try airBinBuiltinCall(f, inst, "addw", .bits),
2785 .sub_wrap => try airBinBuiltinCall(f, inst, "subw", .bits),
2786 .mul_wrap => try airBinBuiltinCall(f, inst, "mulw", .bits),
2787
2788 .add_sat => try airBinBuiltinCall(f, inst, "adds", .bits),
2789 .sub_sat => try airBinBuiltinCall(f, inst, "subs", .bits),
2790 .mul_sat => try airBinBuiltinCall(f, inst, "muls", .bits),
2791 .shl_sat => try airBinBuiltinCall(f, inst, "shls", .bits_none),
2792
2793 .sqrt => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "sqrt", .none),
2794 .sin => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "sin", .none),
2795 .cos => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "cos", .none),
2796 .tan => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "tan", .none),
2797 .exp => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "exp", .none),
2798 .exp2 => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "exp2", .none),
2799 .log => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "log", .none),
2800 .log2 => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "log2", .none),
2801 .log10 => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "log10", .none),
2802 .floor => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "floor", .none),
2803 .ceil => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "ceil", .none),
2804 .round => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "round", .none),
2805 .trunc_float => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "trunc", .none),
2806
2807 .mul_add => try airMulAdd(f, inst),
2808
2809 .add_with_overflow => try airOverflow(f, inst, "add", .bits),
2810 .sub_with_overflow => try airOverflow(f, inst, "sub", .bits),
2811 .mul_with_overflow => try airOverflow(f, inst, "mul", .bits),
2812 .shl_with_overflow => try airOverflow(f, inst, "shl", .bits),
2813
2814 .min => try airMinMax(f, inst, '<', "min"),
2815 .max => try airMinMax(f, inst, '>', "max"),
2816
2817 .slice => try airSlice(f, inst),
2818
2819 .cmp_gt => try airCmpOp(f, inst, air_datas[@intFromEnum(inst)].bin_op, .gt),
2820 .cmp_gte => try airCmpOp(f, inst, air_datas[@intFromEnum(inst)].bin_op, .gte),
2821 .cmp_lt => try airCmpOp(f, inst, air_datas[@intFromEnum(inst)].bin_op, .lt),
2822 .cmp_lte => try airCmpOp(f, inst, air_datas[@intFromEnum(inst)].bin_op, .lte),
2823
2824 .cmp_eq => try airEquality(f, inst, .eq),
2825 .cmp_neq => try airEquality(f, inst, .neq),
2826
2827 .cmp_vector => blk: {
2828 const ty_pl = air_datas[@intFromEnum(inst)].ty_pl;
2829 const extra = f.air.extraData(Air.VectorCmp, ty_pl.payload).data;
2830 break :blk try airCmpOp(f, inst, extra, extra.compareOperator());
2831 },
2832 .cmp_lte_errors_len => try airCmpLteErrorsLen(f, inst),
2833
2834 .bit_and => try airBinOp(f, inst, "&", "and", .none),
2835 .bit_or => try airBinOp(f, inst, "|", "or", .none),
2836 .xor => try airBinOp(f, inst, "^", "xor", .none),
2837 .shr, .shr_exact => try airBinBuiltinCall(f, inst, "shr", .none),
2838 .shl, => try airBinBuiltinCall(f, inst, "shlw", .bits),
2839 .shl_exact => try airBinOp(f, inst, "<<", "shl", .none),
2840 .not => try airNot (f, inst),
2841
2842 .optional_payload => try airOptionalPayload(f, inst, false),
2843 .optional_payload_ptr => try airOptionalPayload(f, inst, true),
2844 .optional_payload_ptr_set => try airOptionalPayloadPtrSet(f, inst),
2845 .wrap_optional => try airWrapOptional(f, inst),
2846
2847 .is_err => try airIsErr(f, inst, false, "!="),
2848 .is_non_err => try airIsErr(f, inst, false, "=="),
2849 .is_err_ptr => try airIsErr(f, inst, true, "!="),
2850 .is_non_err_ptr => try airIsErr(f, inst, true, "=="),
2851
2852 .is_null => try airIsNull(f, inst, .eq, false),
2853 .is_non_null => try airIsNull(f, inst, .neq, false),
2854 .is_null_ptr => try airIsNull(f, inst, .eq, true),
2855 .is_non_null_ptr => try airIsNull(f, inst, .neq, true),
2856
2857 .alloc => try airAlloc(f, inst),
2858 .ret_ptr => try airRetPtr(f, inst),
2859 .assembly => try airAsm(f, inst),
2860 .ptr_cast => try airPtrCast(f, inst),
2861 .ptr_from_int => try airSimpleCast(f, inst),
2862 .int_from_ptr => try airSimpleCast(f, inst),
2863 .error_cast => try airNopCast(f, inst),
2864 .error_from_int => try airNopCast(f, inst),
2865 .int_from_error => try airNopCast(f, inst),
2866 .union_from_enum => try airUnionFromEnum(f, inst),
2867 .bit_cast => try airBitCast(f, inst),
2868 .int_cast => try airIntCast(f, inst, "intCast", .none),
2869 .trunc => try airIntCast(f, inst, "truncate", .bits),
2870 .load => try airLoad(f, inst),
2871 .store => try airStore(f, inst, false),
2872 .store_safe => try airStore(f, inst, true),
2873 .struct_field_ptr => try airStructFieldPtr(f, inst),
2874 .array_to_slice => try airArrayToSlice(f, inst),
2875 .array_to_vector => unreachable, // legalize .expand_array_to_vector
2876 .cmpxchg_weak => try airCmpxchg(f, inst, "weak"),
2877 .cmpxchg_strong => try airCmpxchg(f, inst, "strong"),
2878 .atomic_rmw => try airAtomicRmw(f, inst),
2879 .atomic_load => try airAtomicLoad(f, inst),
2880 .memset => try airMemset(f, inst, false),
2881 .memset_safe => try airMemset(f, inst, true),
2882 .memcpy => try airMemcpy(f, inst, "memcpy("),
2883 .memmove => try airMemcpy(f, inst, "memmove("),
2884 .set_union_tag => try airSetUnionTag(f, inst),
2885 .get_union_tag => try airGetUnionTag(f, inst),
2886 .clz => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "clz", .bits),
2887 .ctz => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "ctz", .bits),
2888 .popcount => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "popCount", .bits),
2889 .byte_swap => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "byteSwap", .bits),
2890 .bit_reverse => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "bitReverse", .bits),
2891 .tag_name => try airTagName(f, inst),
2892 .error_name => try airErrorName(f, inst),
2893 .splat => try airSplat(f, inst),
2894 .select => try airSelect(f, inst),
2895 .shuffle_one => try airShuffleOne(f, inst),
2896 .shuffle_two => try airShuffleTwo(f, inst),
2897 .reduce => try airReduce(f, inst),
2898 .aggregate_init => try airAggregateInit(f, inst),
2899 .union_init => try airUnionInit(f, inst),
2900 .prefetch => try airPrefetch(f, inst),
2901 .addrspace_cast => return f.fail("TODO: C backend: implement addrspace_cast", .{}),
2902
2903 .@"try" => try airTry(f, inst),
2904 .try_cold => try airTry(f, inst),
2905 .try_ptr => try airTryPtr(f, inst),
2906 .try_ptr_cold => try airTryPtr(f, inst),
2907
2908 .dbg_stmt => try airDbgStmt(f, inst),
2909 .dbg_empty_stmt => try airDbgEmptyStmt(f, inst),
2910 .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => try airDbgVar(f, inst),
2911
2912 .float_from_int,
2913 .int_from_float,
2914 .fptrunc,
2915 .fpext,
2916 => try airFloatCast(f, inst),
2917
2918 .atomic_store_unordered => try airAtomicStore(f, inst, toMemoryOrder(.unordered)),
2919 .atomic_store_monotonic => try airAtomicStore(f, inst, toMemoryOrder(.monotonic)),
2920 .atomic_store_release => try airAtomicStore(f, inst, toMemoryOrder(.release)),
2921 .atomic_store_seq_cst => try airAtomicStore(f, inst, toMemoryOrder(.seq_cst)),
2922
2923 .struct_field_ptr_index_0 => try airStructFieldPtrIndex(f, inst, 0),
2924 .struct_field_ptr_index_1 => try airStructFieldPtrIndex(f, inst, 1),
2925 .struct_field_ptr_index_2 => try airStructFieldPtrIndex(f, inst, 2),
2926 .struct_field_ptr_index_3 => try airStructFieldPtrIndex(f, inst, 3),
2927
2928 .field_parent_ptr => try airFieldParentPtr(f, inst),
2929
2930 .agg_field_val => try airAggFieldVal(f, inst),
2931 .slice_ptr => try airSliceField(f, inst, false, "ptr"),
2932 .slice_len => try airSliceField(f, inst, false, "len"),
2933
2934 .ptr_slice_ptr_ptr => try airSliceField(f, inst, true, "ptr"),
2935 .ptr_slice_len_ptr => try airSliceField(f, inst, true, "len"),
2936
2937 .ptr_elem_val => try airPtrElemVal(f, inst),
2938 .ptr_elem_ptr => try airPtrElemPtr(f, inst),
2939 .slice_elem_val => try airSliceElemVal(f, inst),
2940 .slice_elem_ptr => try airSliceElemPtr(f, inst),
2941 .array_elem_val => try airArrayElemVal(f, inst),
2942
2943 .unwrap_errunion_payload => try airUnwrapErrUnionPay(f, inst, false),
2944 .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(f, inst, true),
2945 .unwrap_errunion_err => try airUnwrapErrUnionErr(f, inst),
2946 .unwrap_errunion_err_ptr => try airUnwrapErrUnionErr(f, inst),
2947 .wrap_errunion_payload => try airWrapErrUnionPay(f, inst),
2948 .wrap_errunion_err => try airWrapErrUnionErr(f, inst),
2949 .errunion_payload_ptr_set => try airErrUnionPayloadPtrSet(f, inst),
2950 .err_return_trace => try airErrReturnTrace(f, inst),
2951 .set_err_return_trace => try airSetErrReturnTrace(f, inst),
2952 .save_err_return_trace_index => try airSaveErrReturnTraceIndex(f, inst),
2953
2954 .wasm_memory_size => try airWasmMemorySize(f, inst),
2955 .wasm_memory_grow => try airWasmMemoryGrow(f, inst),
2956
2957 .add_optimized,
2958 .sub_optimized,
2959 .mul_optimized,
2960 .div_float_optimized,
2961 .div_trunc_optimized,
2962 .div_floor_optimized,
2963 .div_ceil_optimized,
2964 .div_exact_optimized,
2965 .rem_optimized,
2966 .mod_optimized,
2967 .neg_optimized,
2968 .cmp_lt_optimized,
2969 .cmp_lte_optimized,
2970 .cmp_eq_optimized,
2971 .cmp_gte_optimized,
2972 .cmp_gt_optimized,
2973 .cmp_neq_optimized,
2974 .cmp_vector_optimized,
2975 .reduce_optimized,
2976 .int_from_float_optimized,
2977 => return f.fail("TODO implement optimized float mode", .{}),
2978
2979 .add_safe,
2980 .sub_safe,
2981 .mul_safe,
2982 .bit_cast_safe,
2983 .int_cast_safe,
2984 .int_from_float_safe,
2985 .int_from_float_optimized_safe,
2986 => return f.fail("TODO implement safety_checked_instructions", .{}),
2987
2988 .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
2989 .error_set_has_value => return f.fail("TODO: C backend: implement error_set_has_value", .{}),
2990
2991 .runtime_nav_ptr => try airRuntimeNavPtr(f, inst),
2992
2993 .c_va_start => try airCVaStart(f, inst),
2994 .c_va_arg => try airCVaArg(f, inst),
2995 .c_va_end => try airCVaEnd(f, inst),
2996 .c_va_copy => try airCVaCopy(f, inst),
2997
2998 .work_item_id,
2999 .work_group_size,
3000 .work_group_id,
3001 .spirv_runtime_array_len,
3002 => unreachable,
3003
3004 // Instructions that are known to always be `noreturn` based on their tag.
3005 .br => return airBr(f, inst),
3006 .repeat => return airRepeat(f, inst),
3007 .switch_dispatch => return airSwitchDispatch(f, inst),
3008 .cond_br => return airCondBr(f, inst),
3009 .switch_br => return airSwitchBr(f, inst, false),
3010 .loop_switch_br => return airSwitchBr(f, inst, true),
3011 .loop => return airLoop(f, inst),
3012 .ret => return airRet(f, inst, false),
3013 .ret_safe => return airRet(f, inst, false), // TODO
3014 .ret_load => return airRet(f, inst, true),
3015 .trap => return airTrap(f),
3016 .unreach => return airUnreach(f),
3017
3018 // Instructions which may be `noreturn`.
3019 .block => res: {
3020 const res = try airBlock(f, inst);
3021 if (f.typeOfIndex(inst).isNoReturn(zcu)) return;
3022 break :res res;
3023 },
3024 .dbg_inline_block => res: {
3025 const res = try airDbgInlineBlock(f, inst);
3026 if (f.typeOfIndex(inst).isNoReturn(zcu)) return;
3027 break :res res;
3028 },
3029 // TODO: calls should be in this category! The AIR we emit for them is a bit weird.
3030 // The instruction has type `noreturn`, but there are instructions (and maybe a safety
3031 // check) following nonetheless. The `unreachable` or safety check should be emitted by
3032 // backends instead.
3033 .call => try airCall(f, inst, .auto),
3034 .call_always_tail => .none,
3035 .call_never_tail => try airCall(f, inst, .never_tail),
3036 .call_never_inline => try airCall(f, inst, .never_inline),
3037
3038 // zig fmt: on
3039 };
3040 if (result_value == .new_local) {
3041 log.debug("map %{d} to t{d}", .{ inst, result_value.new_local });
3042 }
3043 try f.value_map.putNoClobber(inst.toRef(), switch (result_value) {
3044 .none => continue,
3045 .new_local => |local_index| .{ .local = local_index },
3046 else => result_value,
3047 });
3048 }
3049 unreachable;
3050}
3051
3052fn airSliceField(f: *Function, inst: Air.Inst.Index, is_ptr: bool, field_name: []const u8) !CValue {
3053 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
3054
3055 const inst_ty = f.typeOfIndex(inst);
3056 const operand = try f.resolveInst(ty_op.operand);
3057 try reap(f, inst, &.{ty_op.operand});
3058
3059 const w = &f.code.writer;
3060 const local = try f.allocLocal(inst, inst_ty);
3061 try f.writeCValue(w, local, .other);
3062 try w.writeAll(" = ");
3063 if (is_ptr) {
3064 try w.writeByte('&');
3065 try f.writeCValueDerefMember(w, operand, .{ .identifier = field_name });
3066 } else try f.writeCValueMember(w, operand, .{ .identifier = field_name });
3067 try w.writeByte(';');
3068 try f.newline();
3069 return local;
3070}
3071
3072fn airPtrElemVal(f: *Function, inst: Air.Inst.Index) !CValue {
3073 const zcu = f.dg.pt.zcu;
3074 const inst_ty = f.typeOfIndex(inst);
3075 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
3076 assert(inst_ty.hasRuntimeBits(zcu));
3077
3078 const ptr = try f.resolveInst(bin_op.lhs);
3079 const index = try f.resolveInst(bin_op.rhs);
3080 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3081
3082 const w = &f.code.writer;
3083 const local = try f.allocLocal(inst, inst_ty);
3084 try f.writeCValue(w, local, .other);
3085 try w.writeAll(" = ");
3086 switch (f.typeOf(bin_op.lhs).ptrSize(zcu)) {
3087 .one => try f.writeCValueDerefMember(w, ptr, .{ .identifier = "array" }),
3088 .many, .c => try f.writeCValue(w, ptr, .other),
3089 .slice => unreachable,
3090 }
3091 try w.writeByte('[');
3092 try f.writeCValue(w, index, .other);
3093 try w.writeAll("];");
3094 try f.newline();
3095 return local;
3096}
3097
3098fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue {
3099 const pt = f.dg.pt;
3100 const zcu = pt.zcu;
3101 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
3102 const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;
3103
3104 const inst_ty = f.typeOfIndex(inst);
3105 const ptr_ty = f.typeOf(bin_op.lhs);
3106 assert(ptr_ty.indexableElem(zcu).hasRuntimeBits(zcu));
3107
3108 const ptr = try f.resolveInst(bin_op.lhs);
3109 const index = try f.resolveInst(bin_op.rhs);
3110 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3111
3112 const w = &f.code.writer;
3113 const local = try f.allocLocal(inst, inst_ty);
3114 try f.writeCValue(w, local, .other);
3115 try w.writeAll(" = ");
3116 try w.writeByte('&');
3117 if (ptr_ty.ptrSize(zcu) == .one) {
3118 // `*[n]T` was turned into a pointer to `struct { T array[n]; }`
3119 try f.writeCValueDerefMember(w, ptr, .{ .identifier = "array" });
3120 } else {
3121 try f.writeCValue(w, ptr, .other);
3122 }
3123 try w.writeByte('[');
3124 try f.writeCValue(w, index, .other);
3125 try w.writeAll("];");
3126 try f.newline();
3127 return local;
3128}
3129
3130fn airSliceElemVal(f: *Function, inst: Air.Inst.Index) !CValue {
3131 const zcu = f.dg.pt.zcu;
3132 const inst_ty = f.typeOfIndex(inst);
3133 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
3134 assert(inst_ty.hasRuntimeBits(zcu));
3135
3136 const slice = try f.resolveInst(bin_op.lhs);
3137 const index = try f.resolveInst(bin_op.rhs);
3138 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3139
3140 const w = &f.code.writer;
3141 const local = try f.allocLocal(inst, inst_ty);
3142 try f.writeCValue(w, local, .other);
3143 try w.writeAll(" = ");
3144 try f.writeCValueMember(w, slice, .{ .identifier = "ptr" });
3145 try w.writeByte('[');
3146 try f.writeCValue(w, index, .other);
3147 try w.writeAll("];");
3148 try f.newline();
3149 return local;
3150}
3151
3152fn airSliceElemPtr(f: *Function, inst: Air.Inst.Index) !CValue {
3153 const pt = f.dg.pt;
3154 const zcu = pt.zcu;
3155 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
3156 const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;
3157
3158 const inst_ty = f.typeOfIndex(inst);
3159 const slice_ty = f.typeOf(bin_op.lhs);
3160 const elem_ty = slice_ty.childType(zcu);
3161 assert(elem_ty.hasRuntimeBits(zcu));
3162
3163 const slice = try f.resolveInst(bin_op.lhs);
3164 const index = try f.resolveInst(bin_op.rhs);
3165 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3166
3167 const w = &f.code.writer;
3168 const local = try f.allocLocal(inst, inst_ty);
3169 try f.writeCValue(w, local, .other);
3170 try w.writeAll(" = ");
3171 try w.writeByte('&');
3172 try f.writeCValueMember(w, slice, .{ .identifier = "ptr" });
3173 try w.writeByte('[');
3174 try f.writeCValue(w, index, .other);
3175 try w.writeAll("];");
3176 try f.newline();
3177 return local;
3178}
3179
3180fn airArrayElemVal(f: *Function, inst: Air.Inst.Index) !CValue {
3181 const zcu = f.dg.pt.zcu;
3182 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
3183 const inst_ty = f.typeOfIndex(inst);
3184 assert(inst_ty.hasRuntimeBits(zcu));
3185
3186 const array = try f.resolveInst(bin_op.lhs);
3187 const index = try f.resolveInst(bin_op.rhs);
3188 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3189
3190 const w = &f.code.writer;
3191 const local = try f.allocLocal(inst, inst_ty);
3192 try f.writeCValue(w, local, .other);
3193 try w.writeAll(" = ");
3194 try f.writeCValueMember(w, array, .{ .identifier = "array" });
3195 try w.writeByte('[');
3196 try f.writeCValue(w, index, .other);
3197 try w.writeAll("];");
3198 try f.newline();
3199 return local;
3200}
3201
3202fn airLegalizeVecStoreElem(f: *Function, inst: Air.Inst.Index) !CValue {
3203 const pl_op = f.air.instructions.items(.data)[@backingInt(inst)].pl_op;
3204 const extra = f.air.extraData(Air.Bin, pl_op.payload).data;
3205
3206 const vec_ptr = try f.resolveInst(pl_op.operand);
3207 const index = try f.resolveInst(extra.lhs);
3208 const elem = try f.resolveInst(extra.rhs);
3209 try reap(f, inst, &.{ pl_op.operand, extra.lhs, extra.rhs });
3210
3211 const w = &f.code.writer;
3212
3213 try f.writeCValueDerefMember(w, vec_ptr, .{ .identifier = "array" });
3214 try w.writeByte('[');
3215 try f.writeCValue(w, index, .other);
3216 try w.writeAll("] = ");
3217 try f.writeCValue(w, elem, .other);
3218 try w.writeByte(';');
3219 try f.newline();
3220
3221 return .none;
3222}
3223
3224fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue {
3225 const pt = f.dg.pt;
3226 const zcu = pt.zcu;
3227 const inst_ty = f.typeOfIndex(inst);
3228 const elem_ty = inst_ty.childType(zcu);
3229 if (!elem_ty.hasRuntimeBits(zcu)) {
3230 const w = &f.code.writer;
3231 const local = try f.allocLocal(inst, inst_ty);
3232 try f.writeCValue(w, local, .other);
3233 try w.writeAll(" = ");
3234 try f.dg.renderOpvPointer(w, inst_ty, .other);
3235 try w.writeByte(';');
3236 try f.newline();
3237 return local;
3238 }
3239
3240 const local = try f.allocLocalValue(.{
3241 .type = elem_ty,
3242 .alignment = inst_ty.ptrInfo(zcu).flags.alignment,
3243 });
3244 log.debug("%{d}: allocated unfreeable t{d}", .{ inst, local.new_local });
3245 try f.allocs.put(zcu.gpa, local.new_local, true);
3246
3247 switch (elem_ty.zigTypeTag(zcu)) {
3248 .@"struct", .@"union" => switch (elem_ty.containerLayout(zcu)) {
3249 .@"packed" => {
3250 // For packed aggregates, we zero-initialize to try and work around a design flaw
3251 // related to how `packed`, `undefined`, and RLS interact. See comment in `airStore`
3252 // for details.
3253 const w = &f.code.writer;
3254 try w.print("memset(&t{d}, 0x00, sizeof(", .{local.new_local});
3255 try f.renderType(w, elem_ty);
3256 try w.writeAll("));");
3257 try f.newline();
3258 },
3259 .auto, .@"extern" => {},
3260 },
3261 else => {},
3262 }
3263
3264 return .{ .local_ref = local.new_local };
3265}
3266
3267fn airRetPtr(f: *Function, inst: Air.Inst.Index) !CValue {
3268 const pt = f.dg.pt;
3269 const zcu = pt.zcu;
3270 const inst_ty = f.typeOfIndex(inst);
3271 const elem_ty = inst_ty.childType(zcu);
3272 if (!elem_ty.hasRuntimeBits(zcu)) return .{ .undef = inst_ty };
3273
3274 const local = try f.allocLocalValue(.{
3275 .type = elem_ty,
3276 .alignment = inst_ty.ptrInfo(zcu).flags.alignment,
3277 });
3278 log.debug("%{d}: allocated unfreeable t{d}", .{ inst, local.new_local });
3279 try f.allocs.put(zcu.gpa, local.new_local, true);
3280
3281 switch (elem_ty.zigTypeTag(zcu)) {
3282 .@"struct", .@"union" => switch (elem_ty.containerLayout(zcu)) {
3283 .@"packed" => {
3284 // For packed aggregates, we zero-initialize to try and work around a design flaw
3285 // related to how `packed`, `undefined`, and RLS interact. See comment in `airStore`
3286 // for details.
3287 const w = &f.code.writer;
3288 try w.print("memset(&t{d}, 0x00, sizeof(", .{local.new_local});
3289 try f.renderType(w, elem_ty);
3290 try w.writeAll("));");
3291 try f.newline();
3292 },
3293 .auto, .@"extern" => {},
3294 },
3295 else => {},
3296 }
3297
3298 return .{ .local_ref = local.new_local };
3299}
3300
3301fn airArg(f: *Function, inst: Air.Inst.Index) !CValue {
3302 const i = f.next_arg_index;
3303 f.next_arg_index += 1;
3304 const result: CValue = .{ .arg = i };
3305
3306 if (f.liveness.isUnused(inst)) {
3307 const w = &f.code.writer;
3308 try w.writeByte('(');
3309 try f.renderType(w, .void);
3310 try w.writeByte(')');
3311 try f.writeCValue(w, result, .other);
3312 try w.writeByte(';');
3313 try f.newline();
3314 return .none;
3315 }
3316
3317 return result;
3318}
3319
3320fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue {
3321 const pt = f.dg.pt;
3322 const zcu = pt.zcu;
3323 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
3324
3325 const ptr_ty = f.typeOf(ty_op.operand);
3326 const ptr_scalar_ty = ptr_ty.scalarType(zcu);
3327 const ptr_info = ptr_scalar_ty.ptrInfo(zcu);
3328 const src_ty: Type = .fromInterned(ptr_info.child);
3329
3330 // `Air.Legalize.Feature.expand_packed_load` should ensure that the only
3331 // bit-pointers we see here are vector element pointers.
3332 assert(ptr_info.packed_offset.host_size == 0 or ptr_info.flags.vector_index != .none);
3333
3334 assert(src_ty.hasRuntimeBits(zcu));
3335
3336 const operand = try f.resolveInst(ty_op.operand);
3337
3338 try reap(f, inst, &.{ty_op.operand});
3339
3340 const is_aligned = switch (ptr_info.flags.alignment) {
3341 .none => true,
3342 else => |ptr_align| ptr_align.compare(.gte, src_ty.abiAlignment(zcu)),
3343 };
3344
3345 const w = &f.code.writer;
3346 const local = try f.allocLocal(inst, src_ty);
3347
3348 if (!is_aligned) {
3349 try w.writeAll("memcpy(&");
3350 try f.writeCValue(w, local, .other);
3351 try w.writeAll(", (const char *)");
3352 switch (ptr_info.flags.vector_index) {
3353 .none => try f.writeCValue(w, operand, .other),
3354 else => |index| {
3355 try w.writeByte('&');
3356 try f.writeCValue(w, operand, .other);
3357 try w.print("[{d}]", .{@backingInt(index)});
3358 },
3359 }
3360 try w.writeAll(", sizeof(");
3361 try f.renderType(w, src_ty);
3362 try w.writeAll("))");
3363 } else {
3364 try f.writeCValue(w, local, .other);
3365 try w.writeAll(" = ");
3366 switch (ptr_info.flags.vector_index) {
3367 .none => try f.writeCValueDeref(w, operand),
3368 else => |index| {
3369 try f.writeCValue(w, operand, .other);
3370 try w.print("[{d}]", .{@backingInt(index)});
3371 },
3372 }
3373 }
3374 try w.writeByte(';');
3375 try f.newline();
3376
3377 return local;
3378}
3379
3380fn airRet(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !void {
3381 const pt = f.dg.pt;
3382 const zcu = pt.zcu;
3383 const un_op = f.air.instructions.items(.data)[@backingInt(inst)].un_op;
3384 const w = &f.code.writer;
3385 const op_inst = un_op.toIndex();
3386 const op_ty = f.typeOf(un_op);
3387 const ret_ty = if (is_ptr) op_ty.childType(zcu) else op_ty;
3388
3389 if (op_inst != null and f.air.instructions.items(.tag)[@backingInt(op_inst.?)] == .call_always_tail) {
3390 try reap(f, inst, &.{un_op});
3391 _ = try airCall(f, op_inst.?, .always_tail);
3392 } else if (ret_ty.hasRuntimeBits(zcu)) {
3393 const operand = try f.resolveInst(un_op);
3394 try reap(f, inst, &.{un_op});
3395
3396 try w.writeAll("return ");
3397 if (is_ptr) {
3398 try f.writeCValueDeref(w, operand);
3399 } else switch (operand) {
3400 // Instead of 'return &local', emit 'return undefined'.
3401 .local_ref => try f.dg.renderUndefValue(w, ret_ty, .other),
3402 else => try f.writeCValue(w, operand, .other),
3403 }
3404 try w.writeAll(";\n");
3405 } else {
3406 try reap(f, inst, &.{un_op});
3407 // Not even allowed to return void in a naked function.
3408 if (!f.dg.is_naked_fn) try w.writeAll("return;\n");
3409 }
3410}
3411
3412fn airIntCast(f: *Function, inst: Air.Inst.Index, operation: []const u8, info: BuiltinInfo) !CValue {
3413 const pt = f.dg.pt;
3414 const zcu = pt.zcu;
3415 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
3416
3417 const inst_ty = ty_op.ty;
3418 const inst_scalar_ty = inst_ty.scalarType(zcu);
3419 const operand_ty = f.typeOf(ty_op.operand);
3420 const operand_scalar_ty = operand_ty.scalarType(zcu);
3421 const is_big = lowersToBigInt(operand_ty, zcu);
3422
3423 const operand = try f.resolveInst(ty_op.operand);
3424 if (!is_big) try reap(f, inst, &.{ty_op.operand});
3425
3426 const ref_ret = lowersToBigInt(inst_scalar_ty, zcu);
3427 const ref_arg = lowersToBigInt(operand_scalar_ty, zcu);
3428
3429 const w = &f.code.writer;
3430 const local = try f.allocLocal(inst, inst_ty);
3431 if (is_big) try reap(f, inst, &.{ty_op.operand});
3432 const v = try Vectorize.start(f, inst, w, operand_ty);
3433 if (!ref_ret) {
3434 try f.writeCValue(w, local, .other);
3435 try v.elem(f, w);
3436 try w.writeAll(" = ");
3437 }
3438 try w.writeAll("zig_");
3439 try f.dg.renderTypeForBuiltinFnName(w, inst_scalar_ty);
3440 try w.print("_{s}_", .{operation});
3441 try f.dg.renderTypeForBuiltinFnName(w, operand_scalar_ty);
3442 try w.writeByte('(');
3443 if (ref_ret) {
3444 try w.writeByte('&');
3445 try f.writeCValue(w, local, .other);
3446 try v.elem(f, w);
3447 try w.writeAll(", ");
3448 }
3449 if (ref_arg) {
3450 try w.writeByte('&');
3451 switch (operand) {
3452 .constant => |val| try f.dg.renderValueAsLvalue(w, val),
3453 else => try f.writeCValue(w, operand, .other),
3454 }
3455 } else try f.writeCValue(w, operand, .other);
3456 try v.elem(f, w);
3457 try f.dg.renderBuiltinInfo(w, inst_scalar_ty, info);
3458 try f.dg.renderBuiltinInfo(w, operand_scalar_ty, .none);
3459 try w.writeAll(");");
3460 try f.newline();
3461 try v.end(f, inst, w);
3462
3463 return local;
3464}
3465
3466fn airStore(f: *Function, inst: Air.Inst.Index, safety: bool) !CValue {
3467 const pt = f.dg.pt;
3468 const zcu = pt.zcu;
3469 // *a = b;
3470 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
3471
3472 const ptr_ty = f.typeOf(bin_op.lhs);
3473 const ptr_scalar_ty = ptr_ty.scalarType(zcu);
3474 const ptr_info = ptr_scalar_ty.ptrInfo(zcu);
3475
3476 // `Air.Legalize.Feature.expand_packed_store` should ensure that the only
3477 // bit-pointers we see here are vector element pointers.
3478 assert(ptr_info.packed_offset.host_size == 0 or ptr_info.flags.vector_index != .none);
3479
3480 const ptr_val = try f.resolveInst(bin_op.lhs);
3481 const src_ty = f.typeOf(bin_op.rhs);
3482
3483 const val_is_undef = if (bin_op.rhs.toInterned()) |ip_index| Value.fromInterned(ip_index).isUndef(zcu) else false;
3484
3485 const w = &f.code.writer;
3486 if (val_is_undef) {
3487 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3488 if (safety and ptr_info.packed_offset.host_size == 0) {
3489 // If the thing we're initializing is a packed struct/union, we set to 0 instead of
3490 // 0xAA. This is a hack to work around a problem with partially-undefined packed
3491 // aggregates. If we used 0xAA here, then a later initialization through RLS would
3492 // not zero the high padding bits (for a packed type which is not 8/16/32/64/etc bits),
3493 // so we would get a miscompilation. Using 0x00 here avoids this bug in some cases. It
3494 // is *not* a correct fix; for instance it misses any case where packed structs are
3495 // nested in other aggregates. A proper fix for this will involve changing the language,
3496 // such as to remove RLS. This just prevents miscompilations in *some* common cases.
3497 const byte_str: []const u8 = switch (src_ty.zigTypeTag(zcu)) {
3498 else => "0xaa",
3499 .@"struct", .@"union" => switch (src_ty.containerLayout(zcu)) {
3500 .auto, .@"extern" => "0xaa",
3501 .@"packed" => "0x00",
3502 },
3503 };
3504 try w.writeAll("memset(");
3505 try f.writeCValue(w, ptr_val, .other);
3506 try w.print(", {s}, sizeof(", .{byte_str});
3507 try f.renderType(w, .fromInterned(ptr_info.child));
3508 try w.writeAll("));");
3509 try f.newline();
3510 }
3511 return .none;
3512 }
3513
3514 const is_aligned = if (ptr_info.flags.alignment != .none)
3515 ptr_info.flags.alignment.order(src_ty.abiAlignment(zcu)).compare(.gte)
3516 else
3517 true;
3518
3519 const src_val = try f.resolveInst(bin_op.rhs);
3520 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3521
3522 if (!is_aligned) {
3523 // For this memcpy to safely work we need the rhs to have the same
3524 // underlying type as the lhs (i.e. they must both be arrays of the same underlying type).
3525 assert(src_ty.eql(.fromInterned(ptr_info.child)));
3526
3527 try w.writeAll("memcpy((char *)");
3528 switch (ptr_info.flags.vector_index) {
3529 .none => try f.writeCValue(w, ptr_val, .other),
3530 else => |index| {
3531 try w.writeByte('&');
3532 try f.writeCValue(w, ptr_val, .other);
3533 try w.print("[{d}]", .{@backingInt(index)});
3534 },
3535 }
3536 try w.writeAll(", &");
3537 switch (src_val) {
3538 .constant => |val| try f.dg.renderValueAsLvalue(w, val),
3539 else => try f.writeCValue(w, src_val, .other),
3540 }
3541 try w.writeAll(", sizeof(");
3542 try f.renderType(w, src_ty);
3543 try w.writeAll("));");
3544 try f.newline();
3545 } else {
3546 switch (ptr_val) {
3547 .local_ref => |ptr_local_index| switch (src_val) {
3548 .new_local, .local => |src_local_index| if (ptr_local_index == src_local_index)
3549 return .none,
3550 else => {},
3551 },
3552 else => {},
3553 }
3554
3555 switch (ptr_info.flags.vector_index) {
3556 .none => try f.writeCValueDeref(w, ptr_val),
3557 else => |index| {
3558 try f.writeCValue(w, ptr_val, .other);
3559 try w.print("[{d}]", .{@backingInt(index)});
3560 },
3561 }
3562 try w.writeAll(" = ");
3563 try f.writeCValue(w, src_val, .other);
3564 try w.writeByte(';');
3565 try f.newline();
3566 }
3567 return .none;
3568}
3569
3570fn airOverflow(f: *Function, inst: Air.Inst.Index, operation: []const u8, info: BuiltinInfo) !CValue {
3571 const pt = f.dg.pt;
3572 const zcu = pt.zcu;
3573 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
3574 const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;
3575
3576 const lhs_ty = f.typeOf(bin_op.lhs);
3577 const rhs_ty = f.typeOf(bin_op.rhs);
3578 const is_big = lowersToBigInt(lhs_ty, zcu);
3579
3580 const lhs = try f.resolveInst(bin_op.lhs);
3581 const rhs = try f.resolveInst(bin_op.rhs);
3582 if (!is_big) try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3583
3584 const lhs_scalar_ty = lhs_ty.scalarType(zcu);
3585 const rhs_scalar_ty = rhs_ty.scalarType(zcu);
3586
3587 const ref_lhs = lowersToBigInt(lhs_scalar_ty, zcu);
3588 const ref_rhs = lowersToBigInt(rhs_scalar_ty, zcu);
3589
3590 const w = &f.code.writer;
3591 const local = try f.allocLocal(inst, f.typeOfIndex(inst));
3592 if (is_big) try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3593 const v = try Vectorize.start(f, inst, w, lhs_ty);
3594 try f.writeCValueMember(w, local, .{ .field = 1 });
3595 try v.elem(f, w);
3596 try w.writeAll(" = ");
3597 try w.writeAll("zig_");
3598 try w.writeAll(operation);
3599 try w.writeAll("o_");
3600 try f.dg.renderTypeForBuiltinFnName(w, lhs_scalar_ty);
3601 try w.writeByte('(');
3602
3603 // '&dest', possibly preceded by a cast
3604 switch (zcu.intern_pool.indexToKey(lhs_scalar_ty.toIntern())) {
3605 .int_type => {}, // we already have a '[u]intX_t *'
3606 .simple_type => {
3607 // '&dest' will be something like a 'uintptr_t *', which might be a different C type to
3608 // the equivalent sized integer (e.g. 'uint64_t *'), so we need a cast. We don't need a
3609 // cast on the *operands* because they are passed by value (except for big integers,
3610 // where this issue doesn't exist because no "simple" int type needs bigint repr).
3611 const inst_int_info = lhs_scalar_ty.intInfo(zcu);
3612 try w.print("({s}int{d}_t *)", .{ switch (inst_int_info.signedness) {
3613 .signed => "",
3614 .unsigned => "u",
3615 }, inst_int_info.bits });
3616 },
3617 else => unreachable,
3618 }
3619 try w.writeByte('&');
3620 try f.writeCValueMember(w, local, .{ .field = 0 });
3621 try v.elem(f, w);
3622
3623 try w.writeAll(", ");
3624 if (ref_lhs) {
3625 try w.writeByte('&');
3626 switch (lhs) {
3627 .constant => |lhs_val| try f.dg.renderValueAsLvalue(w, lhs_val),
3628 else => try f.writeCValue(w, lhs, .other),
3629 }
3630 } else try f.writeCValue(w, lhs, .other);
3631 try v.elem(f, w);
3632 try w.writeAll(", ");
3633 if (ref_rhs) {
3634 try w.writeByte('&');
3635 switch (rhs) {
3636 .constant => |rhs_val| try f.dg.renderValueAsLvalue(w, rhs_val),
3637 else => try f.writeCValue(w, rhs, .other),
3638 }
3639 } else try f.writeCValue(w, rhs, .other);
3640 try v.elem(f, w);
3641 try f.dg.renderBuiltinInfo(w, lhs_scalar_ty, info);
3642 try w.writeAll(");");
3643 try f.newline();
3644 try v.end(f, inst, w);
3645
3646 return local;
3647}
3648
3649fn airNot(f: *Function, inst: Air.Inst.Index) !CValue {
3650 const pt = f.dg.pt;
3651 const zcu = pt.zcu;
3652 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
3653 const operand_ty = f.typeOf(ty_op.operand);
3654 const scalar_ty = operand_ty.scalarType(zcu);
3655 if (scalar_ty.toIntern() != .bool_type) return try airUnBuiltinCall(f, inst, ty_op.operand, "not", .bits);
3656
3657 const op = try f.resolveInst(ty_op.operand);
3658 try reap(f, inst, &.{ty_op.operand});
3659
3660 const inst_ty = f.typeOfIndex(inst);
3661
3662 const w = &f.code.writer;
3663 const local = try f.allocLocal(inst, inst_ty);
3664 const v = try Vectorize.start(f, inst, w, operand_ty);
3665 try f.writeCValue(w, local, .other);
3666 try v.elem(f, w);
3667 try w.writeAll(" = ");
3668 try w.writeByte('!');
3669 try f.writeCValue(w, op, .other);
3670 try v.elem(f, w);
3671 try w.writeByte(';');
3672 try f.newline();
3673 try v.end(f, inst, w);
3674
3675 return local;
3676}
3677
3678fn airBinOp(
3679 f: *Function,
3680 inst: Air.Inst.Index,
3681 operator: []const u8,
3682 operation: []const u8,
3683 info: BuiltinInfo,
3684) !CValue {
3685 const pt = f.dg.pt;
3686 const zcu = pt.zcu;
3687 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
3688 const operand_ty = f.typeOf(bin_op.lhs);
3689 const scalar_ty = operand_ty.scalarType(zcu);
3690
3691 builtin: {
3692 if (scalar_ty.isInt(zcu)) switch (CType.classifyInt(scalar_ty, zcu)) {
3693 .void => unreachable,
3694 .small => |int| switch (int) {
3695 else => break :builtin,
3696 .zig_u128, .zig_i128 => {},
3697 },
3698 .big => {},
3699 } else if (!scalar_ty.isRuntimeFloat()) break :builtin;
3700 return airBinBuiltinCall(f, inst, operation, info);
3701 }
3702
3703 const lhs = try f.resolveInst(bin_op.lhs);
3704 const rhs = try f.resolveInst(bin_op.rhs);
3705 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3706
3707 const inst_ty = f.typeOfIndex(inst);
3708
3709 const w = &f.code.writer;
3710 const local = try f.allocLocal(inst, inst_ty);
3711 const v = try Vectorize.start(f, inst, w, operand_ty);
3712 try f.writeCValue(w, local, .other);
3713 try v.elem(f, w);
3714 try w.writeAll(" = ");
3715 try f.writeCValue(w, lhs, .other);
3716 try v.elem(f, w);
3717 try w.writeByte(' ');
3718 try w.writeAll(operator);
3719 try w.writeByte(' ');
3720 try f.writeCValue(w, rhs, .other);
3721 try v.elem(f, w);
3722 try w.writeByte(';');
3723 try f.newline();
3724 try v.end(f, inst, w);
3725
3726 return local;
3727}
3728
3729fn airCmpOp(
3730 f: *Function,
3731 inst: Air.Inst.Index,
3732 data: anytype,
3733 operator: std.math.CompareOperator,
3734) !CValue {
3735 const pt = f.dg.pt;
3736 const zcu = pt.zcu;
3737 const lhs_ty = f.typeOf(data.lhs);
3738 const scalar_ty = lhs_ty.scalarType(zcu);
3739
3740 builtin: {
3741 if (scalar_ty.isInt(zcu)) {
3742 switch (CType.classifyInt(scalar_ty, zcu)) {
3743 .void => unreachable,
3744 .small => |int| switch (int) {
3745 else => break :builtin,
3746 .zig_u128, .zig_i128 => {},
3747 },
3748 .big => {},
3749 }
3750 return airCmpBuiltinCall(f, inst, data, operator, .cmp, .none);
3751 }
3752 if (scalar_ty.isRuntimeFloat())
3753 return airCmpBuiltinCall(f, inst, data, operator, .operator, .none);
3754 }
3755
3756 const inst_ty = f.typeOfIndex(inst);
3757 const lhs = try f.resolveInst(data.lhs);
3758 const rhs = try f.resolveInst(data.rhs);
3759 try reap(f, inst, &.{ data.lhs, data.rhs });
3760
3761 const rhs_ty = f.typeOf(data.rhs);
3762 const need_cast = lhs_ty.isSinglePointer(zcu) or rhs_ty.isSinglePointer(zcu);
3763 const w = &f.code.writer;
3764 const local = try f.allocLocal(inst, inst_ty);
3765 const v = try Vectorize.start(f, inst, w, lhs_ty);
3766 try f.writeCValue(w, local, .other);
3767 try v.elem(f, w);
3768 try w.writeAll(" = ");
3769 if (lhs != .undef and lhs.eql(rhs)) try w.writeAll(switch (operator) {
3770 .lt, .neq, .gt => "false",
3771 .lte, .eq, .gte => "true",
3772 }) else {
3773 if (need_cast) try w.writeAll("(void*)");
3774 try f.writeCValue(w, lhs, .other);
3775 try v.elem(f, w);
3776 try w.writeAll(compareOperatorC(operator));
3777 if (need_cast) try w.writeAll("(void*)");
3778 try f.writeCValue(w, rhs, .other);
3779 try v.elem(f, w);
3780 }
3781 try w.writeByte(';');
3782 try f.newline();
3783 try v.end(f, inst, w);
3784
3785 return local;
3786}
3787
3788fn airEquality(
3789 f: *Function,
3790 inst: Air.Inst.Index,
3791 operator: std.math.CompareOperator,
3792) !CValue {
3793 const pt = f.dg.pt;
3794 const zcu = pt.zcu;
3795 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
3796 const operand_ty = f.typeOf(bin_op.lhs);
3797
3798 builtin: {
3799 if (operand_ty.isAbiInt(zcu)) {
3800 switch (CType.classifyInt(operand_ty, zcu)) {
3801 .void => unreachable,
3802 .small => |int| switch (int) {
3803 else => break :builtin,
3804 .zig_u128, .zig_i128 => {},
3805 },
3806 .big => {},
3807 }
3808 return airCmpBuiltinCall(f, inst, bin_op, operator, .cmp, .none);
3809 }
3810 if (operand_ty.isRuntimeFloat())
3811 return airCmpBuiltinCall(f, inst, bin_op, operator, .operator, .none);
3812 }
3813
3814 const lhs = try f.resolveInst(bin_op.lhs);
3815 const rhs = try f.resolveInst(bin_op.rhs);
3816 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3817
3818 if (lhs.eql(rhs)) {
3819 // Avoid emitting a tautological comparison.
3820 return .{ .constant = .makeBool(switch (operator) {
3821 .eq, .lte, .gte => true,
3822 .neq, .lt, .gt => false,
3823 }) };
3824 }
3825
3826 const w = &f.code.writer;
3827 const local = try f.allocLocal(inst, .bool);
3828 try f.writeCValue(w, local, .other);
3829 try w.writeAll(" = ");
3830
3831 switch (operand_ty.zigTypeTag(zcu)) {
3832 .optional => switch (CType.classifyOptional(operand_ty, zcu)) {
3833 .npv_payload => unreachable, // opv optional
3834
3835 .error_set, .ptr_like => {},
3836
3837 .slice_like => unreachable, // equality is not defined on slices
3838
3839 .opv_payload => {
3840 try f.writeCValueMember(w, lhs, .{ .identifier = "is_null" });
3841 try w.writeAll(compareOperatorC(operator));
3842 try f.writeCValueMember(w, rhs, .{ .identifier = "is_null" });
3843 try w.writeByte(';');
3844 try f.newline();
3845 return local;
3846 },
3847
3848 .@"struct" => {
3849 // `lhs.is_null || rhs.is_null ? lhs.is_null == rhs.is_null : lhs.payload == rhs.payload`
3850 try f.writeCValueMember(w, lhs, .{ .identifier = "is_null" });
3851 try w.writeAll(" || ");
3852 try f.writeCValueMember(w, rhs, .{ .identifier = "is_null" });
3853 try w.writeAll(" ? ");
3854 try f.writeCValueMember(w, lhs, .{ .identifier = "is_null" });
3855 try w.writeAll(compareOperatorC(operator));
3856 try f.writeCValueMember(w, rhs, .{ .identifier = "is_null" });
3857 try w.writeAll(" : ");
3858 try f.writeCValueMember(w, lhs, .{ .identifier = "payload" });
3859 try w.writeAll(compareOperatorC(operator));
3860 try f.writeCValueMember(w, rhs, .{ .identifier = "payload" });
3861 try w.writeByte(';');
3862 try f.newline();
3863 return local;
3864 },
3865 },
3866 .bool, .int, .pointer, .@"enum", .error_set => {},
3867 .@"struct", .@"union" => assert(operand_ty.containerLayout(zcu) == .@"packed"),
3868 else => unreachable,
3869 }
3870
3871 try f.writeCValue(w, lhs, .other);
3872 try w.writeAll(compareOperatorC(operator));
3873 try f.writeCValue(w, rhs, .other);
3874 try w.writeByte(';');
3875 try f.newline();
3876
3877 return local;
3878}
3879
3880fn airCmpLteErrorsLen(f: *Function, inst: Air.Inst.Index) !CValue {
3881 const un_op = f.air.instructions.items(.data)[@backingInt(inst)].un_op;
3882
3883 const operand = try f.resolveInst(un_op);
3884 try reap(f, inst, &.{un_op});
3885
3886 const w = &f.code.writer;
3887 const local = try f.allocLocal(inst, .bool);
3888 try f.writeCValue(w, local, .other);
3889 try w.writeAll(" = ");
3890 try f.writeCValue(w, operand, .other);
3891 try w.writeAll(" <= sizeof(zig_errorName) / sizeof(*zig_errorName);");
3892 try f.newline();
3893 return local;
3894}
3895
3896fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: u8) !CValue {
3897 const pt = f.dg.pt;
3898 const zcu = pt.zcu;
3899 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
3900 const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;
3901
3902 const lhs = try f.resolveInst(bin_op.lhs);
3903 const rhs = try f.resolveInst(bin_op.rhs);
3904 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3905
3906 const inst_ty = f.typeOfIndex(inst);
3907 const inst_scalar_ty = inst_ty.scalarType(zcu);
3908 const elem_ty = inst_scalar_ty.indexableElem(zcu);
3909 assert(elem_ty.hasRuntimeBits(zcu));
3910
3911 const local = try f.allocLocal(inst, inst_ty);
3912 const w = &f.code.writer;
3913 const v = try Vectorize.start(f, inst, w, inst_ty);
3914 try f.writeCValue(w, local, .other);
3915 try v.elem(f, w);
3916 try w.writeAll(" = ");
3917 // We must convert to and from integer types to prevent UB if the operation
3918 // results in a NULL pointer, or if LHS is NULL. The operation is only UB
3919 // if the result is NULL and then dereferenced.
3920 try w.writeByte('(');
3921 try f.renderType(w, inst_scalar_ty);
3922 try w.writeAll(")(((uintptr_t)");
3923 try f.writeCValue(w, lhs, .other);
3924 try v.elem(f, w);
3925 try w.print(") {c} (", .{operator});
3926 try f.writeCValue(w, rhs, .other);
3927 try v.elem(f, w);
3928 try w.writeAll("*sizeof(");
3929 try f.renderType(w, elem_ty);
3930 try w.writeAll(")));");
3931 try f.newline();
3932 try v.end(f, inst, w);
3933 return local;
3934}
3935
3936fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: u8, operation: []const u8) !CValue {
3937 const pt = f.dg.pt;
3938 const zcu = pt.zcu;
3939 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
3940
3941 const inst_ty = f.typeOfIndex(inst);
3942 const inst_scalar_ty = inst_ty.scalarType(zcu);
3943
3944 builtin: {
3945 if (inst_scalar_ty.isInt(zcu)) switch (CType.classifyInt(inst_scalar_ty, zcu)) {
3946 .void => unreachable,
3947 .small => |int| switch (int) {
3948 else => break :builtin,
3949 .zig_u128, .zig_i128 => {},
3950 },
3951 .big => {},
3952 } else if (!inst_scalar_ty.isRuntimeFloat()) break :builtin;
3953 return airBinBuiltinCall(f, inst, operation, .none);
3954 }
3955
3956 const lhs = try f.resolveInst(bin_op.lhs);
3957 const rhs = try f.resolveInst(bin_op.rhs);
3958 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3959
3960 const w = &f.code.writer;
3961 const local = try f.allocLocal(inst, inst_ty);
3962 const v = try Vectorize.start(f, inst, w, inst_ty);
3963 try f.writeCValue(w, local, .other);
3964 try v.elem(f, w);
3965 // (lhs <> rhs) ? lhs : rhs
3966 try w.writeAll(" = (");
3967 try f.writeCValue(w, lhs, .other);
3968 try v.elem(f, w);
3969 try w.writeByte(' ');
3970 try w.writeByte(operator);
3971 try w.writeByte(' ');
3972 try f.writeCValue(w, rhs, .other);
3973 try v.elem(f, w);
3974 try w.writeAll(") ? ");
3975 try f.writeCValue(w, lhs, .other);
3976 try v.elem(f, w);
3977 try w.writeAll(" : ");
3978 try f.writeCValue(w, rhs, .other);
3979 try v.elem(f, w);
3980 try w.writeByte(';');
3981 try f.newline();
3982 try v.end(f, inst, w);
3983
3984 return local;
3985}
3986
3987fn airSlice(f: *Function, inst: Air.Inst.Index) !CValue {
3988 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
3989 const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;
3990
3991 const ptr = try f.resolveInst(bin_op.lhs);
3992 const len = try f.resolveInst(bin_op.rhs);
3993 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
3994
3995 const inst_ty = f.typeOfIndex(inst);
3996
3997 const w = &f.code.writer;
3998 const local = try f.allocLocal(inst, inst_ty);
3999
4000 try f.writeCValueMember(w, local, .{ .identifier = "ptr" });
4001 try w.writeAll(" = ");
4002 try f.writeCValue(w, ptr, .other);
4003 try w.writeByte(';');
4004 try f.newline();
4005
4006 try f.writeCValueMember(w, local, .{ .identifier = "len" });
4007 try w.writeAll(" = ");
4008 try f.writeCValue(w, len, .other);
4009 try w.writeByte(';');
4010 try f.newline();
4011
4012 return local;
4013}
4014
4015fn airCall(
4016 f: *Function,
4017 inst: Air.Inst.Index,
4018 modifier: std.lang.CallModifier,
4019) !CValue {
4020 const pt = f.dg.pt;
4021 const zcu = pt.zcu;
4022 const ip = &zcu.intern_pool;
4023 // Not even allowed to call panic in a naked function.
4024 if (f.dg.is_naked_fn) return .none;
4025
4026 const gpa = f.dg.gpa;
4027 const w = &f.code.writer;
4028
4029 const call = f.air.unwrapCall(inst);
4030 const args = call.args;
4031
4032 const resolved_args = try gpa.alloc(CValue, args.len);
4033 defer gpa.free(resolved_args);
4034 for (resolved_args, args) |*resolved_arg, arg| {
4035 const arg_ty = f.typeOf(arg);
4036 if (!arg_ty.hasRuntimeBits(zcu)) {
4037 resolved_arg.* = .none;
4038 continue;
4039 }
4040 resolved_arg.* = try f.resolveInst(arg);
4041 }
4042
4043 const callee = try f.resolveInst(call.callee);
4044
4045 {
4046 var bt = iterateBigTomb(f, inst);
4047 try bt.feed(call.callee);
4048 for (args) |arg| try bt.feed(arg);
4049 }
4050
4051 const callee_ty = f.typeOf(call.callee);
4052 const callee_is_ptr = switch (callee_ty.zigTypeTag(zcu)) {
4053 .@"fn" => false,
4054 .pointer => true,
4055 else => unreachable,
4056 };
4057 const fn_info = zcu.typeToFunc(if (callee_is_ptr) callee_ty.childType(zcu) else callee_ty).?;
4058 const ret_ty: Type = .fromInterned(fn_info.return_type);
4059
4060 const result_local = result: {
4061 if (modifier == .always_tail) {
4062 try w.writeAll("zig_always_tail return ");
4063 break :result .none;
4064 } else if (!ret_ty.hasRuntimeBits(zcu)) {
4065 break :result .none;
4066 } else if (f.liveness.isUnused(inst)) {
4067 try w.writeAll("(void)");
4068 break :result .none;
4069 } else {
4070 const local = try f.allocAlignedLocal(inst, .{ .type = ret_ty });
4071 try f.writeCValue(w, local, .other);
4072 try w.writeAll(" = ");
4073 break :result local;
4074 }
4075 };
4076
4077 callee: {
4078 known: {
4079 const callee_ip_index = call.callee.toInterned() orelse break :known;
4080 const fn_nav, const need_cast = switch (ip.indexToKey(callee_ip_index)) {
4081 .@"extern" => |@"extern"| .{ @"extern".owner_nav, false },
4082 .func => |func| .{ func.owner_nav, Type.fromInterned(func.ty).fnCallingConvention(zcu) != .naked and
4083 Type.fromInterned(func.uncoerced_ty).fnCallingConvention(zcu) == .naked },
4084 .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) {
4085 .nav => |nav| .{ nav, Type.fromInterned(ptr.ty).childType(zcu).fnCallingConvention(zcu) != .naked and
4086 zcu.navValue(nav).typeOf(zcu).fnCallingConvention(zcu) == .naked },
4087 else => break :known,
4088 } else break :known,
4089 else => break :known,
4090 };
4091 if (need_cast) {
4092 try w.writeAll("((");
4093 try f.renderType(w, if (callee_is_ptr) callee_ty else try pt.singleConstPtrType(callee_ty));
4094 try w.writeByte(')');
4095 if (!callee_is_ptr) try w.writeByte('&');
4096 }
4097 switch (modifier) {
4098 .auto, .always_tail => try renderNavName(w, fn_nav, ip),
4099 .never_tail => {
4100 try f.need_never_tail_funcs.put(gpa, fn_nav, {});
4101 try w.print("zig_never_tail_{f}__{d}", .{
4102 fmtIdentUnsolo(ip.getNav(fn_nav).name.toSlice(ip)), @backingInt(fn_nav),
4103 });
4104 },
4105 .never_inline => {
4106 try f.need_never_inline_funcs.put(gpa, fn_nav, {});
4107 try w.print("zig_never_inline_{f}__{d}", .{
4108 fmtIdentUnsolo(ip.getNav(fn_nav).name.toSlice(ip)), @backingInt(fn_nav),
4109 });
4110 },
4111 else => unreachable,
4112 }
4113 if (need_cast) try w.writeByte(')');
4114 break :callee;
4115 }
4116 switch (modifier) {
4117 .auto, .always_tail => {},
4118 .never_tail => return f.fail("CBE: runtime callee with never_tail attribute unsupported", .{}),
4119 .never_inline => return f.fail("CBE: runtime callee with never_inline attribute unsupported", .{}),
4120 else => unreachable,
4121 }
4122 // Fall back to function pointer call.
4123 try f.writeCValue(w, callee, .other);
4124 }
4125
4126 try w.writeByte('(');
4127 var need_comma = false;
4128 for (resolved_args) |resolved_arg| {
4129 if (resolved_arg == .none) continue;
4130 if (need_comma) try w.writeAll(", ");
4131 need_comma = true;
4132 try f.writeCValue(w, resolved_arg, .other);
4133 }
4134 try w.writeAll(");");
4135 switch (modifier) {
4136 .always_tail => try w.writeByte('\n'),
4137 else => try f.newline(),
4138 }
4139
4140 return result_local;
4141}
4142
4143fn airDbgStmt(f: *Function, inst: Air.Inst.Index) !CValue {
4144 const dbg_stmt = f.air.instructions.items(.data)[@backingInt(inst)].dbg_stmt;
4145 const w = &f.code.writer;
4146 try w.print("/* {d}:{d} */", .{ dbg_stmt.line + 1, dbg_stmt.column + 1 });
4147 try f.newline();
4148 return .none;
4149}
4150
4151fn airDbgEmptyStmt(f: *Function, _: Air.Inst.Index) !CValue {
4152 try f.code.writer.writeAll("(void)0;");
4153 try f.newline();
4154 return .none;
4155}
4156
4157fn airDbgInlineBlock(f: *Function, inst: Air.Inst.Index) !CValue {
4158 const pt = f.dg.pt;
4159 const zcu = pt.zcu;
4160 const ip = &zcu.intern_pool;
4161 const block = f.air.unwrapDbgBlock(inst);
4162 const owner_nav = ip.getNav(zcu.funcInfo(block.func).owner_nav);
4163 const w = &f.code.writer;
4164 try w.print("/* inline:{f} */", .{owner_nav.fqn.fmt(&zcu.intern_pool)});
4165 try f.newline();
4166 return lowerBlock(f, inst, block.body);
4167}
4168
4169fn airDbgVar(f: *Function, inst: Air.Inst.Index) !CValue {
4170 const pt = f.dg.pt;
4171 const zcu = pt.zcu;
4172 const tag = f.air.instructions.items(.tag)[@backingInt(inst)];
4173 const pl_op = f.air.instructions.items(.data)[@backingInt(inst)].pl_op;
4174 const name: Air.NullTerminatedString = @fromBackingInt(@intCast(pl_op.payload));
4175 const operand_is_undef = if (pl_op.operand.toInterned()) |ip_index| Value.fromInterned(ip_index).isUndef(zcu) else false;
4176 if (!operand_is_undef) _ = try f.resolveInst(pl_op.operand);
4177
4178 try reap(f, inst, &.{pl_op.operand});
4179 const w = &f.code.writer;
4180 try w.print("/* {s}:{s} */", .{ @tagName(tag), name.toSlice(f.air) });
4181 try f.newline();
4182 return .none;
4183}
4184
4185fn airBlock(f: *Function, inst: Air.Inst.Index) !CValue {
4186 const block = f.air.unwrapBlock(inst);
4187 return lowerBlock(f, inst, block.body);
4188}
4189
4190fn lowerBlock(f: *Function, inst: Air.Inst.Index, body: []const Air.Inst.Index) !CValue {
4191 const pt = f.dg.pt;
4192 const zcu = pt.zcu;
4193 const liveness_block = f.liveness.getBlock(inst);
4194
4195 const block_id = f.next_block_index;
4196 f.next_block_index += 1;
4197 const w = &f.code.writer;
4198
4199 const inst_ty = f.typeOfIndex(inst);
4200 const result = if (inst_ty.hasRuntimeBits(zcu) and !f.liveness.isUnused(inst))
4201 try f.allocLocal(inst, inst_ty)
4202 else
4203 .none;
4204
4205 try f.blocks.putNoClobber(f.dg.gpa, inst, .{
4206 .block_id = block_id,
4207 .result = result,
4208 });
4209
4210 try genBodyResolveState(f, inst, &.{}, body, true);
4211
4212 assert(f.blocks.remove(inst));
4213
4214 // The body might result in some values we had beforehand being killed
4215 for (liveness_block.deaths) |death| {
4216 try die(f, inst, death.toRef());
4217 }
4218
4219 // noreturn blocks have no `br` instructions reaching them, so we don't want a label
4220 if (f.dg.is_naked_fn) {
4221 if (f.dg.expected_block) |expected_block| {
4222 if (block_id != expected_block)
4223 return f.fail("runtime code not allowed in naked function", .{});
4224 f.dg.expected_block = null;
4225 }
4226 } else if (!f.typeOfIndex(inst).isNoReturn(zcu)) {
4227 // label must be followed by an expression, include an empty one.
4228 try w.print("\nzig_block_{d}:;", .{block_id});
4229 try f.newline();
4230 }
4231
4232 return result;
4233}
4234
4235fn airTry(f: *Function, inst: Air.Inst.Index) !CValue {
4236 const pt = f.dg.pt;
4237 const unwrapped_try = f.air.unwrapTry(inst);
4238 const body = unwrapped_try.else_body;
4239 const err_union_ty = f.air.typeOf(unwrapped_try.error_union, &pt.zcu.intern_pool);
4240 return lowerTry(f, inst, unwrapped_try.error_union, body, err_union_ty, false);
4241}
4242
4243fn airTryPtr(f: *Function, inst: Air.Inst.Index) !CValue {
4244 const pt = f.dg.pt;
4245 const unwrapped_try = f.air.unwrapTryPtr(inst);
4246 const body = unwrapped_try.else_body;
4247 const err_union_ty = f.air.typeOf(unwrapped_try.error_union_ptr, &pt.zcu.intern_pool).childType(pt.zcu);
4248 return lowerTry(f, inst, unwrapped_try.error_union_ptr, body, err_union_ty, true);
4249}
4250
4251fn lowerTry(
4252 f: *Function,
4253 inst: Air.Inst.Index,
4254 operand: Air.Inst.Ref,
4255 body: []const Air.Inst.Index,
4256 err_union_ty: Type,
4257 is_ptr: bool,
4258) !CValue {
4259 const pt = f.dg.pt;
4260 const zcu = pt.zcu;
4261 const err_union = try f.resolveInst(operand);
4262 const inst_ty = f.typeOfIndex(inst);
4263 const liveness_condbr = f.liveness.getCondBr(inst);
4264 const w = &f.code.writer;
4265 const payload_ty = err_union_ty.errorUnionPayload(zcu);
4266
4267 try w.writeAll("if (");
4268
4269 // Reap the operand so that it can be reused inside genBody.
4270 // Remember we must avoid calling reap() twice for the same operand
4271 // in this function.
4272 try reap(f, inst, &.{operand});
4273 if (is_ptr)
4274 try f.writeCValueDerefMember(w, err_union, .{ .identifier = "error" })
4275 else
4276 try f.writeCValueMember(w, err_union, .{ .identifier = "error" });
4277
4278 try w.writeAll(") ");
4279
4280 try genBodyResolveState(f, inst, liveness_condbr.else_deaths, body, false);
4281 try f.newline();
4282 if (f.dg.expected_block) |_|
4283 return f.fail("runtime code not allowed in naked function", .{});
4284
4285 // Now we have the "then branch" (in terms of the liveness data); process any deaths.
4286 for (liveness_condbr.then_deaths) |death| {
4287 try die(f, inst, death.toRef());
4288 }
4289
4290 if (!payload_ty.hasRuntimeBits(zcu)) {
4291 if (!is_ptr) {
4292 return .none;
4293 } else {
4294 return err_union;
4295 }
4296 }
4297
4298 try reap(f, inst, &.{operand});
4299
4300 if (f.liveness.isUnused(inst)) return .none;
4301
4302 const local = try f.allocLocal(inst, inst_ty);
4303 try f.writeCValue(w, local, .other);
4304 try w.writeAll(" = ");
4305 if (is_ptr) {
4306 try w.writeByte('&');
4307 try f.writeCValueDerefMember(w, err_union, .{ .identifier = "payload" });
4308 } else try f.writeCValueMember(w, err_union, .{ .identifier = "payload" });
4309 try w.writeByte(';');
4310 try f.newline();
4311 return local;
4312}
4313
4314fn airBr(f: *Function, inst: Air.Inst.Index) !void {
4315 const branch = f.air.instructions.items(.data)[@backingInt(inst)].br;
4316 const block = f.blocks.get(branch.block_inst).?;
4317 const result = block.result;
4318 const w = &f.code.writer;
4319
4320 if (f.dg.is_naked_fn) {
4321 if (result != .none) return f.fail("runtime code not allowed in naked function", .{});
4322 f.dg.expected_block = block.block_id;
4323 return;
4324 }
4325
4326 // If result is .none then the value of the block is unused.
4327 if (result != .none) {
4328 const operand = try f.resolveInst(branch.operand);
4329 try reap(f, inst, &.{branch.operand});
4330
4331 try f.writeCValue(w, result, .other);
4332 try w.writeAll(" = ");
4333 try f.writeCValue(w, operand, .other);
4334 try w.writeByte(';');
4335 try f.newline();
4336 }
4337
4338 try w.print("goto zig_block_{d};\n", .{block.block_id});
4339}
4340
4341fn airRepeat(f: *Function, inst: Air.Inst.Index) !void {
4342 const repeat = f.air.instructions.items(.data)[@backingInt(inst)].repeat;
4343 try f.code.writer.print("goto zig_loop_{d};\n", .{@backingInt(repeat.loop_inst)});
4344}
4345
4346fn airSwitchDispatch(f: *Function, inst: Air.Inst.Index) !void {
4347 const pt = f.dg.pt;
4348 const zcu = pt.zcu;
4349 const br = f.air.instructions.items(.data)[@backingInt(inst)].br;
4350 const w = &f.code.writer;
4351
4352 if (br.operand.toInterned()) |cond_ip_index| {
4353 const cond_val: Value = .fromInterned(cond_ip_index);
4354 // Comptime-known dispatch. Iterate the cases to find the correct
4355 // one, and branch directly to the corresponding case.
4356 const switch_br = f.air.unwrapSwitch(br.block_inst);
4357 var it = switch_br.iterateCases();
4358 const target_case_idx: u32 = target: while (it.next()) |case| {
4359 for (case.items) |item| {
4360 const val = Value.fromInterned(item.toInterned().?);
4361 if (cond_val.compareHetero(.eq, val, zcu)) break :target case.idx;
4362 }
4363 for (case.ranges) |range| {
4364 const low = Value.fromInterned(range[0].toInterned().?);
4365 const high = Value.fromInterned(range[1].toInterned().?);
4366 if (cond_val.compareHetero(.gte, low, zcu) and
4367 cond_val.compareHetero(.lte, high, zcu))
4368 {
4369 break :target case.idx;
4370 }
4371 }
4372 } else switch_br.cases_len;
4373 try w.print("goto zig_switch_{d}_dispatch_{d};\n", .{ @backingInt(br.block_inst), target_case_idx });
4374 return;
4375 }
4376
4377 // Runtime-known dispatch. Set the switch condition, and branch back.
4378 const cond = try f.resolveInst(br.operand);
4379 const cond_local = f.loop_switch_conds.get(br.block_inst).?;
4380 try f.writeCValue(w, .{ .local = cond_local }, .other);
4381 try w.writeAll(" = ");
4382 try f.writeCValue(w, cond, .other);
4383 try w.writeByte(';');
4384 try f.newline();
4385 try w.print("goto zig_switch_{d}_loop;\n", .{@backingInt(br.block_inst)});
4386}
4387
4388fn airPtrCast(f: *Function, inst: Air.Inst.Index) Error!CValue {
4389 const zcu = f.dg.pt.zcu;
4390
4391 const dest_ty = f.typeOfIndex(inst);
4392 const ptr_ty = switch (dest_ty.zigTypeTag(zcu)) {
4393 .optional => dest_ty.childType(zcu),
4394 .pointer => dest_ty,
4395 else => unreachable,
4396 };
4397
4398 if (!ptr_ty.isSlice(zcu)) {
4399 return airSimpleCast(f, inst);
4400 }
4401
4402 // For slice casts we need to assign both fields.
4403
4404 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
4405 const operand = try f.resolveInst(ty_op.operand);
4406
4407 const w = &f.code.writer;
4408 const dest_local = try f.allocLocal(inst, dest_ty);
4409
4410 try f.writeCValueMember(w, dest_local, .{ .identifier = "ptr" });
4411 try w.writeAll(" = (");
4412 try f.renderType(w, ptr_ty.slicePtrFieldType(zcu));
4413 try w.writeByte(')');
4414 try f.writeCValueMember(w, operand, .{ .identifier = "ptr" });
4415 try w.writeByte(';');
4416 try f.newline();
4417
4418 try f.writeCValueMember(w, dest_local, .{ .identifier = "len" });
4419 try w.writeAll(" = ");
4420 try f.writeCValueMember(w, operand, .{ .identifier = "len" });
4421 try w.writeByte(';');
4422 try f.newline();
4423
4424 try reap(f, inst, &.{ty_op.operand});
4425 return dest_local;
4426}
4427
4428fn airSimpleCast(f: *Function, inst: Air.Inst.Index) Error!CValue {
4429 const zcu = f.dg.pt.zcu;
4430
4431 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
4432 const dest_ty = f.typeOfIndex(inst);
4433 const operand_ty = f.typeOf(ty_op.operand);
4434 const operand = try f.resolveInst(ty_op.operand);
4435
4436 const w = &f.code.writer;
4437 const dest_local = try f.allocLocal(inst, dest_ty);
4438 const v: Vectorize = try .start(f, inst, w, operand_ty);
4439 try f.writeCValue(w, dest_local, .other);
4440 try v.elem(f, w);
4441 try w.writeAll(" = (");
4442 try f.renderType(w, dest_ty.scalarType(zcu));
4443 try w.writeByte(')');
4444 try f.writeCValue(w, operand, .other);
4445 try v.elem(f, w);
4446 try w.writeByte(';');
4447 try f.newline();
4448 try v.end(f, inst, w);
4449
4450 try reap(f, inst, &.{ty_op.operand});
4451 return dest_local;
4452}
4453
4454fn airNopCast(f: *Function, inst: Air.Inst.Index) Error!CValue {
4455 const zcu = f.dg.pt.zcu;
4456
4457 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
4458 const dest_ty = f.typeOfIndex(inst);
4459 const operand_ty = f.typeOf(ty_op.operand);
4460 const operand = try f.resolveInst(ty_op.operand);
4461
4462 assert(operand_ty.abiSize(zcu) == dest_ty.abiSize(zcu));
4463 assert(operand_ty.isAbiInt(zcu) == dest_ty.isAbiInt(zcu));
4464
4465 try reap(f, inst, &.{ty_op.operand});
4466 return f.moveCValue(inst, dest_ty, operand);
4467}
4468
4469fn airUnionFromEnum(f: *Function, inst: Air.Inst.Index) Error!CValue {
4470 const zcu = f.dg.pt.zcu;
4471
4472 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
4473 const dest_ty = f.typeOfIndex(inst);
4474 const operand_ty = f.typeOf(ty_op.operand);
4475 const operand = try f.resolveInst(ty_op.operand);
4476
4477 assert(dest_ty.zigTypeTag(zcu) == .@"union");
4478 assert(operand_ty.zigTypeTag(zcu) == .@"enum");
4479
4480 const w = &f.code.writer;
4481 const dest_local = try f.allocLocal(inst, dest_ty);
4482 try f.writeCValueMember(w, dest_local, .{ .identifier = "tag" });
4483 try w.writeAll(" = ");
4484 try f.writeCValue(w, operand, .other);
4485 try w.writeByte(';');
4486 try f.newline();
4487
4488 try reap(f, inst, &.{ty_op.operand});
4489 return dest_local;
4490}
4491
4492fn airBitCast(f: *Function, inst: Air.Inst.Index) Error!CValue {
4493 const pt = f.dg.pt;
4494 const zcu = pt.zcu;
4495 const w = &f.code.writer;
4496
4497 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
4498 const dest_ty = f.typeOfIndex(inst);
4499
4500 const operand = try f.resolveInst(ty_op.operand);
4501 const operand_ty = f.typeOf(ty_op.operand);
4502
4503 const dest_local = try f.allocLocal(inst, dest_ty);
4504
4505 // Because we have `scalarize_bit_cast_array` and `scalarize_bit_cast_vector_non_elementwise`
4506 // enabled, we usually only see scalars here. The only case in which we may see vectors is when
4507 // the operation happens elementwise, which we can handle with `Vectorize`.
4508 var v: Vectorize = try .start(f, inst, w, operand_ty);
4509 const operand_scalar_ty = operand_ty.scalarType(zcu);
4510 const dest_scalar_ty = dest_ty.scalarType(zcu);
4511
4512 if ((operand_scalar_ty.isRuntimeFloat() and dest_scalar_ty.isRuntimeFloat()) or
4513 (operand_scalar_ty.toIntern() == .bool_type and dest_scalar_ty.isAbiInt(zcu)))
4514 {
4515 // Some cases are handled with a simple cast:
4516 // * float -> float
4517 // * bool -> int
4518 try f.writeCValue(w, dest_local, .other);
4519 try v.elem(f, w);
4520 try w.writeAll(" = (");
4521 try f.renderType(w, dest_scalar_ty);
4522 try w.writeByte(')');
4523 try f.writeCValue(w, operand, .other);
4524 try v.elem(f, w);
4525 try w.writeByte(';');
4526 try f.newline();
4527 } else if (dest_scalar_ty.toIntern() == .bool_type) {
4528 // If the result is a boolean type, just check if the operand is non-zero.
4529 assert(operand_scalar_ty.isAbiInt(zcu));
4530 try f.writeCValue(w, dest_local, .other);
4531 try v.elem(f, w);
4532 try w.writeAll(" = ");
4533 try f.writeCValue(w, operand, .other);
4534 try v.elem(f, w);
4535 try w.writeAll(" != 0;");
4536 try f.newline();
4537 } else {
4538 assert(operand_scalar_ty.isRuntimeFloat() or operand_scalar_ty.isAbiInt(zcu));
4539 assert(dest_scalar_ty.isRuntimeFloat() or dest_scalar_ty.isAbiInt(zcu));
4540
4541 const ref_ret = lowersToBigInt(dest_scalar_ty, zcu);
4542 const ref_arg = lowersToBigInt(operand_scalar_ty, zcu);
4543
4544 if (!ref_ret) {
4545 try f.writeCValue(w, dest_local, .other);
4546 try v.elem(f, w);
4547 try w.writeAll(" = ");
4548 }
4549 try w.writeAll("zig_");
4550 try f.dg.renderTypeForBuiltinFnName(w, dest_scalar_ty);
4551 try w.writeAll("_bitCast_");
4552 try f.dg.renderTypeForBuiltinFnName(w, operand_scalar_ty);
4553 try w.writeByte('(');
4554 if (ref_ret) {
4555 try w.writeByte('&');
4556 try f.writeCValue(w, dest_local, .other);
4557 try v.elem(f, w);
4558 try w.writeAll(", ");
4559 }
4560 if (ref_arg) {
4561 try w.writeByte('&');
4562 switch (operand) {
4563 .constant => |val| try f.dg.renderValueAsLvalue(w, val),
4564 else => try f.writeCValue(w, operand, .other),
4565 }
4566 } else try f.writeCValue(w, operand, .other);
4567 try v.elem(f, w);
4568 try f.dg.renderBuiltinInfo(
4569 w,
4570 dest_scalar_ty,
4571 if (operand_scalar_ty.isRuntimeFloat() or dest_scalar_ty.isRuntimeFloat()) .none else .bits,
4572 );
4573 try w.writeAll(");");
4574 try f.newline();
4575 }
4576
4577 try v.end(f, inst, w);
4578
4579 try reap(f, inst, &.{ty_op.operand});
4580 return dest_local;
4581}
4582
4583fn airTrap(f: *Function) !void {
4584 // Not even allowed to call trap in a naked function.
4585 if (f.dg.is_naked_fn) return;
4586 try f.code.writer.writeAll("zig_trap();\n");
4587}
4588
4589fn airBreakpoint(f: *Function) !CValue {
4590 const w = &f.code.writer;
4591 try w.writeAll("zig_breakpoint();");
4592 try f.newline();
4593 return .none;
4594}
4595
4596fn airRetAddr(f: *Function, inst: Air.Inst.Index) !CValue {
4597 const w = &f.code.writer;
4598 const local = try f.allocLocal(inst, .usize);
4599 try f.writeCValue(w, local, .other);
4600 try w.writeAll(" = (");
4601 try f.renderType(w, .usize);
4602 try w.writeAll(")zig_return_address();");
4603 try f.newline();
4604 return local;
4605}
4606
4607fn airFrameAddress(f: *Function, inst: Air.Inst.Index) !CValue {
4608 const w = &f.code.writer;
4609 const local = try f.allocLocal(inst, .usize);
4610 try f.writeCValue(w, local, .other);
4611 try w.writeAll(" = (");
4612 try f.renderType(w, .usize);
4613 try w.writeAll(")zig_frame_address();");
4614 try f.newline();
4615 return local;
4616}
4617
4618fn airUnreach(f: *Function) !void {
4619 // Not even allowed to call unreachable in a naked function.
4620 if (f.dg.is_naked_fn) return;
4621 try f.code.writer.writeAll("zig_unreachable();\n");
4622}
4623
4624fn airLoop(f: *Function, inst: Air.Inst.Index) !void {
4625 const block = f.air.unwrapBlock(inst);
4626 const w = &f.code.writer;
4627
4628 // `repeat` instructions matching this loop will branch to
4629 // this label. Since we need a label for arbitrary `repeat`
4630 // anyway, there's actually no need to use a "real" looping
4631 // construct at all!
4632 try w.print("zig_loop_{d}:", .{@backingInt(inst)});
4633 try f.newline();
4634 try genBodyInner(f, block.body); // no need to restore state, we're noreturn
4635}
4636
4637fn airCondBr(f: *Function, inst: Air.Inst.Index) !void {
4638 const cond_br = f.air.unwrapCondBr(inst);
4639 const cond = try f.resolveInst(cond_br.condition);
4640 try reap(f, inst, &.{cond_br.condition});
4641 const then_body = cond_br.then_body;
4642 const else_body = cond_br.else_body;
4643 const liveness_condbr = f.liveness.getCondBr(inst);
4644 const w = &f.code.writer;
4645
4646 try w.writeAll("if (");
4647 try f.writeCValue(w, cond, .other);
4648 try w.writeAll(") ");
4649
4650 try genBodyResolveState(f, inst, liveness_condbr.then_deaths, then_body, false);
4651 try f.newline();
4652 if (else_body.len > 0) if (f.dg.expected_block) |_|
4653 return f.fail("runtime code not allowed in naked function", .{});
4654
4655 // We don't need to use `genBodyResolveState` for the else block, because this instruction is
4656 // noreturn so must terminate a body, therefore we don't need to leave `value_map` or
4657 // `free_locals_map` well defined (our parent is responsible for doing that).
4658
4659 for (liveness_condbr.else_deaths) |death| {
4660 try die(f, inst, death.toRef());
4661 }
4662
4663 // We never actually need an else block, because our branches are noreturn so must (for
4664 // instance) `br` to a block (label).
4665
4666 try genBodyInner(f, else_body);
4667}
4668
4669fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void {
4670 const pt = f.dg.pt;
4671 const zcu = pt.zcu;
4672 const gpa = f.dg.gpa;
4673 const switch_br = f.air.unwrapSwitch(inst);
4674 const init_condition = try f.resolveInst(switch_br.operand);
4675 try reap(f, inst, &.{switch_br.operand});
4676 const cond_ty = f.typeOf(switch_br.operand);
4677 const w = &f.code.writer;
4678
4679 // For dispatches, we will create a local alloc to contain the condition value.
4680 // This may not result in optimal codegen for switch loops, but it minimizes the
4681 // amount of C code we generate, which is probably more desirable here (and is simpler).
4682 const cond_val = if (is_dispatch_loop) cond: {
4683 const new_local = try f.allocLocal(inst, cond_ty);
4684 try f.copyCValue(new_local, init_condition);
4685 try w.print("zig_switch_{d}_loop:", .{@backingInt(inst)});
4686 try f.newline();
4687 try f.loop_switch_conds.put(gpa, inst, new_local.new_local);
4688 break :cond new_local;
4689 } else init_condition;
4690
4691 defer if (is_dispatch_loop) {
4692 assert(f.loop_switch_conds.remove(inst));
4693 };
4694
4695 const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1);
4696 defer gpa.free(liveness.deaths);
4697
4698 const lowered_cond_ty: Type = switch (cond_ty.zigTypeTag(zcu)) {
4699 .@"enum", .error_set, .int, .@"struct", .@"union" => cond_ty,
4700 .bool => .u1,
4701 .pointer => .usize,
4702 .void => unreachable, // OPV type, always lowered to block/loop
4703 .comptime_int, .enum_literal, .@"fn", .type => unreachable, // comptime-only
4704 else => unreachable, // not supported by switch statement
4705 };
4706 const cond_cint = switch (CType.classifyInt(lowered_cond_ty, zcu)) {
4707 .void => unreachable, // OPV type, always lowered to block/loop
4708 .small => |small| small,
4709 .big => {
4710 return lowerSwitchToConditions(f, inst, cond_val, lowered_cond_ty, switch_br, liveness, is_dispatch_loop, false);
4711 },
4712 };
4713
4714 switch (cond_cint) {
4715 .zig_u128, .zig_i128 => try w.writeAll("zig_switch_int128("),
4716 else => try w.writeAll("switch ("),
4717 }
4718 if (cond_ty.toIntern() != lowered_cond_ty.toIntern()) {
4719 try w.writeByte('(');
4720 try f.renderType(w, lowered_cond_ty);
4721 try w.writeByte(')');
4722 }
4723 try f.writeCValue(w, cond_val, .other);
4724 try w.writeAll(") {");
4725 f.indent();
4726
4727 var any_range_cases = false;
4728 var it = switch_br.iterateCases();
4729 while (it.next()) |case| {
4730 if (case.ranges.len > 0) {
4731 any_range_cases = true;
4732 continue;
4733 }
4734
4735 switch (cond_cint) {
4736 .zig_u128, .zig_i128 => {
4737 try f.newline();
4738 try w.writeAll("zig_switch_prong_begin_int128()");
4739 },
4740 else => {},
4741 }
4742
4743 for (case.items) |item| {
4744 try f.newline();
4745 case: {
4746 switch (cond_cint) {
4747 .zig_u128 => try w.writeAll(" zig_switch_case_int128(u128, "),
4748 .zig_i128 => try w.writeAll(" zig_switch_case_int128(i128, "),
4749 else => {
4750 try w.writeAll("case ");
4751 break :case;
4752 },
4753 }
4754 if (cond_ty.toIntern() != lowered_cond_ty.toIntern()) {
4755 try w.writeByte('(');
4756 try f.renderType(w, lowered_cond_ty);
4757 try w.writeByte(')');
4758 }
4759 try f.writeCValue(w, cond_val, .other);
4760 try w.writeAll(", ");
4761 }
4762 const item_value: Value = .fromInterned(item.toInterned().?);
4763 // If `item_value` is a pointer with a known integer address, print the address
4764 // with no cast to avoid a warning.
4765 write_val: {
4766 if (cond_ty.zigTypeTag(zcu) == .pointer) {
4767 if (item_value.getUnsignedInt(zcu)) |item_int| {
4768 try w.print("{f}", .{try f.fmtIntLiteralDec(try pt.intValue(lowered_cond_ty, item_int))});
4769 break :write_val;
4770 }
4771 try w.writeByte('(');
4772 try f.renderType(w, .usize);
4773 try w.writeByte(')');
4774 }
4775 try f.dg.renderValue(w, .fromInterned(item.toInterned().?), .other);
4776 }
4777 switch (cond_cint) {
4778 .zig_u128, .zig_i128 => try w.writeByte(')'),
4779 else => try w.writeByte(':'),
4780 }
4781 }
4782
4783 switch (cond_cint) {
4784 .zig_u128, .zig_i128 => {
4785 try f.newline();
4786 try w.writeAll("zig_switch_prong_end_int128()");
4787 },
4788 else => {},
4789 }
4790
4791 try w.writeAll(" {");
4792 f.indent();
4793 try f.newline();
4794 if (is_dispatch_loop) {
4795 try w.print("zig_switch_{d}_dispatch_{d}:;", .{ @backingInt(inst), case.idx });
4796 try f.newline();
4797 }
4798 try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
4799 try f.outdent();
4800 try w.writeByte('}');
4801 if (f.dg.expected_block) |_|
4802 return f.fail("runtime code not allowed in naked function", .{});
4803
4804 // The case body must be noreturn so we don't need to insert a break.
4805 }
4806
4807 try f.newline();
4808
4809 switch (cond_cint) {
4810 .zig_u128, .zig_i128 => try w.writeAll("zig_switch_default_int128() "),
4811 else => try w.writeAll("default: "),
4812 }
4813 if (any_range_cases) {
4814 // We will iterate the cases again to handle those with ranges, and generate
4815 // code using conditions rather than switch cases for such cases.
4816 try lowerSwitchToConditions(f, inst, cond_val, lowered_cond_ty, switch_br, liveness, is_dispatch_loop, true);
4817 }
4818 if (is_dispatch_loop) {
4819 try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @backingInt(inst), switch_br.cases_len });
4820 }
4821 const else_body = it.elseBody();
4822 if (else_body.len > 0) {
4823 // Note that this must be the last case, so we do not need to use `genBodyResolveState`
4824 // since the parent block will do it (because the case body is noreturn).
4825 for (liveness.deaths[liveness.deaths.len - 1]) |death| {
4826 try die(f, inst, death.toRef());
4827 }
4828 try genBody(f, else_body);
4829 if (f.dg.expected_block) |_|
4830 return f.fail("runtime code not allowed in naked function", .{});
4831 } else try airUnreach(f);
4832 try f.newline();
4833 try f.outdent();
4834 try w.writeAll("}\n");
4835}
4836fn lowerSwitchToConditions(
4837 f: *Function,
4838 inst: Air.Inst.Index,
4839 cond_val: CValue,
4840 cond_ty: Type,
4841 switch_br: Air.UnwrappedSwitch,
4842 liveness: Air.Liveness.SwitchBrTable,
4843 is_dispatch_loop: bool,
4844 only_ranges: bool,
4845) !void {
4846 const w = &f.code.writer;
4847
4848 var it = switch_br.iterateCases();
4849 while (it.next()) |case| {
4850 if (case.ranges.len == 0 and only_ranges) continue;
4851
4852 try w.writeAll("if (");
4853 for (case.items, 0..) |item, item_i| {
4854 if (item_i != 0) {
4855 try f.newline();
4856 try w.writeAll(" || ");
4857 }
4858 try lowerSwitchCmp(f, cond_val, .eq, item, cond_ty);
4859 }
4860 for (case.ranges, 0..) |range, range_i| {
4861 if (case.items.len != 0 or range_i != 0) {
4862 try f.newline();
4863 try w.writeAll(" || ");
4864 }
4865 // "(x >= lower && x <= upper)"
4866 try w.writeByte('(');
4867 try lowerSwitchCmp(f, cond_val, .gte, range[0], cond_ty);
4868 try w.writeAll(" && ");
4869 try lowerSwitchCmp(f, cond_val, .lte, range[1], cond_ty);
4870 try w.writeByte(')');
4871 }
4872 try w.writeAll(") {");
4873 f.indent();
4874 try f.newline();
4875 if (is_dispatch_loop) {
4876 try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @backingInt(inst), case.idx });
4877 }
4878 try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
4879 try f.outdent();
4880 try w.writeByte('}');
4881 try f.newline();
4882 if (f.dg.expected_block) |_|
4883 return f.fail("runtime code not allowed in naked function", .{});
4884 }
4885
4886 if (!only_ranges) {
4887 if (is_dispatch_loop) {
4888 try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @backingInt(inst), switch_br.cases_len });
4889 }
4890 const else_body = it.elseBody();
4891 if (else_body.len > 0) {
4892 // Note that this must be the last case, so we do not need to use `genBodyResolveState`
4893 // since the parent block will do it (because the case body is noreturn).
4894 for (liveness.deaths[liveness.deaths.len - 1]) |death| {
4895 try die(f, inst, death.toRef());
4896 }
4897 try genBody(f, else_body);
4898 if (f.dg.expected_block) |_|
4899 return f.fail("runtime code not allowed in naked function", .{});
4900 } else try airUnreach(f);
4901 try f.newline();
4902 }
4903}
4904fn lowerSwitchCmp(
4905 f: *Function,
4906 cond_val: CValue,
4907 operator: std.math.CompareOperator,
4908 case_inst: Air.Inst.Ref,
4909 ty: Type,
4910) !void {
4911 const pt = f.dg.pt;
4912 const zcu = pt.zcu;
4913 const w = &f.code.writer;
4914
4915 const class = CType.classifyInt(ty, zcu);
4916 const use_builtin = switch (class) {
4917 .void => unreachable, // assertion failure
4918 .small => |small| switch (small) {
4919 .zig_u128, .zig_i128 => true,
4920 else => false,
4921 },
4922 .big => true,
4923 };
4924 if (use_builtin) {
4925 try w.writeAll("zig_cmp_");
4926 try f.dg.renderTypeForBuiltinFnName(w, ty);
4927 try w.writeByte('(');
4928 }
4929 if (class == .big) try w.writeByte('&');
4930 try f.writeCValue(w, cond_val, .other);
4931 try w.writeAll(if (use_builtin) ", " else compareOperatorC(operator));
4932 if (class == .big) try w.writeByte('&');
4933 try f.dg.renderValue(w, .fromInterned(case_inst.toInterned().?), .other);
4934 if (use_builtin) {
4935 try f.dg.renderBuiltinInfo(w, ty, if (class == .big) .bits else .none);
4936 try w.writeByte(')');
4937 try w.writeAll(compareOperatorC(operator));
4938 try w.writeByte('0');
4939 }
4940}
4941
4942fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool {
4943 const dg = f.dg;
4944 return switch (constraint[0]) {
4945 '{' => true,
4946 'r', 'i', 'n', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P' => false,
4947 else => switch (value) {
4948 .constant => |val| switch (dg.pt.zcu.intern_pool.indexToKey(val.toIntern())) {
4949 .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) {
4950 .nav => false,
4951 else => true,
4952 } else true,
4953 else => true,
4954 },
4955 else => false,
4956 },
4957 };
4958}
4959
4960fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
4961 const pt = f.dg.pt;
4962 const zcu = pt.zcu;
4963 const unwrapped_asm = f.air.unwrapAsm(inst);
4964 const is_volatile = unwrapped_asm.is_volatile;
4965 const gpa = f.dg.gpa;
4966 const outputs = unwrapped_asm.outputs;
4967 const inputs = unwrapped_asm.inputs;
4968
4969 const result = result: {
4970 const w = &f.code.writer;
4971 const inst_ty = f.typeOfIndex(inst);
4972 const inst_local = if (inst_ty.hasRuntimeBits(zcu)) local: {
4973 const inst_local = try f.allocLocalValue(.{ .type = inst_ty });
4974 if (f.wantSafety()) {
4975 try f.writeCValue(w, inst_local, .other);
4976 try w.writeAll(" = ");
4977 try f.writeCValue(w, .{ .undef = inst_ty }, .other);
4978 try w.writeByte(';');
4979 try f.newline();
4980 }
4981 break :local inst_local;
4982 } else .none;
4983
4984 const locals_begin: LocalIndex = @intCast(f.locals.items.len);
4985 var it = unwrapped_asm.iterateOutputs();
4986 while (it.next()) |output| {
4987 const constraint = output.constraint;
4988
4989 if (constraint.len < 2 or
4990 (constraint[0] != '=' and constraint[0] != '+') or
4991 (constraint[1] == '{' and constraint[constraint.len - 1] != '}'))
4992 {
4993 return f.fail("CBE: constraint not supported: '{s}'", .{constraint});
4994 }
4995
4996 const is_reg = constraint[1] == '{';
4997 if (is_reg) {
4998 const output_ty = if (output.operand == .none) inst_ty else f.typeOf(output.operand).childType(zcu);
4999 try w.writeAll("register ");
5000 const output_local = try f.allocLocalValue(.{ .type = output_ty });
5001 try f.allocs.put(gpa, output_local.new_local, false);
5002 try f.dg.renderTypeAndName(w, output_ty, output_local, .{}, .none);
5003 try w.writeAll(" __asm(\"");
5004 try w.writeAll(constraint["={".len .. constraint.len - "}".len]);
5005 try w.writeAll("\")");
5006 if (f.wantSafety()) {
5007 try w.writeAll(" = ");
5008 try f.writeCValue(w, .{ .undef = output_ty }, .other);
5009 }
5010 try w.writeByte(';');
5011 try f.newline();
5012 }
5013 }
5014
5015 it = unwrapped_asm.iterateInputs();
5016 while (it.next()) |input| {
5017 const constraint = input.constraint;
5018
5019 if (constraint.len < 1 or mem.findScalar(u8, "=+&%", constraint[0]) != null or
5020 (constraint[0] == '{' and constraint[constraint.len - 1] != '}'))
5021 {
5022 return f.fail("CBE: constraint not supported: '{s}'", .{constraint});
5023 }
5024
5025 const is_reg = constraint[0] == '{';
5026 const input_val = try f.resolveInst(input.operand);
5027 if (asmInputNeedsLocal(f, constraint, input_val)) {
5028 const input_ty = f.typeOf(input.operand);
5029 if (is_reg) try w.writeAll("register ");
5030 const input_local = try f.allocLocalValue(.{ .type = input_ty });
5031 try f.allocs.put(gpa, input_local.new_local, false);
5032 // Do not render the declaration as `const` qualified if we're generating an
5033 // explicit `register` local, as GCC will ignore the constraint completely.
5034 try f.dg.renderTypeAndName(w, input_ty, input_local, .{ .@"const" = is_reg }, .none);
5035 if (is_reg) {
5036 try w.writeAll(" __asm(\"");
5037 try w.writeAll(constraint["{".len .. constraint.len - "}".len]);
5038 try w.writeAll("\")");
5039 }
5040 try w.writeAll(" = ");
5041 try f.writeCValue(w, input_val, .other);
5042 try w.writeByte(';');
5043 try f.newline();
5044 }
5045 }
5046
5047 {
5048 const asm_source = unwrapped_asm.source;
5049
5050 var bfa_buf: [256]u8 = undefined;
5051 var bfa: std.heap.BufferFirstAllocator = .init(&bfa_buf, f.dg.gpa);
5052 const allocator = bfa.allocator();
5053 const fixed_asm_source = try allocator.alloc(u8, asm_source.len);
5054 defer allocator.free(fixed_asm_source);
5055
5056 var src_i: usize = 0;
5057 var dst_i: usize = 0;
5058 while (true) {
5059 const literal = mem.sliceTo(asm_source[src_i..], '%');
5060 src_i += literal.len;
5061
5062 @memcpy(fixed_asm_source[dst_i..][0..literal.len], literal);
5063 dst_i += literal.len;
5064
5065 if (src_i >= asm_source.len) break;
5066
5067 src_i += 1;
5068 if (src_i >= asm_source.len)
5069 return f.fail("CBE: invalid inline asm string '{s}'", .{asm_source});
5070
5071 fixed_asm_source[dst_i] = '%';
5072 dst_i += 1;
5073
5074 if (asm_source[src_i] != '[') {
5075 // This also handles %%
5076 fixed_asm_source[dst_i] = asm_source[src_i];
5077 src_i += 1;
5078 dst_i += 1;
5079 continue;
5080 }
5081
5082 const desc = mem.sliceTo(asm_source[src_i..], ']');
5083 if (mem.findScalar(u8, desc, ':')) |colon| {
5084 const name = desc[0..colon];
5085 const modifier = desc[colon + 1 ..];
5086
5087 @memcpy(fixed_asm_source[dst_i..][0..modifier.len], modifier);
5088 dst_i += modifier.len;
5089 @memcpy(fixed_asm_source[dst_i..][0..name.len], name);
5090 dst_i += name.len;
5091
5092 src_i += desc.len;
5093 if (src_i >= asm_source.len)
5094 return f.fail("CBE: invalid inline asm string '{s}'", .{asm_source});
5095 }
5096 }
5097
5098 try w.writeAll("__asm");
5099 if (is_volatile) try w.writeAll(" volatile");
5100 try w.print("({f}", .{fmtStringLiteral(fixed_asm_source[0..dst_i], null)});
5101 }
5102
5103 var locals_index = locals_begin;
5104 try w.writeByte(':');
5105
5106 it = unwrapped_asm.iterateOutputs();
5107 while (it.next()) |output| {
5108 const constraint = output.constraint;
5109 const name = output.name;
5110
5111 if (output.index > 0) try w.writeByte(',');
5112 try w.writeByte(' ');
5113 if (!mem.eql(u8, name, "_")) try w.print("[{s}]", .{name});
5114 const is_reg = constraint[1] == '{';
5115 try w.print("{f}(", .{fmtStringLiteral(if (is_reg) "=r" else constraint, null)});
5116 if (is_reg) {
5117 try f.writeCValue(w, .{ .local = locals_index }, .other);
5118 locals_index += 1;
5119 } else if (output.operand == .none) {
5120 try f.writeCValue(w, inst_local, .other);
5121 } else {
5122 try f.writeCValueDeref(w, try f.resolveInst(output.operand));
5123 }
5124 try w.writeByte(')');
5125 }
5126 try w.writeByte(':');
5127
5128 it = unwrapped_asm.iterateInputs();
5129 while (it.next()) |input| {
5130 const constraint = input.constraint;
5131 const name = input.name;
5132
5133 if (input.index > 0) try w.writeByte(',');
5134 try w.writeByte(' ');
5135 if (!mem.eql(u8, name, "_")) try w.print("[{s}]", .{name});
5136
5137 const is_reg = constraint[0] == '{';
5138 const input_val = try f.resolveInst(input.operand);
5139 try w.print("{f}(", .{fmtStringLiteral(if (is_reg) "r" else constraint, null)});
5140 try f.writeCValue(w, if (asmInputNeedsLocal(f, constraint, input_val)) local: {
5141 const input_local_idx = locals_index;
5142 locals_index += 1;
5143 break :local .{ .local = input_local_idx };
5144 } else input_val, .other);
5145 try w.writeByte(')');
5146 }
5147 try w.writeByte(':');
5148 const ip = &zcu.intern_pool;
5149 const clobbers_val: Value = .fromInterned(unwrapped_asm.clobbers);
5150 const clobbers_ty = clobbers_val.typeOf(zcu);
5151 var clobbers_bigint_buf: Value.BigIntSpace = undefined;
5152 const clobbers_bigint = clobbers_val.toBigInt(&clobbers_bigint_buf, zcu);
5153 for (0..clobbers_ty.structFieldCount(zcu)) |field_index| {
5154 assert(clobbers_ty.fieldType(field_index, zcu).toIntern() == .bool_type);
5155 const limb_bits = @bitSizeOf(std.math.big.Limb);
5156 if (field_index / limb_bits >= clobbers_bigint.limbs.len) continue; // field is false
5157 switch (@as(u1, @truncate(clobbers_bigint.limbs[field_index / limb_bits] >> @intCast(field_index % limb_bits)))) {
5158 0 => continue, // field is false
5159 1 => {}, // field is true
5160 }
5161 const field_name = clobbers_ty.structFieldName(field_index, zcu).toSlice(ip).?;
5162 assert(field_name.len != 0);
5163
5164 const target = &f.dg.mod.resolved_target.result;
5165 var c_name_buf: [16]u8 = undefined;
5166 const name =
5167 if ((target.cpu.arch.isMIPS() or target.cpu.arch == .alpha) and field_name[0] == 'r') name: {
5168 // Convert "rN" to "$N"
5169 const c_name = (&c_name_buf)[0..field_name.len];
5170 @memcpy(c_name, field_name);
5171 c_name_buf[0] = '$';
5172 break :name c_name;
5173 } else if ((target.cpu.arch.isMIPS() and (mem.startsWith(u8, field_name, "fcc") or field_name[0] == 'w')) or
5174 ((target.cpu.arch.isMIPS() or target.cpu.arch == .alpha) and field_name[0] == 'f') or
5175 (target.cpu.arch == .kvx and !mem.eql(u8, field_name, "memory"))) name: {
5176 // "$" prefix for these registers
5177 c_name_buf[0] = '$';
5178 @memcpy((&c_name_buf)[1..][0..field_name.len], field_name);
5179 break :name (&c_name_buf)[0 .. 1 + field_name.len];
5180 } else if (target.cpu.arch.isSPARC() and
5181 (mem.eql(u8, field_name, "ccr") or mem.eql(u8, field_name, "icc") or mem.eql(u8, field_name, "xcc"))) name: {
5182 // C compilers just use `icc` to encompass all of these.
5183 break :name "icc";
5184 } else if (target.cpu.arch.isRISCV() and mem.eql(u8, field_name, "fp")) name: {
5185 break :name "s0";
5186 } else field_name;
5187
5188 try w.print(" {f}", .{fmtStringLiteral(name, null)});
5189 (try w.writableArray(1))[0] = ',';
5190 }
5191 w.undo(1); // erase the last comma
5192 try w.writeAll(");");
5193 try f.newline();
5194
5195 locals_index = locals_begin;
5196 it = unwrapped_asm.iterateOutputs();
5197 while (it.next()) |output| {
5198 const constraint = output.constraint;
5199
5200 const is_reg = constraint[1] == '{';
5201 if (is_reg) {
5202 try f.writeCValueDeref(w, if (output.operand == .none)
5203 .{ .local_ref = inst_local.new_local }
5204 else
5205 try f.resolveInst(output.operand));
5206 try w.writeAll(" = ");
5207 try f.writeCValue(w, .{ .local = locals_index }, .other);
5208 locals_index += 1;
5209 try w.writeByte(';');
5210 try f.newline();
5211 }
5212 }
5213
5214 break :result if (f.liveness.isUnused(inst)) .none else inst_local;
5215 };
5216
5217 var bt = iterateBigTomb(f, inst);
5218 for (outputs) |output| {
5219 if (output == .none) continue;
5220 try bt.feed(output);
5221 }
5222 for (inputs) |input| {
5223 try bt.feed(input);
5224 }
5225
5226 return result;
5227}
5228
5229fn airIsNull(
5230 f: *Function,
5231 inst: Air.Inst.Index,
5232 operator: enum { eq, neq },
5233 is_ptr: bool,
5234) !CValue {
5235 const pt = f.dg.pt;
5236 const zcu = pt.zcu;
5237 const un_op = f.air.instructions.items(.data)[@backingInt(inst)].un_op;
5238
5239 const w = &f.code.writer;
5240 const operand = try f.resolveInst(un_op);
5241 try reap(f, inst, &.{un_op});
5242
5243 const local = try f.allocLocal(inst, .bool);
5244 try f.writeCValue(w, local, .other);
5245 try w.writeAll(" = ");
5246
5247 const operand_ty = f.typeOf(un_op);
5248 const optional_ty = if (is_ptr) operand_ty.childType(zcu) else operand_ty;
5249
5250 const pre: []const u8, const maybe_field: ?[]const u8, const post: []const u8 = switch (operator) {
5251 // zig fmt: off
5252 .eq => switch (CType.classifyOptional(optional_ty, zcu)) {
5253 .npv_payload => unreachable, // opv optional
5254 .error_set => .{ "", null, " == 0" },
5255 .ptr_like => .{ "", null, " == NULL" },
5256 .slice_like => .{ "", "ptr", " == NULL" },
5257 .opv_payload => .{ "", "is_null", "" },
5258 .@"struct" => .{ "", "is_null", "" },
5259 },
5260 .neq => switch (CType.classifyOptional(optional_ty, zcu)) {
5261 .npv_payload => unreachable, // opv optional
5262 .error_set => .{ "", null, " != 0" },
5263 .ptr_like => .{ "", null, " != NULL" },
5264 .slice_like => .{ "", "ptr", " != NULL" },
5265 .opv_payload => .{ "!", "is_null", "" },
5266 .@"struct" => .{ "!", "is_null", "" },
5267 },
5268 // zig fmt: on
5269 };
5270
5271 try w.writeAll(pre);
5272 if (maybe_field) |field| {
5273 if (is_ptr) {
5274 try f.writeCValueDerefMember(w, operand, .{ .identifier = field });
5275 } else {
5276 try f.writeCValueMember(w, operand, .{ .identifier = field });
5277 }
5278 } else {
5279 if (is_ptr) {
5280 try f.writeCValueDeref(w, operand);
5281 } else {
5282 try f.writeCValue(w, operand, .other);
5283 }
5284 }
5285 try w.writeAll(post);
5286
5287 try w.writeByte(';');
5288 try f.newline();
5289 return local;
5290}
5291
5292fn airOptionalPayload(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValue {
5293 const pt = f.dg.pt;
5294 const zcu = pt.zcu;
5295 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5296
5297 const inst_ty = f.typeOfIndex(inst);
5298 const operand_ty = f.typeOf(ty_op.operand);
5299 const opt_ty = if (is_ptr) operand_ty.childType(zcu) else operand_ty;
5300
5301 const operand = try f.resolveInst(ty_op.operand);
5302
5303 switch (CType.classifyOptional(opt_ty, zcu)) {
5304 .npv_payload => unreachable, // opv optional
5305
5306 .opv_payload => return if (is_ptr) .{ .undef = inst_ty } else .none,
5307
5308 .error_set,
5309 .ptr_like,
5310 .slice_like,
5311 => return f.moveCValue(inst, inst_ty, operand),
5312
5313 .@"struct" => {
5314 const w = &f.code.writer;
5315 const local = try f.allocLocal(inst, inst_ty);
5316 try f.writeCValue(w, local, .other);
5317 try w.writeAll(" = ");
5318 if (is_ptr) {
5319 try w.writeByte('&');
5320 try f.writeCValueDerefMember(w, operand, .{ .identifier = "payload" });
5321 } else try f.writeCValueMember(w, operand, .{ .identifier = "payload" });
5322 try w.writeByte(';');
5323 try f.newline();
5324 return local;
5325 },
5326 }
5327}
5328
5329fn airOptionalPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
5330 const pt = f.dg.pt;
5331 const zcu = pt.zcu;
5332 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5333 const w = &f.code.writer;
5334 const operand = try f.resolveInst(ty_op.operand);
5335 try reap(f, inst, &.{ty_op.operand});
5336 const operand_ty = f.typeOf(ty_op.operand);
5337 const opt_ty = operand_ty.childType(zcu);
5338
5339 const inst_ty = f.typeOfIndex(inst);
5340
5341 switch (CType.classifyOptional(opt_ty, zcu)) {
5342 .npv_payload => unreachable, // opv optional
5343
5344 .opv_payload => {
5345 try f.writeCValueDerefMember(w, operand, .{ .identifier = "is_null" });
5346 try w.writeAll(" = ");
5347 try f.dg.renderValue(w, .false, .other);
5348 try w.writeByte(';');
5349 try f.newline();
5350 return .{ .undef = inst_ty };
5351 },
5352
5353 .error_set,
5354 .ptr_like,
5355 .slice_like,
5356 => return f.moveCValue(inst, inst_ty, operand),
5357
5358 .@"struct" => {
5359 try f.writeCValueDerefMember(w, operand, .{ .identifier = "is_null" });
5360 try w.writeAll(" = ");
5361 try f.dg.renderValue(w, .false, .other);
5362 try w.writeByte(';');
5363 try f.newline();
5364 if (f.liveness.isUnused(inst)) return .none;
5365 const local = try f.allocLocal(inst, inst_ty);
5366 try f.writeCValue(w, local, .other);
5367 try w.writeAll(" = &");
5368 try f.writeCValueDerefMember(w, operand, .{ .identifier = "payload" });
5369 try w.writeByte(';');
5370 try f.newline();
5371 return local;
5372 },
5373 }
5374}
5375
5376fn fieldLocation(
5377 container_ptr_ty: Type,
5378 field_ptr_ty: Type,
5379 field_index: u32,
5380 zcu: *Zcu,
5381) union(enum) {
5382 begin: void,
5383 field: CValue,
5384 byte_offset: u64,
5385} {
5386 const ip = &zcu.intern_pool;
5387 const container_ty: Type = .fromInterned(ip.indexToKey(container_ptr_ty.toIntern()).ptr_type.child);
5388 switch (ip.indexToKey(container_ty.toIntern())) {
5389 .struct_type => {
5390 const loaded_struct = ip.loadStructType(container_ty.toIntern());
5391 return switch (loaded_struct.layout) {
5392 .auto, .@"extern" => if (!container_ty.hasRuntimeBits(zcu))
5393 .begin
5394 else if (!field_ptr_ty.childType(zcu).hasRuntimeBits(zcu))
5395 .{ .byte_offset = loaded_struct.field_offsets.get(ip)[field_index] }
5396 else
5397 .{ .field = .{ .identifier = loaded_struct.field_names.get(ip)[field_index].toSlice(ip) } },
5398 .@"packed" => if (field_ptr_ty.ptrInfo(zcu).packed_offset.host_size == 0)
5399 .{ .byte_offset = @divExact(zcu.structPackedFieldBitOffset(loaded_struct, field_index) +
5400 container_ptr_ty.ptrInfo(zcu).packed_offset.bit_offset, 8) }
5401 else
5402 .begin,
5403 };
5404 },
5405 .tuple_type => return if (!container_ty.hasRuntimeBits(zcu))
5406 .begin
5407 else if (!field_ptr_ty.childType(zcu).hasRuntimeBits(zcu))
5408 .{ .byte_offset = container_ty.structFieldOffset(field_index, zcu) }
5409 else
5410 .{ .field = .{ .field = field_index } },
5411 .union_type => {
5412 const loaded_union = ip.loadUnionType(container_ty.toIntern());
5413 switch (loaded_union.layout) {
5414 .auto => {
5415 const field_ty: Type = .fromInterned(loaded_union.field_types.get(ip)[field_index]);
5416 if (!field_ty.hasRuntimeBits(zcu)) {
5417 if (container_ty.unionHasAllZeroBitFieldTypes(zcu)) return .begin;
5418 return .{ .field = .{ .identifier = "payload" } };
5419 }
5420 const field_name = ip.loadEnumType(loaded_union.enum_tag_type).field_names.get(ip)[field_index];
5421 return .{ .field = .{ .payload_identifier = field_name.toSlice(ip) } };
5422 },
5423 .@"extern" => {
5424 const field_ty: Type = .fromInterned(loaded_union.field_types.get(ip)[field_index]);
5425 if (!field_ty.hasRuntimeBits(zcu)) return .begin;
5426 const field_name = ip.loadEnumType(loaded_union.enum_tag_type).field_names.get(ip)[field_index];
5427 return .{ .field = .{ .identifier = field_name.toSlice(ip) } };
5428 },
5429 .@"packed" => return .begin,
5430 }
5431 },
5432 .ptr_type => |ptr_info| switch (ptr_info.flags.size) {
5433 .one, .many, .c => unreachable,
5434 .slice => switch (field_index) {
5435 0 => return .{ .field = .{ .identifier = "ptr" } },
5436 1 => return .{ .field = .{ .identifier = "len" } },
5437 else => unreachable,
5438 },
5439 },
5440 else => unreachable,
5441 }
5442}
5443
5444fn airStructFieldPtr(f: *Function, inst: Air.Inst.Index) !CValue {
5445 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
5446 const extra = f.air.extraData(Air.StructField, ty_pl.payload).data;
5447
5448 const container_ptr_val = try f.resolveInst(extra.struct_operand);
5449 try reap(f, inst, &.{extra.struct_operand});
5450 const container_ptr_ty = f.typeOf(extra.struct_operand);
5451 return fieldPtr(f, inst, container_ptr_ty, container_ptr_val, extra.field_index);
5452}
5453
5454fn airStructFieldPtrIndex(f: *Function, inst: Air.Inst.Index, index: u8) !CValue {
5455 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5456
5457 const container_ptr_val = try f.resolveInst(ty_op.operand);
5458 try reap(f, inst, &.{ty_op.operand});
5459 const container_ptr_ty = f.typeOf(ty_op.operand);
5460 return fieldPtr(f, inst, container_ptr_ty, container_ptr_val, index);
5461}
5462
5463fn airFieldParentPtr(f: *Function, inst: Air.Inst.Index) !CValue {
5464 const pt = f.dg.pt;
5465 const zcu = pt.zcu;
5466 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
5467 const extra = f.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
5468
5469 const container_ptr_ty = f.typeOfIndex(inst);
5470 const container_ty = container_ptr_ty.childType(zcu);
5471
5472 const field_ptr_ty = f.typeOf(extra.field_ptr);
5473 const field_ptr_val = try f.resolveInst(extra.field_ptr);
5474 try reap(f, inst, &.{extra.field_ptr});
5475
5476 const w = &f.code.writer;
5477 const local = try f.allocLocal(inst, container_ptr_ty);
5478 try f.writeCValue(w, local, .other);
5479 try w.writeAll(" = (");
5480 try f.renderType(w, container_ptr_ty);
5481 try w.writeByte(')');
5482
5483 switch (fieldLocation(container_ptr_ty, field_ptr_ty, extra.field_index, zcu)) {
5484 .begin => try f.writeCValue(w, field_ptr_val, .other),
5485 .field => |field| {
5486 const u8_ptr_ty = try pt.adjustPtrTypeChild(field_ptr_ty, .u8);
5487
5488 try w.writeAll("((");
5489 try f.renderType(w, u8_ptr_ty);
5490 try w.writeByte(')');
5491 try f.writeCValue(w, field_ptr_val, .other);
5492 try w.writeAll(" - offsetof(");
5493 try f.renderType(w, container_ty);
5494 try w.writeAll(", ");
5495 try f.writeCValue(w, field, .other);
5496 try w.writeAll("))");
5497 },
5498 .byte_offset => |byte_offset| {
5499 const u8_ptr_ty = try pt.adjustPtrTypeChild(field_ptr_ty, .u8);
5500
5501 try w.writeAll("((");
5502 try f.renderType(w, u8_ptr_ty);
5503 try w.writeByte(')');
5504 try f.writeCValue(w, field_ptr_val, .other);
5505 try w.print(" - {f})", .{
5506 try f.fmtIntLiteralDec(try pt.intValue(.usize, byte_offset)),
5507 });
5508 },
5509 }
5510
5511 try w.writeByte(';');
5512 try f.newline();
5513 return local;
5514}
5515
5516fn fieldPtr(
5517 f: *Function,
5518 inst: Air.Inst.Index,
5519 container_ptr_ty: Type,
5520 container_ptr_val: CValue,
5521 field_index: u32,
5522) !CValue {
5523 const pt = f.dg.pt;
5524 const zcu = pt.zcu;
5525 const field_ptr_ty = f.typeOfIndex(inst);
5526
5527 const w = &f.code.writer;
5528 const local = try f.allocLocal(inst, field_ptr_ty);
5529 try f.writeCValue(w, local, .other);
5530 try w.writeAll(" = (");
5531 try f.renderType(w, field_ptr_ty);
5532 try w.writeByte(')');
5533
5534 switch (fieldLocation(container_ptr_ty, field_ptr_ty, field_index, zcu)) {
5535 .begin => try f.writeCValue(w, container_ptr_val, .other),
5536 .field => |field| {
5537 try w.writeByte('&');
5538 try f.writeCValueDerefMember(w, container_ptr_val, field);
5539 },
5540 .byte_offset => |byte_offset| {
5541 const u8_ptr_ty = try pt.adjustPtrTypeChild(field_ptr_ty, .u8);
5542
5543 try w.writeAll("((");
5544 try f.renderType(w, u8_ptr_ty);
5545 try w.writeByte(')');
5546 try f.writeCValue(w, container_ptr_val, .other);
5547 try w.print(" + {f})", .{
5548 try f.fmtIntLiteralDec(try pt.intValue(.usize, byte_offset)),
5549 });
5550 },
5551 }
5552
5553 try w.writeByte(';');
5554 try f.newline();
5555 return local;
5556}
5557
5558fn airAggFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
5559 const pt = f.dg.pt;
5560 const zcu = pt.zcu;
5561 const ip = &zcu.intern_pool;
5562 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
5563 const extra = f.air.extraData(Air.StructField, ty_pl.payload).data;
5564
5565 const inst_ty = f.typeOfIndex(inst);
5566 assert(inst_ty.hasRuntimeBits(zcu));
5567
5568 const struct_byval = try f.resolveInst(extra.struct_operand);
5569 try reap(f, inst, &.{extra.struct_operand});
5570 const struct_ty = f.typeOf(extra.struct_operand);
5571 const w = &f.code.writer;
5572
5573 assert(struct_ty.containerLayout(zcu) != .@"packed"); // `Air.Legalize.Feature.expand_packed_agg_field_val` handles this case
5574 const field_name: CValue = switch (ip.indexToKey(struct_ty.toIntern())) {
5575 .struct_type => .{ .identifier = struct_ty.structFieldName(extra.field_index, zcu).unwrap().?.toSlice(ip) },
5576 .union_type => name: {
5577 const union_type = ip.loadUnionType(struct_ty.toIntern());
5578 const enum_tag_ty: Type = .fromInterned(union_type.enum_tag_type);
5579 const field_name_str = enum_tag_ty.enumFieldName(extra.field_index, zcu).toSlice(ip);
5580 break :name .{ .payload_identifier = field_name_str };
5581 },
5582 .tuple_type => .{ .field = extra.field_index },
5583 else => unreachable,
5584 };
5585
5586 const local = try f.allocLocal(inst, inst_ty);
5587 try f.writeCValue(w, local, .other);
5588 try w.writeAll(" = ");
5589 try f.writeCValueMember(w, struct_byval, field_name);
5590 try w.writeByte(';');
5591 try f.newline();
5592 return local;
5593}
5594
5595/// *(E!T) -> E
5596/// Note that the result is never a pointer.
5597fn airUnwrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
5598 const pt = f.dg.pt;
5599 const zcu = pt.zcu;
5600 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5601
5602 const inst_ty = f.typeOfIndex(inst);
5603 const operand = try f.resolveInst(ty_op.operand);
5604 const operand_ty = f.typeOf(ty_op.operand);
5605 try reap(f, inst, &.{ty_op.operand});
5606
5607 const operand_is_ptr = operand_ty.zigTypeTag(zcu) == .pointer;
5608 const local = try f.allocLocal(inst, inst_ty);
5609
5610 const w = &f.code.writer;
5611 try f.writeCValue(w, local, .other);
5612 try w.writeAll(" = ");
5613
5614 if (operand_is_ptr)
5615 try f.writeCValueDerefMember(w, operand, .{ .identifier = "error" })
5616 else
5617 try f.writeCValueMember(w, operand, .{ .identifier = "error" });
5618 try w.writeByte(';');
5619 try f.newline();
5620 return local;
5621}
5622
5623fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValue {
5624 const pt = f.dg.pt;
5625 const zcu = pt.zcu;
5626 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5627
5628 const inst_ty = f.typeOfIndex(inst);
5629 const operand = try f.resolveInst(ty_op.operand);
5630 try reap(f, inst, &.{ty_op.operand});
5631 const operand_ty = f.typeOf(ty_op.operand);
5632 const error_union_ty = if (is_ptr) operand_ty.childType(zcu) else operand_ty;
5633
5634 const w = &f.code.writer;
5635 if (!error_union_ty.errorUnionPayload(zcu).hasRuntimeBits(zcu)) {
5636 assert(is_ptr); // opv bug in sema
5637 const local = try f.allocLocal(inst, inst_ty);
5638 try f.writeCValue(w, local, .other);
5639 try w.writeAll(" = (");
5640 try f.renderType(w, inst_ty);
5641 try w.writeByte(')');
5642 try f.writeCValue(w, operand, .other);
5643 try w.writeByte(';');
5644 try f.newline();
5645 return local;
5646 }
5647
5648 const local = try f.allocLocal(inst, inst_ty);
5649 try f.writeCValue(w, local, .other);
5650 try w.writeAll(" = ");
5651 if (is_ptr) {
5652 try w.writeByte('&');
5653 try f.writeCValueDerefMember(w, operand, .{ .identifier = "payload" });
5654 } else try f.writeCValueMember(w, operand, .{ .identifier = "payload" });
5655 try w.writeByte(';');
5656 try f.newline();
5657 return local;
5658}
5659
5660fn airWrapOptional(f: *Function, inst: Air.Inst.Index) !CValue {
5661 const zcu = f.dg.pt.zcu;
5662 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5663
5664 const inst_ty = f.typeOfIndex(inst);
5665
5666 const operand = try f.resolveInst(ty_op.operand);
5667
5668 switch (CType.classifyOptional(inst_ty, zcu)) {
5669 .npv_payload => unreachable, // opv optional
5670
5671 .opv_payload => unreachable, // opv bug in Sema
5672
5673 .error_set,
5674 .ptr_like,
5675 .slice_like,
5676 => return f.moveCValue(inst, inst_ty, operand),
5677
5678 .@"struct" => {
5679 const w = &f.code.writer;
5680 const local = try f.allocLocal(inst, inst_ty);
5681
5682 try f.writeCValueMember(w, local, .{ .identifier = "is_null" });
5683 try w.writeAll(" = false;");
5684 try f.newline();
5685
5686 try f.writeCValueMember(w, local, .{ .identifier = "payload" });
5687 try w.writeAll(" = ");
5688 try f.writeCValue(w, operand, .other);
5689 try w.writeByte(';');
5690 try f.newline();
5691
5692 return local;
5693 },
5694 }
5695}
5696
5697fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
5698 const pt = f.dg.pt;
5699 const zcu = pt.zcu;
5700 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5701
5702 const inst_ty = f.typeOfIndex(inst);
5703 const payload_ty = inst_ty.errorUnionPayload(zcu);
5704 const err = try f.resolveInst(ty_op.operand);
5705 try reap(f, inst, &.{ty_op.operand});
5706
5707 const w = &f.code.writer;
5708 const local = try f.allocLocal(inst, inst_ty);
5709
5710 if (payload_ty.hasRuntimeBits(zcu)) {
5711 try f.writeCValueMember(w, local, .{ .identifier = "payload" });
5712 try w.writeAll(" = ");
5713 try f.dg.renderUndefValue(w, payload_ty, .other);
5714 try w.writeByte(';');
5715 try f.newline();
5716 }
5717
5718 try f.writeCValueMember(w, local, .{ .identifier = "error" });
5719 try w.writeAll(" = ");
5720 try f.writeCValue(w, err, .other);
5721 try w.writeByte(';');
5722 try f.newline();
5723
5724 return local;
5725}
5726
5727fn airErrUnionPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
5728 const pt = f.dg.pt;
5729 const w = &f.code.writer;
5730 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5731 const inst_ty = f.typeOfIndex(inst);
5732 const operand = try f.resolveInst(ty_op.operand);
5733
5734 const err_int_ty = try pt.errorIntType();
5735 const no_err = try pt.intValue(err_int_ty, 0);
5736 try reap(f, inst, &.{ty_op.operand});
5737
5738 // First, set the non-error value.
5739 try f.writeCValueDerefMember(w, operand, .{ .identifier = "error" });
5740 try w.print(" = {f};", .{try f.fmtIntLiteralDec(no_err)});
5741 try f.newline();
5742
5743 // Then return the payload pointer (only if it is used)
5744 if (f.liveness.isUnused(inst)) return .none;
5745
5746 const local = try f.allocLocal(inst, inst_ty);
5747 try f.writeCValue(w, local, .other);
5748 try w.writeAll(" = &");
5749 try f.writeCValueDerefMember(w, operand, .{ .identifier = "payload" });
5750 try w.writeByte(';');
5751 try f.newline();
5752 return local;
5753}
5754
5755fn airErrReturnTrace(f: *Function, inst: Air.Inst.Index) !CValue {
5756 _ = inst;
5757 return f.fail("TODO: C backend: implement airErrReturnTrace", .{});
5758}
5759
5760fn airSetErrReturnTrace(f: *Function, inst: Air.Inst.Index) !CValue {
5761 _ = inst;
5762 return f.fail("TODO: C backend: implement airSetErrReturnTrace", .{});
5763}
5764
5765fn airSaveErrReturnTraceIndex(f: *Function, inst: Air.Inst.Index) !CValue {
5766 _ = inst;
5767 return f.fail("TODO: C backend: implement airSaveErrReturnTraceIndex", .{});
5768}
5769
5770fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {
5771 const pt = f.dg.pt;
5772 const zcu = pt.zcu;
5773 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5774
5775 const inst_ty = f.typeOfIndex(inst);
5776 const payload_ty = inst_ty.errorUnionPayload(zcu);
5777 const payload = try f.resolveInst(ty_op.operand);
5778 assert(payload_ty.hasRuntimeBits(zcu));
5779 try reap(f, inst, &.{ty_op.operand});
5780
5781 const w = &f.code.writer;
5782 const local = try f.allocLocal(inst, inst_ty);
5783
5784 try f.writeCValueMember(w, local, .{ .identifier = "payload" });
5785 try w.writeAll(" = ");
5786 try f.writeCValue(w, payload, .other);
5787 try w.writeByte(';');
5788 try f.newline();
5789
5790 try f.writeCValueMember(w, local, .{ .identifier = "error" });
5791 try w.writeAll(" = ");
5792 try f.dg.renderValue(w, try pt.intValue(try pt.errorIntType(), 0), .other);
5793 try w.writeByte(';');
5794 try f.newline();
5795
5796 return local;
5797}
5798
5799fn airIsErr(f: *Function, inst: Air.Inst.Index, is_ptr: bool, operator: []const u8) !CValue {
5800 const pt = f.dg.pt;
5801 const un_op = f.air.instructions.items(.data)[@backingInt(inst)].un_op;
5802
5803 const w = &f.code.writer;
5804 const operand = try f.resolveInst(un_op);
5805 try reap(f, inst, &.{un_op});
5806 const local = try f.allocLocal(inst, .bool);
5807
5808 try f.writeCValue(w, local, .other);
5809 try w.writeAll(" = ");
5810 const err_int_ty = try pt.errorIntType();
5811 if (is_ptr)
5812 try f.writeCValueDerefMember(w, operand, .{ .identifier = "error" })
5813 else
5814 try f.writeCValueMember(w, operand, .{ .identifier = "error" });
5815 try w.print(" {s} ", .{operator});
5816 try f.dg.renderValue(w, try pt.intValue(err_int_ty, 0), .other);
5817 try w.writeByte(';');
5818 try f.newline();
5819 return local;
5820}
5821
5822fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue {
5823 const pt = f.dg.pt;
5824 const zcu = pt.zcu;
5825 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5826
5827 const operand = try f.resolveInst(ty_op.operand);
5828 try reap(f, inst, &.{ty_op.operand});
5829 const inst_ty = f.typeOfIndex(inst);
5830 const w = &f.code.writer;
5831 const local = try f.allocLocal(inst, inst_ty);
5832 const operand_ty = f.typeOf(ty_op.operand);
5833 const array_ty = operand_ty.childType(zcu);
5834
5835 // We have a `*[n]T`, which was turned into to a pointer to `struct { T array[n]; }`.
5836 // Ideally we would want to use 'operand->array' to convert to a `T *` (we get a `T []`
5837 // which decays to a pointer), but if the element type is zero-bit or the array length is
5838 // zero, there will not be an `array` member (the array type lowers to `void`). We cannot
5839 // check the type layout here because it may not be resolved, so in this instance, we must
5840 // use a pointer cast.
5841 try f.writeCValueMember(w, local, .{ .identifier = "ptr" });
5842 try w.writeAll(" = (");
5843 try f.dg.renderType(w, inst_ty.slicePtrFieldType(zcu));
5844 try w.writeByte(')');
5845 try f.writeCValue(w, operand, .other);
5846 try w.writeByte(';');
5847 try f.newline();
5848
5849 try f.writeCValueMember(w, local, .{ .identifier = "len" });
5850 try w.print(" = {f}", .{
5851 try f.fmtIntLiteralDec(try pt.intValue(.usize, array_ty.arrayLen(zcu))),
5852 });
5853 try w.writeByte(';');
5854 try f.newline();
5855
5856 return local;
5857}
5858
5859fn airFloatCast(f: *Function, inst: Air.Inst.Index) !CValue {
5860 const pt = f.dg.pt;
5861 const zcu = pt.zcu;
5862 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5863
5864 const inst_ty = f.typeOfIndex(inst);
5865 const inst_scalar_ty = inst_ty.scalarType(zcu);
5866 const operand = try f.resolveInst(ty_op.operand);
5867 try reap(f, inst, &.{ty_op.operand});
5868 const operand_ty = f.typeOf(ty_op.operand);
5869 const scalar_ty = operand_ty.scalarType(zcu);
5870 const target = &f.dg.mod.resolved_target.result;
5871 const operation = if (inst_scalar_ty.isRuntimeFloat() and scalar_ty.isRuntimeFloat())
5872 if (inst_scalar_ty.floatBits(target) < scalar_ty.floatBits(target)) "trunc" else "extend"
5873 else if (inst_scalar_ty.isInt(zcu) and scalar_ty.isRuntimeFloat())
5874 if (inst_scalar_ty.isSignedInt(zcu)) "fix" else "fixuns"
5875 else if (inst_scalar_ty.isRuntimeFloat() and scalar_ty.isInt(zcu))
5876 if (scalar_ty.isSignedInt(zcu)) "float" else "floatun"
5877 else
5878 unreachable;
5879
5880 const ref_ret = lowersToBigInt(inst_scalar_ty, zcu);
5881 const ref_operand = lowersToBigInt(scalar_ty, zcu);
5882
5883 const w = &f.code.writer;
5884 const local = try f.allocLocal(inst, inst_ty);
5885 const v = try Vectorize.start(f, inst, w, operand_ty);
5886 if (ref_ret) {
5887 const inst_int_info = inst_scalar_ty.intInfo(zcu);
5888 if (inst_int_info.bits <= 128) {
5889 try w.writeAll("zig_");
5890 try f.dg.renderTypeForBuiltinFnName(w, inst_scalar_ty);
5891 try w.print("_intCast_{c}{d}", .{
5892 @as(u8, switch (inst_int_info.signedness) {
5893 .signed => 'i',
5894 .unsigned => 'u',
5895 }),
5896 std.math.ceilPowerOfTwoAssert(u16, @max(inst_int_info.bits, 32)),
5897 });
5898 try w.writeAll("(&");
5899 try f.writeCValue(w, local, .other);
5900 try v.elem(f, w);
5901 try w.writeAll(", ");
5902 }
5903 } else {
5904 try f.writeCValue(w, local, .other);
5905 try v.elem(f, w);
5906 try w.writeAll(" = ");
5907 }
5908 if (inst_scalar_ty.isInt(zcu) and scalar_ty.isRuntimeFloat()) {
5909 const inst_int_info = inst_scalar_ty.intInfo(zcu);
5910 if (inst_int_info.bits <= 128) try w.print("zig_{c}{d}_truncate_{[0]c}{[1]d}(", .{
5911 @as(u8, switch (inst_int_info.signedness) {
5912 .signed => 'i',
5913 .unsigned => 'u',
5914 }),
5915 std.math.ceilPowerOfTwoAssert(u16, @max(inst_int_info.bits, 32)),
5916 });
5917 }
5918 try w.writeAll("zig_");
5919 try w.writeAll(operation);
5920 try w.writeAll(compilerRtAbbrev(scalar_ty, zcu, target));
5921 try w.writeAll(compilerRtAbbrev(inst_scalar_ty, zcu, target));
5922 try w.writeByte('(');
5923 if (ref_ret) {
5924 const inst_int_info = inst_scalar_ty.intInfo(zcu);
5925 if (inst_int_info.bits > 128) {
5926 try w.writeByte('&');
5927 try f.writeCValue(w, local, .other);
5928 try v.elem(f, w);
5929 try w.writeAll(", ");
5930 }
5931 }
5932 if (ref_operand) {
5933 const operand_int_info = scalar_ty.intInfo(zcu);
5934 if (operand_int_info.bits <= 128) {
5935 try w.print("zig_{c}{d}_intCast_", .{
5936 @as(u8, switch (operand_int_info.signedness) {
5937 .signed => 'i',
5938 .unsigned => 'u',
5939 }),
5940 std.math.ceilPowerOfTwoAssert(u16, @max(operand_int_info.bits, 32)),
5941 });
5942 try f.dg.renderTypeForBuiltinFnName(w, scalar_ty);
5943 try w.writeAll("(&");
5944 switch (operand) {
5945 .constant => |val| try f.dg.renderValueAsLvalue(w, val),
5946 else => try f.writeCValue(w, operand, .other),
5947 }
5948 try v.elem(f, w);
5949 try f.dg.renderBuiltinInfo(w, scalar_ty, .none);
5950 try w.writeByte(')');
5951 } else {
5952 try w.writeByte('&');
5953 switch (operand) {
5954 .constant => |val| try f.dg.renderValueAsLvalue(w, val),
5955 else => try f.writeCValue(w, operand, .other),
5956 }
5957 try v.elem(f, w);
5958 try w.print(", {f}", .{fmtUnsignedIntLiteralSmall(
5959 target,
5960 .uint16_t,
5961 operand_int_info.bits,
5962 false,
5963 10,
5964 .lower,
5965 )});
5966 }
5967 } else {
5968 try f.writeCValue(w, operand, .other);
5969 try v.elem(f, w);
5970 }
5971 if (ref_ret) {
5972 const inst_int_info = inst_scalar_ty.intInfo(zcu);
5973 if (inst_int_info.bits > 128) try w.print(", {f}", .{fmtUnsignedIntLiteralSmall(
5974 target,
5975 .uint16_t,
5976 inst_int_info.bits,
5977 false,
5978 10,
5979 .lower,
5980 )});
5981 }
5982 try w.writeByte(')');
5983 if (inst_scalar_ty.isInt(zcu) and scalar_ty.isRuntimeFloat()) {
5984 const inst_int_info = inst_scalar_ty.intInfo(zcu);
5985 if (inst_int_info.bits <= 128) {
5986 try w.print(", {f}", .{
5987 try f.dg.fmtIntLiteralDec(try pt.intValue(.u8, inst_int_info.bits), .other),
5988 });
5989 try w.writeByte(')');
5990 }
5991 }
5992 if (ref_ret) {
5993 const inst_int_info = inst_scalar_ty.intInfo(zcu);
5994 if (inst_int_info.bits <= 128) {
5995 try f.dg.renderBuiltinInfo(w, inst_scalar_ty, .none);
5996 try w.writeByte(')');
5997 }
5998 }
5999 try w.writeByte(';');
6000 try f.newline();
6001 try v.end(f, inst, w);
6002
6003 return local;
6004}
6005
6006fn airUnBuiltinCall(
6007 f: *Function,
6008 inst: Air.Inst.Index,
6009 operand_ref: Air.Inst.Ref,
6010 operation: []const u8,
6011 info: BuiltinInfo,
6012) !CValue {
6013 const pt = f.dg.pt;
6014 const zcu = pt.zcu;
6015
6016 const inst_ty = f.typeOfIndex(inst);
6017 const inst_scalar_ty = inst_ty.scalarType(zcu);
6018 const operand_ty = f.typeOf(operand_ref);
6019 const scalar_ty = operand_ty.scalarType(zcu);
6020 const is_big = lowersToBigInt(operand_ty, zcu);
6021
6022 const operand = try f.resolveInst(operand_ref);
6023 if (!is_big) try reap(f, inst, &.{operand_ref});
6024
6025 const ref_ret = lowersToBigInt(inst_scalar_ty, zcu);
6026 const ref_arg = lowersToBigInt(scalar_ty, zcu);
6027
6028 const w = &f.code.writer;
6029 const local = try f.allocLocal(inst, inst_ty);
6030 if (is_big) try reap(f, inst, &.{operand_ref});
6031 const v = try Vectorize.start(f, inst, w, operand_ty);
6032 if (!ref_ret) {
6033 try f.writeCValue(w, local, .other);
6034 try v.elem(f, w);
6035 try w.writeAll(" = ");
6036 }
6037 try w.print("zig_{s}_", .{operation});
6038 try f.dg.renderTypeForBuiltinFnName(w, scalar_ty);
6039 try w.writeByte('(');
6040 if (ref_ret) {
6041 try w.writeByte('&');
6042 try f.writeCValue(w, local, .other);
6043 try v.elem(f, w);
6044 try w.writeAll(", ");
6045 }
6046 if (ref_arg) {
6047 try w.writeByte('&');
6048 switch (operand) {
6049 .constant => |val| try f.dg.renderValueAsLvalue(w, val),
6050 else => try f.writeCValue(w, operand, .other),
6051 }
6052 } else try f.writeCValue(w, operand, .other);
6053 try v.elem(f, w);
6054 try f.dg.renderBuiltinInfo(w, scalar_ty, info);
6055 try w.writeAll(");");
6056 try f.newline();
6057 try v.end(f, inst, w);
6058
6059 return local;
6060}
6061
6062fn airBinBuiltinCall(
6063 f: *Function,
6064 inst: Air.Inst.Index,
6065 operation: []const u8,
6066 info: BuiltinInfo,
6067) !CValue {
6068 const pt = f.dg.pt;
6069 const zcu = pt.zcu;
6070 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
6071
6072 const lhs_ty = f.typeOf(bin_op.lhs);
6073 const rhs_ty = f.typeOf(bin_op.rhs);
6074 const is_big = lowersToBigInt(lhs_ty, zcu);
6075
6076 const lhs = try f.resolveInst(bin_op.lhs);
6077 const rhs = try f.resolveInst(bin_op.rhs);
6078 if (!is_big) try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
6079
6080 const inst_ty = f.typeOfIndex(inst);
6081 const inst_scalar_ty = inst_ty.scalarType(zcu);
6082 const lhs_scalar_ty = lhs_ty.scalarType(zcu);
6083 const rhs_scalar_ty = rhs_ty.scalarType(zcu);
6084
6085 const ref_ret = lowersToBigInt(inst_scalar_ty, zcu);
6086 const ref_lhs = lowersToBigInt(lhs_scalar_ty, zcu);
6087 const ref_rhs = lowersToBigInt(rhs_scalar_ty, zcu);
6088
6089 const w = &f.code.writer;
6090 const local = try f.allocLocal(inst, inst_ty);
6091 if (is_big) try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
6092 const v = try Vectorize.start(f, inst, w, lhs_ty);
6093 if (!ref_ret) {
6094 try f.writeCValue(w, local, .other);
6095 try v.elem(f, w);
6096 try w.writeAll(" = ");
6097 }
6098 try w.print("zig_{s}_", .{operation});
6099 try f.dg.renderTypeForBuiltinFnName(w, lhs_scalar_ty);
6100 switch (info) {
6101 .bits, .none, .big_temp_bits => {},
6102 .bits_none => {
6103 try w.writeByte('_');
6104 try f.dg.renderTypeForBuiltinFnName(w, rhs_scalar_ty);
6105 },
6106 }
6107 try w.writeByte('(');
6108 if (ref_ret) {
6109 try w.writeByte('&');
6110 try f.writeCValue(w, local, .other);
6111 try v.elem(f, w);
6112 try w.writeAll(", ");
6113 }
6114 if (ref_lhs) {
6115 try w.writeByte('&');
6116 switch (lhs) {
6117 .constant => |lhs_val| try f.dg.renderValueAsLvalue(w, lhs_val),
6118 else => try f.writeCValue(w, lhs, .other),
6119 }
6120 } else try f.writeCValue(w, lhs, .other);
6121 try v.elem(f, w);
6122 try w.writeAll(", ");
6123 if (ref_rhs) {
6124 try w.writeByte('&');
6125 switch (rhs) {
6126 .constant => |rhs_val| try f.dg.renderValueAsLvalue(w, rhs_val),
6127 else => try f.writeCValue(w, rhs, .other),
6128 }
6129 } else try f.writeCValue(w, rhs, .other);
6130 try v.elem(f, w);
6131 try f.dg.renderBuiltinInfo(w, lhs_scalar_ty, info: switch (info) {
6132 .none => .none,
6133 .bits, .bits_none => .bits,
6134 .big_temp_bits => {
6135 if (lowersToBigInt(lhs_scalar_ty, zcu)) {
6136 const temp_local = try f.allocAlignedLocal(inst, .{
6137 .type = lhs_scalar_ty,
6138 .array_len = 2,
6139 });
6140 try w.writeAll(", &");
6141 try f.writeCValue(w, temp_local, .other);
6142 try freeLocal(f, inst, temp_local.new_local, null);
6143 }
6144 break :info .none;
6145 },
6146 });
6147 switch (info) {
6148 .none, .bits, .big_temp_bits => {},
6149 .bits_none => try f.dg.renderBuiltinInfo(w, rhs_scalar_ty, .none),
6150 }
6151 try w.writeAll(");");
6152 try f.newline();
6153 try v.end(f, inst, w);
6154
6155 return local;
6156}
6157
6158fn airCmpBuiltinCall(
6159 f: *Function,
6160 inst: Air.Inst.Index,
6161 data: anytype,
6162 operator: std.math.CompareOperator,
6163 operation: enum { cmp, operator },
6164 info: BuiltinInfo,
6165) !CValue {
6166 const pt = f.dg.pt;
6167 const zcu = pt.zcu;
6168 const lhs = try f.resolveInst(data.lhs);
6169 const rhs = try f.resolveInst(data.rhs);
6170 try reap(f, inst, &.{ data.lhs, data.rhs });
6171
6172 const inst_ty = f.typeOfIndex(inst);
6173 const inst_scalar_ty = inst_ty.scalarType(zcu);
6174 const operand_ty = f.typeOf(data.lhs);
6175 const scalar_ty = operand_ty.scalarType(zcu);
6176
6177 const ref_ret = lowersToBigInt(inst_scalar_ty, zcu);
6178 const ref_arg = lowersToBigInt(scalar_ty, zcu);
6179
6180 const w = &f.code.writer;
6181 const local = try f.allocLocal(inst, inst_ty);
6182 const v = try Vectorize.start(f, inst, w, operand_ty);
6183 if (!ref_ret) {
6184 try f.writeCValue(w, local, .other);
6185 try v.elem(f, w);
6186 try w.writeAll(" = ");
6187 }
6188 try w.print("zig_{s}_", .{switch (operation) {
6189 else => @tagName(operation),
6190 .operator => compareOperatorAbbrev(operator),
6191 }});
6192 try f.dg.renderTypeForBuiltinFnName(w, scalar_ty);
6193 try w.writeByte('(');
6194 if (ref_ret) {
6195 try w.writeByte('&');
6196 try f.writeCValue(w, local, .other);
6197 try v.elem(f, w);
6198 try w.writeAll(", ");
6199 }
6200 if (ref_arg) {
6201 try w.writeByte('&');
6202 switch (lhs) {
6203 .constant => |lhs_val| try f.dg.renderValueAsLvalue(w, lhs_val),
6204 else => try f.writeCValue(w, lhs, .other),
6205 }
6206 } else try f.writeCValue(w, lhs, .other);
6207 try v.elem(f, w);
6208 try w.writeAll(", ");
6209 if (ref_arg) {
6210 try w.writeByte('&');
6211 switch (rhs) {
6212 .constant => |rhs_val| try f.dg.renderValueAsLvalue(w, rhs_val),
6213 else => try f.writeCValue(w, rhs, .other),
6214 }
6215 } else try f.writeCValue(w, rhs, .other);
6216 try v.elem(f, w);
6217 try f.dg.renderBuiltinInfo(w, scalar_ty, info);
6218 try w.writeByte(')');
6219 if (!ref_ret) try w.print("{s}{f}", .{
6220 compareOperatorC(operator),
6221 try f.fmtIntLiteralDec(try pt.intValue(.i32, 0)),
6222 });
6223 try w.writeByte(';');
6224 try f.newline();
6225 try v.end(f, inst, w);
6226
6227 return local;
6228}
6229
6230fn airCmpxchg(f: *Function, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue {
6231 const pt = f.dg.pt;
6232 const zcu = pt.zcu;
6233 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
6234 const extra = f.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
6235 const inst_ty = f.typeOfIndex(inst);
6236 const ptr = try f.resolveInst(extra.ptr);
6237 const expected_value = try f.resolveInst(extra.expected_value);
6238 const new_value = try f.resolveInst(extra.new_value);
6239 const ptr_ty = f.typeOf(extra.ptr);
6240 const ty = ptr_ty.childType(zcu);
6241
6242 const w = &f.code.writer;
6243 const new_value_mat = try Materialize.start(f, inst, ty, new_value);
6244 try reap(f, inst, &.{ extra.ptr, extra.expected_value, extra.new_value });
6245
6246 const repr_ty = if (ty.isRuntimeFloat())
6247 pt.intType(.unsigned, @as(u16, @intCast(ty.abiSize(zcu) * 8))) catch unreachable
6248 else
6249 ty;
6250
6251 const local = try f.allocLocal(inst, inst_ty);
6252 if (inst_ty.isPtrLikeOptional(zcu)) {
6253 try f.writeCValue(w, local, .other);
6254 try w.writeAll(" = ");
6255 try f.writeCValue(w, expected_value, .other);
6256 try w.writeByte(';');
6257 try f.newline();
6258
6259 try w.writeAll("if (");
6260 try w.print("zig_cmpxchg_{s}((zig_atomic(", .{flavor});
6261 try f.renderType(w, ty);
6262 try w.writeByte(')');
6263 if (ptr_ty.isVolatilePtr(zcu)) try w.writeAll(" volatile");
6264 try w.writeAll(" *)");
6265 try f.writeCValue(w, ptr, .other);
6266 try w.writeAll(", ");
6267 try f.writeCValue(w, local, .other);
6268 try w.writeAll(", ");
6269 try new_value_mat.mat(f, w);
6270 try w.writeAll(", ");
6271 try writeMemoryOrder(w, extra.successOrder());
6272 try w.writeAll(", ");
6273 try writeMemoryOrder(w, extra.failureOrder());
6274 try w.writeAll(", ");
6275 try f.dg.renderTypeForBuiltinFnName(w, ty);
6276 try w.writeAll(", ");
6277 try f.renderType(w, repr_ty);
6278 try w.writeByte(')');
6279 try w.writeAll(") {");
6280 f.indent();
6281 try f.newline();
6282
6283 try f.writeCValue(w, local, .other);
6284 try w.writeAll(" = NULL;");
6285 try f.newline();
6286
6287 try f.outdent();
6288 try w.writeByte('}');
6289 try f.newline();
6290 } else {
6291 try f.writeCValueMember(w, local, .{ .identifier = "payload" });
6292 try w.writeAll(" = ");
6293 try f.writeCValue(w, expected_value, .other);
6294 try w.writeByte(';');
6295 try f.newline();
6296
6297 try f.writeCValueMember(w, local, .{ .identifier = "is_null" });
6298 try w.print(" = zig_cmpxchg_{s}((zig_atomic(", .{flavor});
6299 try f.renderType(w, ty);
6300 try w.writeByte(')');
6301 if (ptr_ty.isVolatilePtr(zcu)) try w.writeAll(" volatile");
6302 try w.writeAll(" *)");
6303 try f.writeCValue(w, ptr, .other);
6304 try w.writeAll(", ");
6305 try f.writeCValueMember(w, local, .{ .identifier = "payload" });
6306 try w.writeAll(", ");
6307 try new_value_mat.mat(f, w);
6308 try w.writeAll(", ");
6309 try writeMemoryOrder(w, extra.successOrder());
6310 try w.writeAll(", ");
6311 try writeMemoryOrder(w, extra.failureOrder());
6312 try w.writeAll(", ");
6313 try f.dg.renderTypeForBuiltinFnName(w, ty);
6314 try w.writeAll(", ");
6315 try f.renderType(w, repr_ty);
6316 try w.writeAll(");");
6317 try f.newline();
6318 }
6319 try new_value_mat.end(f, inst);
6320
6321 if (f.liveness.isUnused(inst)) {
6322 try freeLocal(f, inst, local.new_local, null);
6323 return .none;
6324 }
6325
6326 return local;
6327}
6328
6329fn airAtomicRmw(f: *Function, inst: Air.Inst.Index) !CValue {
6330 const pt = f.dg.pt;
6331 const zcu = pt.zcu;
6332 const pl_op = f.air.instructions.items(.data)[@backingInt(inst)].pl_op;
6333 const extra = f.air.extraData(Air.AtomicRmw, pl_op.payload).data;
6334 const inst_ty = f.typeOfIndex(inst);
6335 const ptr_ty = f.typeOf(pl_op.operand);
6336 const ty = ptr_ty.childType(zcu);
6337 const ptr = try f.resolveInst(pl_op.operand);
6338 const operand = try f.resolveInst(extra.operand);
6339
6340 const w = &f.code.writer;
6341 const operand_mat = try Materialize.start(f, inst, ty, operand);
6342 try reap(f, inst, &.{ pl_op.operand, extra.operand });
6343
6344 const repr_bits: u16 = @intCast(ty.abiSize(zcu) * 8);
6345 const is_float = ty.isRuntimeFloat();
6346 const is_128 = repr_bits == 128;
6347 const repr_ty = if (is_float) pt.intType(.unsigned, repr_bits) catch unreachable else ty;
6348
6349 const local = try f.allocLocal(inst, inst_ty);
6350 try w.print("zig_atomicrmw_{s}", .{toAtomicRmwSuffix(extra.op())});
6351 if (is_float) try w.writeAll("_float") else if (is_128) try w.writeAll("_int128");
6352 try w.writeByte('(');
6353 try f.writeCValue(w, local, .other);
6354 try w.writeAll(", (");
6355 const use_atomic = switch (extra.op()) {
6356 else => true,
6357 // These are missing from stdatomic.h, so no atomic types unless a fallback is used.
6358 .Nand, .Min, .Max => is_float or is_128,
6359 };
6360 if (use_atomic) try w.writeAll("zig_atomic(");
6361 try f.renderType(w, ty);
6362 if (use_atomic) try w.writeByte(')');
6363 if (ptr_ty.isVolatilePtr(zcu)) try w.writeAll(" volatile");
6364 try w.writeAll(" *)");
6365 try f.writeCValue(w, ptr, .other);
6366 try w.writeAll(", ");
6367 try operand_mat.mat(f, w);
6368 try w.writeAll(", ");
6369 try writeMemoryOrder(w, extra.ordering());
6370 try w.writeAll(", ");
6371 try f.dg.renderTypeForBuiltinFnName(w, ty);
6372 try w.writeAll(", ");
6373 try f.renderType(w, repr_ty);
6374 try w.writeAll(");");
6375 try f.newline();
6376 try operand_mat.end(f, inst);
6377
6378 if (f.liveness.isUnused(inst)) {
6379 try freeLocal(f, inst, local.new_local, null);
6380 return .none;
6381 }
6382
6383 return local;
6384}
6385
6386fn airAtomicLoad(f: *Function, inst: Air.Inst.Index) !CValue {
6387 const pt = f.dg.pt;
6388 const zcu = pt.zcu;
6389 const atomic_load = f.air.instructions.items(.data)[@backingInt(inst)].atomic_load;
6390 const ptr = try f.resolveInst(atomic_load.ptr);
6391 try reap(f, inst, &.{atomic_load.ptr});
6392 const ptr_ty = f.typeOf(atomic_load.ptr);
6393 const ty = ptr_ty.childType(zcu);
6394
6395 const repr_ty = if (ty.isRuntimeFloat())
6396 pt.intType(.unsigned, @as(u16, @intCast(ty.abiSize(zcu) * 8))) catch unreachable
6397 else
6398 ty;
6399
6400 const inst_ty = f.typeOfIndex(inst);
6401 const w = &f.code.writer;
6402 const local = try f.allocLocal(inst, inst_ty);
6403
6404 try w.writeAll("zig_atomic_load(");
6405 try f.writeCValue(w, local, .other);
6406 try w.writeAll(", (zig_atomic(");
6407 try f.renderType(w, ty);
6408 try w.writeByte(')');
6409 if (ptr_ty.isVolatilePtr(zcu)) try w.writeAll(" volatile");
6410 try w.writeAll(" *)");
6411 try f.writeCValue(w, ptr, .other);
6412 try w.writeAll(", ");
6413 try writeMemoryOrder(w, atomic_load.order);
6414 try w.writeAll(", ");
6415 try f.dg.renderTypeForBuiltinFnName(w, ty);
6416 try w.writeAll(", ");
6417 try f.renderType(w, repr_ty);
6418 try w.writeAll(");");
6419 try f.newline();
6420
6421 return local;
6422}
6423
6424fn airAtomicStore(f: *Function, inst: Air.Inst.Index, order: [*:0]const u8) !CValue {
6425 const pt = f.dg.pt;
6426 const zcu = pt.zcu;
6427 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
6428 const ptr_ty = f.typeOf(bin_op.lhs);
6429 const ty = ptr_ty.childType(zcu);
6430 const ptr = try f.resolveInst(bin_op.lhs);
6431 const element = try f.resolveInst(bin_op.rhs);
6432
6433 const w = &f.code.writer;
6434 const element_mat = try Materialize.start(f, inst, ty, element);
6435 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
6436
6437 const repr_ty = if (ty.isRuntimeFloat())
6438 pt.intType(.unsigned, @as(u16, @intCast(ty.abiSize(zcu) * 8))) catch unreachable
6439 else
6440 ty;
6441
6442 try w.writeAll("zig_atomic_store((zig_atomic(");
6443 try f.renderType(w, ty);
6444 try w.writeByte(')');
6445 if (ptr_ty.isVolatilePtr(zcu)) try w.writeAll(" volatile");
6446 try w.writeAll(" *)");
6447 try f.writeCValue(w, ptr, .other);
6448 try w.writeAll(", ");
6449 try element_mat.mat(f, w);
6450 try w.print(", {s}, ", .{order});
6451 try f.dg.renderTypeForBuiltinFnName(w, ty);
6452 try w.writeAll(", ");
6453 try f.renderType(w, repr_ty);
6454 try w.writeAll(");");
6455 try f.newline();
6456 try element_mat.end(f, inst);
6457
6458 return .none;
6459}
6460
6461fn airMemset(f: *Function, inst: Air.Inst.Index, safety: bool) !CValue {
6462 const pt = f.dg.pt;
6463 const zcu = pt.zcu;
6464 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
6465 const dest_ty = f.typeOf(bin_op.lhs);
6466 const dest_slice = try f.resolveInst(bin_op.lhs);
6467 const value = try f.resolveInst(bin_op.rhs);
6468 const elem_ty = f.typeOf(bin_op.rhs);
6469 const elem_abi_size = elem_ty.abiSize(zcu);
6470 const val_is_undef = if (bin_op.rhs.toInterned()) |ip_index| Value.fromInterned(ip_index).isUndef(zcu) else false;
6471 const w = &f.code.writer;
6472
6473 if (val_is_undef) {
6474 if (!safety) {
6475 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
6476 return .none;
6477 }
6478
6479 try w.writeAll("memset(");
6480 switch (dest_ty.ptrSize(zcu)) {
6481 .slice => {
6482 try f.writeCValueMember(w, dest_slice, .{ .identifier = "ptr" });
6483 try w.writeAll(", 0xaa, ");
6484 try f.writeCValueMember(w, dest_slice, .{ .identifier = "len" });
6485 },
6486 .one => {
6487 try f.writeCValue(w, dest_slice, .other);
6488 try w.print(", 0xaa, {d}", .{dest_ty.childType(zcu).arrayLen(zcu)});
6489 },
6490 .many, .c => unreachable,
6491 }
6492 if (elem_abi_size > 0) try w.print(" * {d}", .{elem_abi_size});
6493 try w.writeAll(");");
6494 try f.newline();
6495 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
6496 return .none;
6497 }
6498
6499 if (elem_abi_size == 1 and elem_ty.isAbiInt(zcu) and !dest_ty.isVolatilePtr(zcu)) {
6500 try w.writeAll("memset(");
6501 switch (dest_ty.ptrSize(zcu)) {
6502 .slice => {
6503 try f.writeCValueMember(w, dest_slice, .{ .identifier = "ptr" });
6504 try w.writeAll(", *(const char *)&");
6505 switch (value) {
6506 .constant => |v| try f.dg.renderValueAsLvalue(w, v),
6507 else => try f.writeCValue(w, value, .other),
6508 }
6509 try w.writeAll(", ");
6510 try f.writeCValueMember(w, dest_slice, .{ .identifier = "len" });
6511 },
6512 .one => {
6513 try f.writeCValue(w, dest_slice, .other);
6514 try w.writeAll(", *(const char *)&");
6515 switch (value) {
6516 .constant => |v| try f.dg.renderValueAsLvalue(w, v),
6517 else => try f.writeCValue(w, value, .other),
6518 }
6519 try w.print(", {d}", .{dest_ty.childType(zcu).arrayLen(zcu)});
6520 },
6521 .many, .c => unreachable,
6522 }
6523 try w.writeAll(");");
6524 try f.newline();
6525 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
6526 return .none;
6527 }
6528
6529 // Fallback path: use a `for` loop.
6530
6531 const index = try f.allocLocal(inst, .usize);
6532
6533 try w.writeAll("for (");
6534 try f.writeCValue(w, index, .other);
6535 try w.writeAll(" = ");
6536 try f.dg.renderValue(w, .zero_usize, .other);
6537 try w.writeAll("; ");
6538 try f.writeCValue(w, index, .other);
6539 try w.writeAll(" != ");
6540 switch (dest_ty.ptrSize(zcu)) {
6541 .slice => try f.writeCValueMember(w, dest_slice, .{ .identifier = "len" }),
6542 .one => try w.print("{d}", .{dest_ty.childType(zcu).arrayLen(zcu)}),
6543 .many, .c => unreachable,
6544 }
6545 try w.writeAll("; ++");
6546 try f.writeCValue(w, index, .other);
6547 try w.writeAll(") ");
6548
6549 switch (dest_ty.ptrSize(zcu)) {
6550 .slice => try f.writeCValueMember(w, dest_slice, .{ .identifier = "ptr" }),
6551 .one => try f.writeCValueDerefMember(w, dest_slice, .{ .identifier = "array" }),
6552 .many, .c => unreachable,
6553 }
6554 try w.writeByte('[');
6555 try f.writeCValue(w, index, .other);
6556 try w.writeAll("] = ");
6557 try f.writeCValue(w, value, .other);
6558 try w.writeByte(';');
6559 try f.newline();
6560
6561 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
6562 try freeLocal(f, inst, index.new_local, null);
6563
6564 return .none;
6565}
6566
6567fn airMemcpy(f: *Function, inst: Air.Inst.Index, function_paren: []const u8) !CValue {
6568 const pt = f.dg.pt;
6569 const zcu = pt.zcu;
6570 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
6571 const dest_ptr = try f.resolveInst(bin_op.lhs);
6572 const src_ptr = try f.resolveInst(bin_op.rhs);
6573 const dest_ty = f.typeOf(bin_op.lhs);
6574 const src_ty = f.typeOf(bin_op.rhs);
6575 const w = &f.code.writer;
6576
6577 if (dest_ty.ptrSize(zcu) != .one) {
6578 try w.writeAll("if (");
6579 try f.writeCValueMember(w, dest_ptr, .{ .identifier = "len" });
6580 try w.writeAll(" != 0) ");
6581 }
6582 try w.writeAll(function_paren);
6583 switch (dest_ty.ptrSize(zcu)) {
6584 .slice => try f.writeCValueMember(w, dest_ptr, .{ .identifier = "ptr" }),
6585 .one => try f.writeCValueDerefMember(w, dest_ptr, .{ .identifier = "array" }),
6586 .many, .c => unreachable,
6587 }
6588 try w.writeAll(", ");
6589 switch (src_ty.ptrSize(zcu)) {
6590 .slice => try f.writeCValueMember(w, src_ptr, .{ .identifier = "ptr" }),
6591 .one => try f.writeCValueDerefMember(w, src_ptr, .{ .identifier = "array" }),
6592 .many, .c => try f.writeCValue(w, src_ptr, .other),
6593 }
6594 try w.writeAll(", ");
6595 switch (dest_ty.ptrSize(zcu)) {
6596 .slice => try f.writeCValueMember(w, dest_ptr, .{ .identifier = "len" }),
6597 .one => try w.print("{d}", .{dest_ty.childType(zcu).arrayLen(zcu)}),
6598 .many, .c => unreachable,
6599 }
6600 try w.writeAll(" * sizeof(");
6601 try f.renderType(w, dest_ty.indexableElem(zcu));
6602 try w.writeAll("));");
6603 try f.newline();
6604
6605 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
6606 return .none;
6607}
6608
6609fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
6610 const pt = f.dg.pt;
6611 const zcu = pt.zcu;
6612 const bin_op = f.air.instructions.items(.data)[@backingInt(inst)].bin_op;
6613 const union_ptr = try f.resolveInst(bin_op.lhs);
6614 const new_tag = try f.resolveInst(bin_op.rhs);
6615 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
6616
6617 const union_ty = f.typeOf(bin_op.lhs).childType(zcu);
6618 const layout = union_ty.unionGetLayout(zcu);
6619 if (layout.tag_size == 0) return .none;
6620
6621 const w = &f.code.writer;
6622 try f.writeCValueDerefMember(w, union_ptr, .{ .identifier = "tag" });
6623 try w.writeAll(" = ");
6624 try f.writeCValue(w, new_tag, .other);
6625 try w.writeByte(';');
6626 try f.newline();
6627 return .none;
6628}
6629
6630fn airGetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
6631 const pt = f.dg.pt;
6632 const zcu = pt.zcu;
6633 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
6634
6635 const operand = try f.resolveInst(ty_op.operand);
6636 try reap(f, inst, &.{ty_op.operand});
6637
6638 const union_ty = f.typeOf(ty_op.operand);
6639 const layout = union_ty.unionGetLayout(zcu);
6640 if (layout.tag_size == 0) return .none;
6641
6642 const inst_ty = f.typeOfIndex(inst);
6643 const w = &f.code.writer;
6644 const local = try f.allocLocal(inst, inst_ty);
6645 try f.writeCValue(w, local, .other);
6646 try w.writeAll(" = ");
6647 try f.writeCValueMember(w, operand, .{ .identifier = "tag" });
6648 try w.writeByte(';');
6649 try f.newline();
6650 return local;
6651}
6652
6653fn airTagName(f: *Function, inst: Air.Inst.Index) !CValue {
6654 const zcu = f.dg.pt.zcu;
6655 const ip = &zcu.intern_pool;
6656 const gpa = zcu.comp.gpa;
6657 const un_op = f.air.instructions.items(.data)[@backingInt(inst)].un_op;
6658
6659 const inst_ty = f.typeOfIndex(inst);
6660 const enum_ty = f.typeOf(un_op);
6661 const operand = try f.resolveInst(un_op);
6662 try reap(f, inst, &.{un_op});
6663
6664 const w = &f.code.writer;
6665 const local = try f.allocLocal(inst, inst_ty);
6666 try f.writeCValue(w, local, .other);
6667 try f.need_tag_name_funcs.put(gpa, enum_ty.toIntern(), {});
6668 try w.print(" = zig_tagName_{f}__{d}(", .{
6669 fmtIdentUnsolo(enum_ty.containerTypeName(ip).toSlice(ip)),
6670 @backingInt(enum_ty.toIntern()),
6671 });
6672 try f.writeCValue(w, operand, .other);
6673 try w.writeAll(");");
6674 try f.newline();
6675
6676 return local;
6677}
6678
6679fn airErrorName(f: *Function, inst: Air.Inst.Index) !CValue {
6680 const un_op = f.air.instructions.items(.data)[@backingInt(inst)].un_op;
6681
6682 const w = &f.code.writer;
6683 const inst_ty = f.typeOfIndex(inst);
6684 const operand = try f.resolveInst(un_op);
6685 try reap(f, inst, &.{un_op});
6686 const local = try f.allocLocal(inst, inst_ty);
6687 try f.writeCValue(w, local, .other);
6688
6689 try w.writeAll(" = zig_errorName[");
6690 try f.writeCValue(w, operand, .other);
6691 try w.writeAll(" - 1];");
6692 try f.newline();
6693 return local;
6694}
6695
6696fn airSplat(f: *Function, inst: Air.Inst.Index) !CValue {
6697 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
6698
6699 const operand = try f.resolveInst(ty_op.operand);
6700 try reap(f, inst, &.{ty_op.operand});
6701
6702 const inst_ty = f.typeOfIndex(inst);
6703
6704 const w = &f.code.writer;
6705 const local = try f.allocLocal(inst, inst_ty);
6706 const v = try Vectorize.start(f, inst, w, inst_ty);
6707 try f.writeCValue(w, local, .other);
6708 try v.elem(f, w);
6709 try w.writeAll(" = ");
6710 try f.writeCValue(w, operand, .other);
6711 try w.writeByte(';');
6712 try f.newline();
6713 try v.end(f, inst, w);
6714
6715 return local;
6716}
6717
6718fn airSelect(f: *Function, inst: Air.Inst.Index) !CValue {
6719 const pl_op = f.air.instructions.items(.data)[@backingInt(inst)].pl_op;
6720 const extra = f.air.extraData(Air.Bin, pl_op.payload).data;
6721
6722 const pred = try f.resolveInst(pl_op.operand);
6723 const lhs = try f.resolveInst(extra.lhs);
6724 const rhs = try f.resolveInst(extra.rhs);
6725 try reap(f, inst, &.{ pl_op.operand, extra.lhs, extra.rhs });
6726
6727 const inst_ty = f.typeOfIndex(inst);
6728
6729 const w = &f.code.writer;
6730 const local = try f.allocLocal(inst, inst_ty);
6731 const v = try Vectorize.start(f, inst, w, inst_ty);
6732 try f.writeCValue(w, local, .other);
6733 try v.elem(f, w);
6734 try w.writeAll(" = ");
6735 try f.writeCValue(w, pred, .other);
6736 try v.elem(f, w);
6737 try w.writeAll(" ? ");
6738 try f.writeCValue(w, lhs, .other);
6739 try v.elem(f, w);
6740 try w.writeAll(" : ");
6741 try f.writeCValue(w, rhs, .other);
6742 try v.elem(f, w);
6743 try w.writeByte(';');
6744 try f.newline();
6745 try v.end(f, inst, w);
6746
6747 return local;
6748}
6749
6750fn airShuffleOne(f: *Function, inst: Air.Inst.Index) !CValue {
6751 const pt = f.dg.pt;
6752 const zcu = pt.zcu;
6753
6754 const unwrapped = f.air.unwrapShuffleOne(zcu, inst);
6755 const mask = unwrapped.mask;
6756 const operand = try f.resolveInst(unwrapped.operand);
6757 const inst_ty = unwrapped.result_ty;
6758
6759 const w = &f.code.writer;
6760 const local = try f.allocLocal(inst, inst_ty);
6761 try reap(f, inst, &.{unwrapped.operand}); // local cannot alias operand
6762 for (mask, 0..) |mask_elem, out_idx| {
6763 try f.writeCValueMember(w, local, .{ .identifier = "array" });
6764 try w.writeByte('[');
6765 try f.dg.renderValue(w, try pt.intValue(.usize, out_idx), .other);
6766 try w.writeAll("] = ");
6767 switch (mask_elem.unwrap()) {
6768 .elem => |src_idx| {
6769 try f.writeCValueMember(w, operand, .{ .identifier = "array" });
6770 try w.writeByte('[');
6771 try f.dg.renderValue(w, try pt.intValue(.usize, src_idx), .other);
6772 try w.writeByte(']');
6773 },
6774 .value => |val| try f.dg.renderValue(w, .fromInterned(val), .other),
6775 }
6776 try w.writeByte(';');
6777 try f.newline();
6778 }
6779
6780 return local;
6781}
6782
6783fn airShuffleTwo(f: *Function, inst: Air.Inst.Index) !CValue {
6784 const pt = f.dg.pt;
6785 const zcu = pt.zcu;
6786
6787 const unwrapped = f.air.unwrapShuffleTwo(zcu, inst);
6788 const mask = unwrapped.mask;
6789 const operand_a = try f.resolveInst(unwrapped.operand_a);
6790 const operand_b = try f.resolveInst(unwrapped.operand_b);
6791 const inst_ty = unwrapped.result_ty;
6792 const elem_ty = inst_ty.childType(zcu);
6793
6794 const w = &f.code.writer;
6795 const local = try f.allocLocal(inst, inst_ty);
6796 try reap(f, inst, &.{ unwrapped.operand_a, unwrapped.operand_b }); // local cannot alias operands
6797 for (mask, 0..) |mask_elem, out_idx| {
6798 try f.writeCValueMember(w, local, .{ .identifier = "array" });
6799 try w.writeByte('[');
6800 try f.dg.renderValue(w, try pt.intValue(.usize, out_idx), .other);
6801 try w.writeAll("] = ");
6802 switch (mask_elem.unwrap()) {
6803 .a_elem => |src_idx| {
6804 try f.writeCValueMember(w, operand_a, .{ .identifier = "array" });
6805 try w.writeByte('[');
6806 try f.dg.renderValue(w, try pt.intValue(.usize, src_idx), .other);
6807 try w.writeByte(']');
6808 },
6809 .b_elem => |src_idx| {
6810 try f.writeCValueMember(w, operand_b, .{ .identifier = "array" });
6811 try w.writeByte('[');
6812 try f.dg.renderValue(w, try pt.intValue(.usize, src_idx), .other);
6813 try w.writeByte(']');
6814 },
6815 .undef => try f.dg.renderUndefValue(w, elem_ty, .other),
6816 }
6817 try w.writeByte(';');
6818 try f.newline();
6819 }
6820
6821 return local;
6822}
6823
6824fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue {
6825 const pt = f.dg.pt;
6826 const zcu = pt.zcu;
6827 const reduce = f.air.instructions.items(.data)[@backingInt(inst)].reduce;
6828
6829 const scalar_ty = f.typeOfIndex(inst);
6830 const operand = try f.resolveInst(reduce.operand);
6831 try reap(f, inst, &.{reduce.operand});
6832 const operand_ty = f.typeOf(reduce.operand);
6833 const w = &f.code.writer;
6834
6835 const use_operator, const is_big = if (scalar_ty.isInt(zcu)) switch (CType.classifyInt(scalar_ty, zcu)) {
6836 .void => unreachable,
6837 .small => |int| switch (int) {
6838 else => .{ true, false },
6839 .zig_u128, .zig_i128 => .{ false, false },
6840 },
6841 .big => .{ false, true },
6842 } else .{ false, false };
6843 const op: union(enum) {
6844 const Func = struct { operation: []const u8, info: BuiltinInfo = .none };
6845 builtin: Func,
6846 infix: []const u8,
6847 ternary: []const u8,
6848 } = switch (reduce.operation) {
6849 .And => if (use_operator) .{ .infix = " &= " } else .{ .builtin = .{ .operation = "and" } },
6850 .Or => if (use_operator) .{ .infix = " |= " } else .{ .builtin = .{ .operation = "or" } },
6851 .Xor => if (use_operator) .{ .infix = " ^= " } else .{ .builtin = .{ .operation = "xor" } },
6852 .Min => switch (scalar_ty.zigTypeTag(zcu)) {
6853 .int => if (use_operator) .{ .ternary = " < " } else .{ .builtin = .{ .operation = "min" } },
6854 .float => .{ .builtin = .{ .operation = "min" } },
6855 else => unreachable,
6856 },
6857 .Max => switch (scalar_ty.zigTypeTag(zcu)) {
6858 .int => if (use_operator) .{ .ternary = " > " } else .{ .builtin = .{ .operation = "max" } },
6859 .float => .{ .builtin = .{ .operation = "max" } },
6860 else => unreachable,
6861 },
6862 .Add => switch (scalar_ty.zigTypeTag(zcu)) {
6863 .int => if (use_operator) .{ .infix = " += " } else .{ .builtin = .{ .operation = "addw", .info = .bits } },
6864 .float => .{ .builtin = .{ .operation = "add" } },
6865 else => unreachable,
6866 },
6867 .Mul => switch (scalar_ty.zigTypeTag(zcu)) {
6868 .int => if (use_operator) .{ .infix = " *= " } else .{ .builtin = .{ .operation = "mulw", .info = .bits } },
6869 .float => .{ .builtin = .{ .operation = "mul" } },
6870 else => unreachable,
6871 },
6872 };
6873
6874 // Reduce a vector by repeatedly applying a function to produce an
6875 // accumulated result.
6876 //
6877 // Equivalent to:
6878 // reduce: {
6879 // var accum: T = init;
6880 // for (vec) |elem| {
6881 // accum = func(accum, elem);
6882 // }
6883 // break :reduce accum;
6884 // }
6885
6886 const accum = try f.allocLocal(inst, scalar_ty);
6887 try f.writeCValue(w, accum, .other);
6888 try w.writeAll(" = ");
6889
6890 try f.dg.renderValue(w, switch (reduce.operation) {
6891 .Or, .Xor => switch (scalar_ty.zigTypeTag(zcu)) {
6892 .bool => Value.false,
6893 .int => try pt.intValue(scalar_ty, 0),
6894 else => unreachable,
6895 },
6896 .And => switch (scalar_ty.zigTypeTag(zcu)) {
6897 .bool => Value.true,
6898 .int => switch (scalar_ty.intInfo(zcu).signedness) {
6899 .unsigned => try scalar_ty.maxIntScalar(pt, scalar_ty),
6900 .signed => try pt.intValue(scalar_ty, -1),
6901 },
6902 else => unreachable,
6903 },
6904 .Add => switch (scalar_ty.zigTypeTag(zcu)) {
6905 .int => try pt.intValue(scalar_ty, 0),
6906 .float => try pt.floatValue(scalar_ty, 0.0),
6907 else => unreachable,
6908 },
6909 .Mul => switch (scalar_ty.zigTypeTag(zcu)) {
6910 .int => try pt.intValue(scalar_ty, 1),
6911 .float => try pt.floatValue(scalar_ty, 1.0),
6912 else => unreachable,
6913 },
6914 .Min => switch (scalar_ty.zigTypeTag(zcu)) {
6915 .bool => Value.true,
6916 .int => try scalar_ty.maxIntScalar(pt, scalar_ty),
6917 .float => try pt.floatValue(scalar_ty, std.math.nan(f128)),
6918 else => unreachable,
6919 },
6920 .Max => switch (scalar_ty.zigTypeTag(zcu)) {
6921 .bool => Value.false,
6922 .int => try scalar_ty.minIntScalar(pt, scalar_ty),
6923 .float => try pt.floatValue(scalar_ty, std.math.nan(f128)),
6924 else => unreachable,
6925 },
6926 }, .other);
6927 try w.writeByte(';');
6928 try f.newline();
6929
6930 const v = try Vectorize.start(f, inst, w, operand_ty);
6931 switch (op) {
6932 .builtin => |func| {
6933 const prev_accum = if (is_big) prev_accum: {
6934 const prev_accum = try f.allocLocal(inst, scalar_ty);
6935 try f.writeCValue(w, prev_accum, .other);
6936 try w.writeAll(" = ");
6937 try f.writeCValue(w, accum, .other);
6938 try w.writeByte(';');
6939 try f.newline();
6940 break :prev_accum prev_accum;
6941 } else prev_accum: {
6942 try f.writeCValue(w, accum, .other);
6943 try w.writeAll(" = ");
6944 break :prev_accum accum;
6945 };
6946 try w.print("zig_{s}_", .{func.operation});
6947 try f.dg.renderTypeForBuiltinFnName(w, scalar_ty);
6948 try w.writeByte('(');
6949 if (is_big) {
6950 try w.writeByte('&');
6951 switch (accum) {
6952 .constant => |val| try f.dg.renderValueAsLvalue(w, val),
6953 else => try f.writeCValue(w, accum, .other),
6954 }
6955 try w.writeAll(", &");
6956 switch (prev_accum) {
6957 .constant => |val| try f.dg.renderValueAsLvalue(w, val),
6958 else => try f.writeCValue(w, prev_accum, .other),
6959 }
6960 } else try f.writeCValue(w, prev_accum, .other);
6961 try w.writeAll(", ");
6962 if (is_big) {
6963 try w.writeByte('&');
6964 switch (operand) {
6965 .constant => |val| try f.dg.renderValueAsLvalue(w, val),
6966 else => try f.writeCValue(w, operand, .other),
6967 }
6968 } else try f.writeCValue(w, operand, .other);
6969 try v.elem(f, w);
6970 try f.dg.renderBuiltinInfo(w, scalar_ty, func.info);
6971 try w.writeByte(')');
6972 if (is_big) try freeLocal(f, inst, prev_accum.new_local, null);
6973 },
6974 .infix => |ass| {
6975 try f.writeCValue(w, accum, .other);
6976 try w.writeAll(ass);
6977 try f.writeCValue(w, operand, .other);
6978 try v.elem(f, w);
6979 },
6980 .ternary => |cmp| {
6981 try f.writeCValue(w, accum, .other);
6982 try w.writeAll(" = ");
6983 try f.writeCValue(w, accum, .other);
6984 try w.writeAll(cmp);
6985 try f.writeCValue(w, operand, .other);
6986 try v.elem(f, w);
6987 try w.writeAll(" ? ");
6988 try f.writeCValue(w, accum, .other);
6989 try w.writeAll(" : ");
6990 try f.writeCValue(w, operand, .other);
6991 try v.elem(f, w);
6992 },
6993 }
6994 try w.writeByte(';');
6995 try f.newline();
6996 try v.end(f, inst, w);
6997
6998 return accum;
6999}
7000
7001fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue {
7002 const pt = f.dg.pt;
7003 const zcu = pt.zcu;
7004 const ip = &zcu.intern_pool;
7005 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
7006 const inst_ty = f.typeOfIndex(inst);
7007 const len: usize = @intCast(inst_ty.arrayLen(zcu));
7008 const elements: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[ty_pl.payload..][0..len]);
7009 const gpa = f.dg.gpa;
7010 const resolved_elements = try gpa.alloc(CValue, elements.len);
7011 defer gpa.free(resolved_elements);
7012 for (resolved_elements, elements) |*resolved_element, element| {
7013 resolved_element.* = try f.resolveInst(element);
7014 }
7015 {
7016 var bt = iterateBigTomb(f, inst);
7017 for (elements) |element| {
7018 try bt.feed(element);
7019 }
7020 }
7021
7022 const w = &f.code.writer;
7023 const local = try f.allocLocal(inst, inst_ty);
7024 switch (ip.indexToKey(inst_ty.toIntern())) {
7025 inline .array_type, .vector_type => |info, tag| {
7026 for (resolved_elements, 0..) |element, i| {
7027 try f.writeCValueMember(w, local, .{ .identifier = "array" });
7028 try w.print("[{d}] = ", .{i});
7029 try f.writeCValue(w, element, .other);
7030 try w.writeByte(';');
7031 try f.newline();
7032 }
7033 if (tag == .array_type and info.sentinel != .none) {
7034 try f.writeCValueMember(w, local, .{ .identifier = "array" });
7035 try w.print("[{d}] = ", .{info.len});
7036 try f.dg.renderValue(w, Value.fromInterned(info.sentinel), .other);
7037 try w.writeByte(';');
7038 try f.newline();
7039 }
7040 },
7041 .struct_type => {
7042 const loaded_struct = ip.loadStructType(inst_ty.toIntern());
7043 switch (loaded_struct.layout) {
7044 .auto, .@"extern" => {
7045 var field_it = loaded_struct.iterateRuntimeOrder(ip);
7046 while (field_it.next()) |field_index| {
7047 const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
7048 if (!field_ty.hasRuntimeBits(zcu)) continue;
7049
7050 try f.writeCValueMember(w, local, .{ .identifier = loaded_struct.field_names.get(ip)[field_index].toSlice(ip) });
7051 try w.writeAll(" = ");
7052 try f.writeCValue(w, resolved_elements[field_index], .other);
7053 try w.writeByte(';');
7054 try f.newline();
7055 }
7056 },
7057 .@"packed" => unreachable, // `Air.Legalize.Feature.expand_packed_struct_init` handles this case
7058 }
7059 },
7060 .tuple_type => |tuple_info| for (0..tuple_info.types.len) |field_index| {
7061 if (tuple_info.values.get(ip)[field_index] != .none) continue;
7062 const field_ty: Type = .fromInterned(tuple_info.types.get(ip)[field_index]);
7063 if (!field_ty.hasRuntimeBits(zcu)) continue;
7064
7065 try f.writeCValueMember(w, local, .{ .field = field_index });
7066 try w.writeAll(" = ");
7067 try f.writeCValue(w, resolved_elements[field_index], .other);
7068 try w.writeByte(';');
7069 try f.newline();
7070 },
7071 else => unreachable,
7072 }
7073
7074 return local;
7075}
7076
7077fn airUnionInit(f: *Function, inst: Air.Inst.Index) !CValue {
7078 const pt = f.dg.pt;
7079 const zcu = pt.zcu;
7080 const ip = &zcu.intern_pool;
7081 const ty_pl = f.air.instructions.items(.data)[@backingInt(inst)].ty_pl;
7082 const extra = f.air.extraData(Air.UnionInit, ty_pl.payload).data;
7083 const field_index = extra.field_index;
7084
7085 const union_ty = f.typeOfIndex(inst);
7086 const loaded_union = ip.loadUnionType(union_ty.toIntern());
7087 const loaded_enum = ip.loadEnumType(loaded_union.enum_tag_type);
7088
7089 const payload = try f.resolveInst(extra.init);
7090 try reap(f, inst, &.{extra.init});
7091
7092 const w = &f.code.writer;
7093 if (loaded_union.layout == .@"packed") return f.moveCValue(inst, union_ty, payload);
7094
7095 const local = try f.allocLocal(inst, union_ty);
7096
7097 if (loaded_union.has_runtime_tag) {
7098 try f.writeCValueMember(w, local, .{ .identifier = "tag" });
7099 if (loaded_enum.field_values.len == 0) {
7100 // auto-numbered
7101 try w.print(" = {d};", .{field_index});
7102 } else {
7103 const tag_int_val: Value = .fromInterned(loaded_enum.field_values.get(ip)[field_index]);
7104 try w.print(" = {f};", .{try f.fmtIntLiteralDec(tag_int_val)});
7105 }
7106 try f.newline();
7107 }
7108
7109 const field_name_slice = loaded_enum.field_names.get(ip)[field_index].toSlice(ip);
7110 switch (loaded_union.layout) {
7111 .auto => try f.writeCValueMember(w, local, .{ .payload_identifier = field_name_slice }),
7112 .@"extern" => try f.writeCValueMember(w, local, .{ .identifier = field_name_slice }),
7113 .@"packed" => unreachable,
7114 }
7115 try w.writeAll(" = ");
7116 try f.writeCValue(w, payload, .other);
7117 try w.writeByte(';');
7118 try f.newline();
7119 return local;
7120}
7121
7122fn airPrefetch(f: *Function, inst: Air.Inst.Index) !CValue {
7123 const pt = f.dg.pt;
7124 const zcu = pt.zcu;
7125 const prefetch = f.air.instructions.items(.data)[@backingInt(inst)].prefetch;
7126
7127 const ptr_ty = f.typeOf(prefetch.ptr);
7128 const ptr = try f.resolveInst(prefetch.ptr);
7129 try reap(f, inst, &.{prefetch.ptr});
7130
7131 const w = &f.code.writer;
7132 switch (prefetch.cache) {
7133 .data => {
7134 try w.writeAll("zig_prefetch(");
7135 if (ptr_ty.isSlice(zcu))
7136 try f.writeCValueMember(w, ptr, .{ .identifier = "ptr" })
7137 else
7138 try f.writeCValue(w, ptr, .other);
7139 try w.print(", {d}, {d});", .{ @backingInt(prefetch.rw), prefetch.locality });
7140 try f.newline();
7141 },
7142 // The available prefetch intrinsics do not accept a cache argument; only
7143 // address, rw, and locality.
7144 .instruction => {},
7145 }
7146
7147 return .none;
7148}
7149
7150fn airWasmMemorySize(f: *Function, inst: Air.Inst.Index) !CValue {
7151 const pl_op = f.air.instructions.items(.data)[@backingInt(inst)].pl_op;
7152
7153 const w = &f.code.writer;
7154 const inst_ty = f.typeOfIndex(inst);
7155 const local = try f.allocLocal(inst, inst_ty);
7156 try f.writeCValue(w, local, .other);
7157
7158 try w.writeAll(" = ");
7159 try w.print("zig_wasm_memory_size({d});", .{pl_op.payload});
7160 try f.newline();
7161
7162 return local;
7163}
7164
7165fn airWasmMemoryGrow(f: *Function, inst: Air.Inst.Index) !CValue {
7166 const pl_op = f.air.instructions.items(.data)[@backingInt(inst)].pl_op;
7167
7168 const w = &f.code.writer;
7169 const inst_ty = f.typeOfIndex(inst);
7170 const operand = try f.resolveInst(pl_op.operand);
7171 try reap(f, inst, &.{pl_op.operand});
7172 const local = try f.allocLocal(inst, inst_ty);
7173 try f.writeCValue(w, local, .other);
7174
7175 try w.writeAll(" = ");
7176 try w.print("zig_wasm_memory_grow({d}, ", .{pl_op.payload});
7177 try f.writeCValue(w, operand, .other);
7178 try w.writeAll(");");
7179 try f.newline();
7180 return local;
7181}
7182
7183fn airMulAdd(f: *Function, inst: Air.Inst.Index) !CValue {
7184 const pt = f.dg.pt;
7185 const zcu = pt.zcu;
7186 const pl_op = f.air.instructions.items(.data)[@backingInt(inst)].pl_op;
7187 const bin_op = f.air.extraData(Air.Bin, pl_op.payload).data;
7188
7189 const mulend1 = try f.resolveInst(bin_op.lhs);
7190 const mulend2 = try f.resolveInst(bin_op.rhs);
7191 const addend = try f.resolveInst(pl_op.operand);
7192 try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs, pl_op.operand });
7193
7194 const inst_ty = f.typeOfIndex(inst);
7195 const inst_scalar_ty = inst_ty.scalarType(zcu);
7196
7197 const w = &f.code.writer;
7198 const local = try f.allocLocal(inst, inst_ty);
7199 const v = try Vectorize.start(f, inst, w, inst_ty);
7200 try f.writeCValue(w, local, .other);
7201 try v.elem(f, w);
7202 try w.writeAll(" = zig_fma_");
7203 try f.dg.renderTypeForBuiltinFnName(w, inst_scalar_ty);
7204 try w.writeByte('(');
7205 try f.writeCValue(w, mulend1, .other);
7206 try v.elem(f, w);
7207 try w.writeAll(", ");
7208 try f.writeCValue(w, mulend2, .other);
7209 try v.elem(f, w);
7210 try w.writeAll(", ");
7211 try f.writeCValue(w, addend, .other);
7212 try v.elem(f, w);
7213 try w.writeAll(");");
7214 try f.newline();
7215 try v.end(f, inst, w);
7216
7217 return local;
7218}
7219
7220fn airRuntimeNavPtr(f: *Function, inst: Air.Inst.Index) !CValue {
7221 const ty_nav = f.air.instructions.items(.data)[@backingInt(inst)].ty_nav;
7222 const w = &f.code.writer;
7223 const local = try f.allocLocal(inst, ty_nav.ty);
7224 try f.writeCValue(w, local, .other);
7225 try w.writeAll(" = ");
7226 try f.dg.renderNav(w, ty_nav.nav, .other);
7227 try w.writeByte(';');
7228 try f.newline();
7229 return local;
7230}
7231
7232fn airCVaStart(f: *Function, inst: Air.Inst.Index) !CValue {
7233 const pt = f.dg.pt;
7234 const zcu = pt.zcu;
7235 const inst_ty = f.typeOfIndex(inst);
7236
7237 assert(Value.fromInterned(f.func_index).typeOf(zcu).fnIsVarArgs(zcu));
7238
7239 const w = &f.code.writer;
7240 const local = try f.allocLocal(inst, inst_ty);
7241 try w.writeAll("va_start(*(va_list *)&");
7242 try f.writeCValue(w, local, .other);
7243 if (f.next_arg_index > 0) {
7244 try w.writeAll(", ");
7245 try f.writeCValue(w, .{ .arg = f.next_arg_index - 1 }, .other);
7246 }
7247 try w.writeAll(");");
7248 try f.newline();
7249 return local;
7250}
7251
7252fn airCVaArg(f: *Function, inst: Air.Inst.Index) !CValue {
7253 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
7254
7255 const inst_ty = f.typeOfIndex(inst);
7256 const va_list = try f.resolveInst(ty_op.operand);
7257 try reap(f, inst, &.{ty_op.operand});
7258
7259 const w = &f.code.writer;
7260 const local = try f.allocLocal(inst, inst_ty);
7261 try f.writeCValue(w, local, .other);
7262 try w.writeAll(" = va_arg(*(va_list *)");
7263 try f.writeCValue(w, va_list, .other);
7264 try w.writeAll(", ");
7265 try f.renderType(w, ty_op.ty);
7266 try w.writeAll(");");
7267 try f.newline();
7268 return local;
7269}
7270
7271fn airCVaEnd(f: *Function, inst: Air.Inst.Index) !CValue {
7272 const un_op = f.air.instructions.items(.data)[@backingInt(inst)].un_op;
7273
7274 const va_list = try f.resolveInst(un_op);
7275 try reap(f, inst, &.{un_op});
7276
7277 const w = &f.code.writer;
7278 try w.writeAll("va_end(*(va_list *)");
7279 try f.writeCValue(w, va_list, .other);
7280 try w.writeAll(");");
7281 try f.newline();
7282 return .none;
7283}
7284
7285fn airCVaCopy(f: *Function, inst: Air.Inst.Index) !CValue {
7286 const ty_op = f.air.instructions.items(.data)[@backingInt(inst)].ty_op;
7287
7288 const inst_ty = f.typeOfIndex(inst);
7289 const va_list = try f.resolveInst(ty_op.operand);
7290 try reap(f, inst, &.{ty_op.operand});
7291
7292 const w = &f.code.writer;
7293 const local = try f.allocLocal(inst, inst_ty);
7294 try w.writeAll("va_copy(*(va_list *)&");
7295 try f.writeCValue(w, local, .other);
7296 try w.writeAll(", *(va_list *)");
7297 try f.writeCValue(w, va_list, .other);
7298 try w.writeAll(");");
7299 try f.newline();
7300 return local;
7301}
7302
7303fn toMemoryOrder(order: std.lang.AtomicOrder) [:0]const u8 {
7304 return switch (order) {
7305 // Note: unordered is actually even less atomic than relaxed
7306 .unordered, .monotonic => "zig_memory_order_relaxed",
7307 .acquire => "zig_memory_order_acquire",
7308 .release => "zig_memory_order_release",
7309 .acq_rel => "zig_memory_order_acq_rel",
7310 .seq_cst => "zig_memory_order_seq_cst",
7311 };
7312}
7313
7314fn writeMemoryOrder(w: *Writer, order: std.lang.AtomicOrder) !void {
7315 return w.writeAll(toMemoryOrder(order));
7316}
7317
7318fn toAtomicRmwSuffix(order: std.lang.AtomicRmwOp) []const u8 {
7319 return switch (order) {
7320 .Xchg => "xchg",
7321 .Add => "add",
7322 .Sub => "sub",
7323 .And => "and",
7324 .Nand => "nand",
7325 .Or => "or",
7326 .Xor => "xor",
7327 .Max => "max",
7328 .Min => "min",
7329 };
7330}
7331
7332fn toCIntBits(zig_bits: u32) ?u32 {
7333 for (&[_]u8{ 8, 16, 32, 64, 128 }) |c_bits| {
7334 if (zig_bits <= c_bits) {
7335 return c_bits;
7336 }
7337 }
7338 return null;
7339}
7340
7341fn signAbbrev(signedness: std.lang.Signedness) u8 {
7342 return switch (signedness) {
7343 .signed => 'i',
7344 .unsigned => 'u',
7345 };
7346}
7347
7348fn compilerRtAbbrev(ty: Type, zcu: *Zcu, target: *const std.Target) []const u8 {
7349 return if (ty.isInt(zcu)) switch (ty.intInfo(zcu).bits) {
7350 0 => unreachable,
7351 1...32 => "si",
7352 33...64 => "di",
7353 65...128 => "ti",
7354 else => "ei",
7355 } else if (ty.isRuntimeFloat()) switch (ty.floatBits(target)) {
7356 else => unreachable,
7357 16 => "hf",
7358 32 => "sf",
7359 64 => "df",
7360 80 => "xf",
7361 128 => if (target.cpu.arch.isPowerPC()) "kf" else "tf",
7362 } else unreachable;
7363}
7364
7365fn compareOperatorAbbrev(operator: std.math.CompareOperator) []const u8 {
7366 return switch (operator) {
7367 .lt => "lt",
7368 .lte => "le",
7369 .eq => "eq",
7370 .gte => "ge",
7371 .gt => "gt",
7372 .neq => "ne",
7373 };
7374}
7375
7376fn compareOperatorC(operator: std.math.CompareOperator) []const u8 {
7377 return switch (operator) {
7378 .lt => " < ",
7379 .lte => " <= ",
7380 .eq => " == ",
7381 .gte => " >= ",
7382 .gt => " > ",
7383 .neq => " != ",
7384 };
7385}
7386
7387const StringLiteral = struct {
7388 len: usize,
7389 cur_len: usize,
7390 w: *Writer,
7391 first: bool,
7392
7393 // MSVC throws C2078 if an array of size 65536 or greater is initialized with a string literal,
7394 // regardless of the length of the string literal initializing it. Array initializer syntax is
7395 // used instead.
7396 // C99 only requires 4095.
7397 const max_string_initializer_len = @min(65535, 4095);
7398
7399 // MSVC has a length limit of 16380 per string literal (before concatenation)
7400 // C99 only requires 4095.
7401 const max_char_len = 4;
7402 const max_literal_len = @min(16380 - max_char_len, 4095);
7403
7404 fn init(w: *Writer, len: usize) StringLiteral {
7405 return .{
7406 .cur_len = 0,
7407 .len = len,
7408 .w = w,
7409 .first = true,
7410 };
7411 }
7412
7413 pub fn start(sl: *StringLiteral) Writer.Error!void {
7414 if (sl.len <= max_string_initializer_len) {
7415 try sl.w.writeByte('\"');
7416 } else {
7417 try sl.w.writeByte('{');
7418 }
7419 }
7420
7421 pub fn end(sl: *StringLiteral) Writer.Error!void {
7422 if (sl.len <= max_string_initializer_len) {
7423 try sl.w.writeByte('\"');
7424 } else {
7425 try sl.w.writeByte('}');
7426 }
7427 }
7428
7429 fn writeStringLiteralChar(sl: *StringLiteral, c: u8) Writer.Error!usize {
7430 const w = sl.w;
7431 switch (c) {
7432 7 => {
7433 try w.writeAll("\\a");
7434 return 2;
7435 },
7436 8 => {
7437 try w.writeAll("\\b");
7438 return 2;
7439 },
7440 '\t' => {
7441 try w.writeAll("\\t");
7442 return 2;
7443 },
7444 '\n' => {
7445 try w.writeAll("\\n");
7446 return 2;
7447 },
7448 11 => {
7449 try w.writeAll("\\v");
7450 return 2;
7451 },
7452 12 => {
7453 try w.writeAll("\\f");
7454 return 2;
7455 },
7456 '\r' => {
7457 try w.writeAll("\\r");
7458 return 2;
7459 },
7460 '"', '\'', '?', '\\' => {
7461 try w.print("\\{c}", .{c});
7462 return 2;
7463 },
7464 ' '...'!', '#'...'&', '('...'>', '@'...'[', ']'...'~' => {
7465 try w.writeByte(c);
7466 return 1;
7467 },
7468 else => {
7469 var buf: [4]u8 = undefined;
7470 const printed = std.mem.print(&buf, "\\{o:0>3}", .{c}) catch unreachable;
7471 try w.writeAll(printed);
7472 return printed.len;
7473 },
7474 }
7475 }
7476
7477 pub fn writeChar(sl: *StringLiteral, c: u8) Writer.Error!void {
7478 if (sl.len <= max_string_initializer_len) {
7479 if (sl.cur_len == 0 and !sl.first) try sl.w.writeAll("\"\"");
7480
7481 const char_len = try sl.writeStringLiteralChar(c);
7482 assert(char_len <= max_char_len);
7483 sl.cur_len += char_len;
7484
7485 if (sl.cur_len >= max_literal_len) {
7486 sl.cur_len = 0;
7487 sl.first = false;
7488 }
7489 } else {
7490 if (!sl.first) try sl.w.writeByte(',');
7491 var buf: [6]u8 = undefined;
7492 const printed = std.mem.print(&buf, "'\\x{x}'", .{c}) catch unreachable;
7493 try sl.w.writeAll(printed);
7494 sl.cur_len += printed.len;
7495 sl.first = false;
7496 }
7497 }
7498};
7499
7500const FormatStringContext = struct {
7501 str: []const u8,
7502 sentinel: ?u8,
7503};
7504
7505fn formatStringLiteral(data: FormatStringContext, w: *Writer) Writer.Error!void {
7506 var literal: StringLiteral = .init(w, data.str.len + @intFromBool(data.sentinel != null));
7507 try literal.start();
7508 for (data.str) |c| try literal.writeChar(c);
7509 if (data.sentinel) |sentinel| if (sentinel != 0) try literal.writeChar(sentinel);
7510 try literal.end();
7511}
7512
7513fn fmtStringLiteral(str: []const u8, sentinel: ?u8) std.fmt.Alt(FormatStringContext, formatStringLiteral) {
7514 return .{ .data = .{ .str = str, .sentinel = sentinel } };
7515}
7516
7517fn undefPattern(comptime Result: type) Result {
7518 return @bitCast(@as(@Int(.unsigned, @bitSizeOf(Result)), (1 << (@bitSizeOf(Result) | 1)) / 3));
7519}
7520
7521const FormatIntLiteralContext = struct {
7522 dg: *DeclGen,
7523 loc: ValueRenderLocation,
7524 val: Value,
7525 cty: CType,
7526 base: u8,
7527 case: std.fmt.Case,
7528};
7529fn formatIntLiteral(data: FormatIntLiteralContext, w: *Writer) Writer.Error!void {
7530 const dg = data.dg;
7531 const zcu = dg.pt.zcu;
7532 const target = &dg.mod.resolved_target.result;
7533
7534 const val = data.val;
7535 const ty = val.typeOf(zcu);
7536
7537 assert(!val.isUndef(zcu));
7538
7539 var space: Value.BigIntSpace = undefined;
7540 const val_bigint = val.toBigInt(&space, zcu);
7541
7542 switch (CType.classifyInt(ty, zcu)) {
7543 .void => unreachable, // opv
7544 .small => |int_cty| return FormatInt128.format(.{
7545 .target = zcu.getTarget(),
7546 .int_cty = int_cty,
7547 .val = val_bigint,
7548 .is_global = data.loc == .static_initializer,
7549 .base = data.base,
7550 .case = data.case,
7551 }, w),
7552 .big => |big| {
7553 if (!data.loc.isInitializer()) {
7554 // Use `CType.fmtTypeName` directly to avoid the possibility of `error.OutOfMemory`.
7555 try w.print("({f})", .{data.cty.fmtTypeName(zcu)});
7556 }
7557
7558 try w.writeAll("{{");
7559
7560 var limb_buf: [std.math.big.int.calcTwosCompLimbCount(65535)]std.math.big.Limb = undefined;
7561 for (0..big.limbs_len) |limb_index| {
7562 if (limb_index != 0) try w.writeAll(", ");
7563 const limb_bit_offset: u16 = switch (target.cpu.arch.endian()) {
7564 .little => @intCast(limb_index * big.limb_size.bits()),
7565 .big => @intCast((big.limbs_len - limb_index - 1) * big.limb_size.bits()),
7566 };
7567 var limb_bigint: std.math.big.int.Mutable = .{
7568 .limbs = &limb_buf,
7569 .len = undefined,
7570 .positive = undefined,
7571 };
7572 limb_bigint.shiftRight(val_bigint, limb_bit_offset);
7573 limb_bigint.truncate(limb_bigint.toConst(), .unsigned, big.limb_size.bits());
7574 try FormatInt128.format(.{
7575 .target = zcu.getTarget(),
7576 .int_cty = big.limb_size.unsigned(),
7577 .val = limb_bigint.toConst(),
7578 .is_global = data.loc == .static_initializer,
7579 .base = data.base,
7580 .case = data.case,
7581 }, w);
7582 }
7583
7584 try w.writeAll("}}");
7585 },
7586 }
7587}
7588const FormatInt128 = struct {
7589 target: *const std.Target,
7590 int_cty: CType.Int,
7591 val: std.math.big.int.Const,
7592 is_global: bool,
7593 base: u8,
7594 case: std.fmt.Case,
7595 pub fn format(data: FormatInt128, w: *Writer) Writer.Error!void {
7596 const target = data.target;
7597
7598 const val = data.val;
7599 const is_global = data.is_global;
7600 const base = data.base;
7601 const case = data.case;
7602
7603 switch (data.int_cty) {
7604 .uint8_t,
7605 .uint16_t,
7606 .uint24_t,
7607 .uint32_t,
7608 .uint48_t,
7609 .uint64_t,
7610 .@"unsigned short",
7611 .@"unsigned int",
7612 .@"unsigned long",
7613 .@"unsigned long long",
7614 .uintptr_t,
7615 => |t| try w.print("{f}", .{
7616 fmtUnsignedIntLiteralSmall(target, t, val.toInt(u64) catch unreachable, is_global, base, case),
7617 }),
7618
7619 .int8_t,
7620 .int16_t,
7621 .int24_t,
7622 .int48_t,
7623 .int32_t,
7624 .int64_t,
7625 .char,
7626 .@"signed short",
7627 .@"signed int",
7628 .@"signed long",
7629 .@"signed long long",
7630 .intptr_t,
7631 => |t| try w.print("{f}", .{
7632 fmtSignedIntLiteralSmall(target, t, val.toInt(i64) catch unreachable, is_global, base, case),
7633 }),
7634
7635 .zig_u128 => {
7636 const raw = val.toInt(u128) catch unreachable;
7637 const lo: u64 = @truncate(raw);
7638 const hi: u64 = @intCast(raw >> 64);
7639 const macro_name: []const u8 = if (is_global) "zig_init_u128" else "zig_make_u128";
7640 try w.print("{s}({f}, {f})", .{
7641 macro_name,
7642 fmtUnsignedIntLiteralSmall(target, .uint64_t, hi, is_global, base, case),
7643 fmtUnsignedIntLiteralSmall(target, .uint64_t, lo, is_global, base, case),
7644 });
7645 },
7646
7647 .zig_i128 => {
7648 const raw = val.toInt(i128) catch unreachable;
7649 const lo: u64 = @truncate(@as(u128, @bitCast(raw)));
7650 const hi: i64 = @intCast(raw >> 64);
7651 const macro_name: []const u8 = if (is_global) "zig_init_i128" else "zig_make_i128";
7652 try w.print("{s}({f}, {f})", .{
7653 macro_name,
7654 fmtSignedIntLiteralSmall(target, .int64_t, hi, is_global, base, case),
7655 fmtUnsignedIntLiteralSmall(target, .uint64_t, lo, is_global, base, case),
7656 });
7657 },
7658 }
7659 }
7660};
7661fn fmtUnsignedIntLiteralSmall(
7662 target: *const std.Target,
7663 int_cty: CType.Int,
7664 val: u64,
7665 is_global: bool,
7666 base: u8,
7667 case: std.fmt.Case,
7668) FormatUnsignedIntLiteralSmall {
7669 return .{
7670 .target = target,
7671 .int_cty = int_cty,
7672 .val = val,
7673 .is_global = is_global,
7674 .base = base,
7675 .case = case,
7676 };
7677}
7678fn fmtSignedIntLiteralSmall(
7679 target: *const std.Target,
7680 int_cty: CType.Int,
7681 val: i64,
7682 is_global: bool,
7683 base: u8,
7684 case: std.fmt.Case,
7685) FormatSignedIntLiteralSmall {
7686 return .{
7687 .target = target,
7688 .int_cty = int_cty,
7689 .val = val,
7690 .is_global = is_global,
7691 .base = base,
7692 .case = case,
7693 };
7694}
7695
7696const FormatSignedIntLiteralSmall = struct {
7697 target: *const std.Target,
7698 int_cty: CType.Int,
7699 val: i64,
7700 is_global: bool,
7701 base: u8,
7702 case: std.fmt.Case,
7703 pub fn format(data: FormatSignedIntLiteralSmall, w: *Writer) Writer.Error!void {
7704 const bits = data.int_cty.bits(data.target);
7705 if (data.val == @as(i64, std.math.maxInt(i64)) >> @intCast(64 - bits)) {
7706 return w.print("{s}_MAX", .{minMaxMacroPrefix(data.int_cty)});
7707 } else if (data.val == @as(i64, std.math.minInt(i64)) >> @intCast(64 - bits)) {
7708 return w.print("{s}_MIN", .{minMaxMacroPrefix(data.int_cty)});
7709 }
7710 if (data.val < 0) try w.writeByte('-');
7711 try w.writeAll(intLiteralPrefix(data.int_cty, data.is_global));
7712 switch (data.base) {
7713 2 => try w.writeAll("0b"),
7714 8 => try w.writeByte('0'),
7715 10 => {},
7716 16 => try w.writeAll("0x"),
7717 else => unreachable,
7718 }
7719 // This `@abs` is safe thanks to the min int check above.
7720 try w.printInt(@abs(data.val), data.base, data.case, .{});
7721 try w.writeAll(intLiteralSuffix(data.int_cty));
7722 }
7723};
7724const FormatUnsignedIntLiteralSmall = struct {
7725 target: *const std.Target,
7726 int_cty: CType.Int,
7727 val: u64,
7728 is_global: bool,
7729 base: u8,
7730 case: std.fmt.Case,
7731 pub fn format(data: FormatUnsignedIntLiteralSmall, w: *Writer) Writer.Error!void {
7732 const bits = data.int_cty.bits(data.target);
7733 if (data.val == @as(u64, std.math.maxInt(u64)) >> @intCast(64 - bits)) {
7734 return w.print("{s}_MAX", .{minMaxMacroPrefix(data.int_cty)});
7735 }
7736 try w.writeAll(intLiteralPrefix(data.int_cty, data.is_global));
7737 switch (data.base) {
7738 2 => try w.writeAll("0b"),
7739 8 => try w.writeByte('0'),
7740 10 => {},
7741 16 => try w.writeAll("0x"),
7742 else => unreachable,
7743 }
7744 try w.printInt(data.val, data.base, data.case, .{});
7745 try w.writeAll(intLiteralSuffix(data.int_cty));
7746 }
7747};
7748fn minMaxMacroPrefix(int_cty: CType.Int) []const u8 {
7749 return switch (int_cty) {
7750 // zig fmt: off
7751 .char => "CHAR",
7752
7753 .@"unsigned short" => "USHRT",
7754 .@"unsigned int" => "UINT",
7755 .@"unsigned long" => "ULONG",
7756 .@"unsigned long long" => "ULLONG",
7757
7758 .@"signed short" => "SHRT",
7759 .@"signed int" => "INT",
7760 .@"signed long" => "LONG",
7761 .@"signed long long" => "LLONG",
7762
7763 .uint8_t => "UINT8",
7764 .uint16_t => "UINT16",
7765 .uint24_t => "UINT24",
7766 .uint32_t => "UINT32",
7767 .uint48_t => "UINT48",
7768 .uint64_t => "UINT64",
7769 .zig_u128 => unreachable,
7770
7771 .int8_t => "INT8",
7772 .int16_t => "INT16",
7773 .int24_t => "INT24",
7774 .int32_t => "INT32",
7775 .int48_t => "INT48",
7776 .int64_t => "INT64",
7777 .zig_i128 => unreachable,
7778
7779 .uintptr_t => "UINTPTR",
7780 .intptr_t => "INTPTR",
7781 // zig fmt: on
7782 };
7783}
7784fn intLiteralPrefix(cty: CType.Int, is_global: bool) []const u8 {
7785 return switch (cty) {
7786 // zig fmt: off
7787 .char => if (is_global) "" else "(char)",
7788
7789 .@"unsigned short" => if (is_global) "" else "(unsigned short)",
7790 .@"unsigned int" => "",
7791 .@"unsigned long" => "",
7792 .@"unsigned long long" => "",
7793
7794 .@"signed short" => if (is_global) "" else "(signed short)",
7795 .@"signed int" => "",
7796 .@"signed long" => "",
7797 .@"signed long long" => "",
7798
7799 .uint8_t => "UINT8_C(",
7800 .uint16_t => "UINT16_C(",
7801 .uint24_t => "UINT24_C(",
7802 .uint32_t => "UINT32_C(",
7803 .uint48_t => "UINT48_C(",
7804 .uint64_t => "UINT64_C(",
7805 .zig_u128 => unreachable,
7806
7807 .int8_t => "INT8_C(",
7808 .int16_t => "INT16_C(",
7809 .int24_t => "INT24_C(",
7810 .int32_t => "INT32_C(",
7811 .int48_t => "INT48_C(",
7812 .int64_t => "INT64_C(",
7813 .zig_i128 => unreachable,
7814
7815 .uintptr_t => if (is_global) "" else "(uintptr_t)",
7816 .intptr_t => if (is_global) "" else "(intptr_t)",
7817 // zig fmt: on
7818 };
7819}
7820fn intLiteralSuffix(cty: CType.Int) []const u8 {
7821 return switch (cty) {
7822 // zig fmt: off
7823 .char => "",
7824
7825 .@"unsigned short" => "u",
7826 .@"unsigned int" => "u",
7827 .@"unsigned long" => "ul",
7828 .@"unsigned long long" => "ull",
7829
7830 .@"signed short" => "",
7831 .@"signed int" => "",
7832 .@"signed long" => "l",
7833 .@"signed long long" => "ll",
7834
7835 .uint8_t => ")",
7836 .uint16_t => ")",
7837 .uint24_t => ")",
7838 .uint32_t => ")",
7839 .uint48_t => ")",
7840 .uint64_t => ")",
7841 .zig_u128 => unreachable,
7842
7843 .int8_t => ")",
7844 .int16_t => ")",
7845 .int24_t => ")",
7846 .int32_t => ")",
7847 .int48_t => ")",
7848 .int64_t => ")",
7849 .zig_i128 => unreachable,
7850
7851 .uintptr_t => "ul",
7852 .intptr_t => "",
7853 // zig fmt: on
7854 };
7855}
7856
7857const F80Repr = packed struct {
7858 mantissa: u64,
7859 exponent: u16,
7860
7861 fn write(repr: F80Repr, w: *Writer, target: *const std.Target, is_global: bool) Writer.Error!void {
7862 try w.print("zig_{s}_repr_f80({f}, {f})", .{
7863 if (is_global) "init" else "make",
7864 fmtUnsignedIntLiteralSmall(target, .uint64_t, repr.mantissa, is_global, 16, .lower),
7865 fmtUnsignedIntLiteralSmall(target, .uint16_t, repr.exponent, is_global, 16, .lower),
7866 });
7867 }
7868};
7869const F128Repr = packed struct {
7870 lo: u64,
7871 hi: u64,
7872
7873 fn write(repr: F128Repr, w: *Writer, target: *const std.Target, is_global: bool) Writer.Error!void {
7874 try w.print("zig_{s}_repr_f128({f}, {f})", .{
7875 if (is_global) "init" else "make",
7876 fmtUnsignedIntLiteralSmall(target, .uint64_t, repr.hi, is_global, 16, .lower),
7877 fmtUnsignedIntLiteralSmall(target, .uint64_t, repr.lo, is_global, 16, .lower),
7878 });
7879 }
7880};
7881
7882const Materialize = struct {
7883 local: CValue,
7884
7885 pub fn start(f: *Function, inst: Air.Inst.Index, ty: Type, value: CValue) !Materialize {
7886 return .{ .local = switch (value) {
7887 .local_ref, .constant, .nav_ref, .undef => try f.moveCValue(inst, ty, value),
7888 .new_local => |local| .{ .local = local },
7889 else => value,
7890 } };
7891 }
7892
7893 pub fn mat(self: Materialize, f: *Function, w: *Writer) !void {
7894 try f.writeCValue(w, self.local, .other);
7895 }
7896
7897 pub fn end(self: Materialize, f: *Function, inst: Air.Inst.Index) !void {
7898 try f.freeCValue(inst, self.local);
7899 }
7900};
7901
7902const Vectorize = struct {
7903 index: CValue = .none,
7904
7905 pub fn start(f: *Function, inst: Air.Inst.Index, w: *Writer, ty: Type) !Vectorize {
7906 const pt = f.dg.pt;
7907 const zcu = pt.zcu;
7908 switch (ty.zigTypeTag(zcu)) {
7909 else => return .{ .index = .none },
7910 .vector => {
7911 const local = try f.allocLocal(inst, .usize);
7912 try w.writeAll("for (");
7913 try f.writeCValue(w, local, .other);
7914 try w.print(" = {f}; ", .{try f.fmtIntLiteralDec(.zero_usize)});
7915 try f.writeCValue(w, local, .other);
7916 try w.print(" < {f}; ", .{try f.fmtIntLiteralDec(try pt.intValue(.usize, ty.vectorLen(zcu)))});
7917 try f.writeCValue(w, local, .other);
7918 try w.print(" += {f}) {{", .{try f.fmtIntLiteralDec(.one_usize)});
7919 f.indent();
7920 try f.newline();
7921 return .{ .index = local };
7922 },
7923 }
7924 }
7925
7926 pub fn elem(self: Vectorize, f: *Function, w: *Writer) !void {
7927 if (self.index != .none) {
7928 try w.writeAll(".array[");
7929 try f.writeCValue(w, self.index, .other);
7930 try w.writeByte(']');
7931 }
7932 }
7933
7934 pub fn end(self: Vectorize, f: *Function, inst: Air.Inst.Index, w: *Writer) !void {
7935 if (self.index != .none) {
7936 try f.outdent();
7937 try w.writeByte('}');
7938 try f.newline();
7939 try freeLocal(f, inst, self.index.new_local, null);
7940 }
7941 }
7942};
7943
7944fn lowersToBigInt(ty: Type, zcu: *const Zcu) bool {
7945 return switch (ty.zigTypeTag(zcu)) {
7946 .int, .@"enum", .@"struct", .@"union" => CType.classifyInt(ty, zcu) == .big,
7947 else => false,
7948 };
7949}
7950
7951fn reap(f: *Function, inst: Air.Inst.Index, operands: []const Air.Inst.Ref) !void {
7952 assert(operands.len <= Air.Liveness.bpi - 1);
7953 var tomb_bits = f.liveness.getTombBits(inst);
7954 for (operands) |operand| {
7955 const dies = @as(u1, @truncate(tomb_bits)) != 0;
7956 tomb_bits >>= 1;
7957 if (!dies) continue;
7958 try die(f, inst, operand);
7959 }
7960}
7961
7962fn die(f: *Function, inst: Air.Inst.Index, ref: Air.Inst.Ref) !void {
7963 const ref_inst = ref.toIndex() orelse return;
7964 const c_value = (f.value_map.fetchRemove(ref) orelse return).value;
7965 const local_index = switch (c_value) {
7966 .new_local, .local => |l| l,
7967 else => return,
7968 };
7969 try freeLocal(f, inst, local_index, ref_inst);
7970}
7971
7972fn freeLocal(f: *Function, inst: ?Air.Inst.Index, local_index: LocalIndex, ref_inst: ?Air.Inst.Index) !void {
7973 const gpa = f.dg.gpa;
7974 const local = f.locals.items[local_index];
7975 if (inst) |i| {
7976 if (ref_inst) |operand| {
7977 log.debug("%{d}: freeing t{d} (operand %{d})", .{ @backingInt(i), local_index, operand });
7978 } else {
7979 log.debug("%{d}: freeing t{d}", .{ @backingInt(i), local_index });
7980 }
7981 } else {
7982 if (ref_inst) |operand| {
7983 log.debug("freeing t{d} (operand %{d})", .{ local_index, operand });
7984 } else {
7985 log.debug("freeing t{d}", .{local_index});
7986 }
7987 }
7988 const gop = try f.free_locals_map.getOrPut(gpa, local);
7989 if (!gop.found_existing) gop.value_ptr.* = .{};
7990 if (std.debug.runtime_safety) {
7991 // If this trips, an unfreeable allocation was attempted to be freed.
7992 assert(!f.allocs.contains(local_index));
7993 }
7994 // If this trips, it means a local is being inserted into the
7995 // free_locals map while it already exists in the map, which is not
7996 // allowed.
7997 try gop.value_ptr.putNoClobber(gpa, local_index, {});
7998}
7999
8000const BigTomb = struct {
8001 f: *Function,
8002 inst: Air.Inst.Index,
8003 lbt: Air.Liveness.BigTomb,
8004
8005 fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) !void {
8006 const dies = bt.lbt.feed();
8007 if (!dies) return;
8008 try die(bt.f, bt.inst, op_ref);
8009 }
8010};
8011
8012fn iterateBigTomb(f: *Function, inst: Air.Inst.Index) BigTomb {
8013 return .{
8014 .f = f,
8015 .inst = inst,
8016 .lbt = f.liveness.iterateBigTomb(inst),
8017 };
8018}
8019
8020/// A naive clone of this map would create copies of the ArrayList which is
8021/// stored in the values. This function additionally clones the values.
8022fn cloneFreeLocalsMap(gpa: Allocator, map: *LocalsMap) !LocalsMap {
8023 var cloned = try map.clone(gpa);
8024 const values = cloned.values();
8025 var i: usize = 0;
8026 errdefer {
8027 cloned.deinit(gpa);
8028 while (i > 0) {
8029 i -= 1;
8030 values[i].deinit(gpa);
8031 }
8032 }
8033 while (i < values.len) : (i += 1) {
8034 values[i] = try values[i].clone(gpa);
8035 }
8036 return cloned;
8037}
8038
8039fn deinitFreeLocalsMap(gpa: Allocator, map: *LocalsMap) void {
8040 for (map.values()) |*value| {
8041 value.deinit(gpa);
8042 }
8043 map.deinit(gpa);
8044}
8045
8046fn renderErrorName(w: *Writer, err_name: []const u8) Writer.Error!void {
8047 try w.print("zig_error_{f}", .{fmtIdentUnsolo(err_name)});
8048}
8049
8050fn renderNavName(w: *Writer, nav_index: InternPool.Nav.Index, ip: *const InternPool) !void {
8051 const nav = ip.getNav(nav_index);
8052 if (nav.getExtern(ip)) |@"extern"| {
8053 try w.print("{f}", .{
8054 fmtIdentSolo(ip.getNav(@"extern".owner_nav).name.toSlice(ip)),
8055 });
8056 } else {
8057 // MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case),
8058 // expand to 3x the length of its input, but let's cut it off at a much shorter limit.
8059 const fqn_slice = ip.getNav(nav_index).fqn.toSlice(ip);
8060 try w.print("{f}__{d}", .{
8061 fmtIdentUnsolo(fqn_slice[0..@min(fqn_slice.len, 100)]),
8062 @backingInt(nav_index),
8063 });
8064 }
8065}
8066
8067fn renderUavName(w: *Writer, uav: Value) !void {
8068 try w.print("__anon_{d}", .{@backingInt(uav.toIntern())});
8069}