1const std = @import("std");
2const mem = std.mem;
3const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;
5
6const CodeGen = @This();
7const codegen = @import("../../codegen.zig");
8const link = @import("../../link.zig");
9const Spork8 = link.File.Spork8;
10const Zcu = @import("../../Zcu.zig");
11const InternPool = @import("../../InternPool.zig");
12const Air = @import("../../Air.zig");
13const Liveness = Air.Liveness;
14const Mir = @import("Mir.zig");
15
16air: Air,
17liveness: Liveness,
18gpa: Allocator,
19pt: Zcu.PerThread,
20owner_nav: InternPool.Nav.Index,
21func_index: InternPool.Index,
22mir_instructions: std.MultiArrayList(Mir.Inst),
23/// Contains extra data for MIR
24mir_extra: std.ArrayListUnmanaged(u32),
25
26pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
27 return comptime &.initMany(&.{
28 .expand_bit_cast_safe,
29 .expand_int_cast_safe,
30 .expand_int_from_float_safe,
31 .expand_int_from_float_optimized_safe,
32 .expand_add_safe,
33 .expand_sub_safe,
34 .expand_mul_safe,
35
36 .expand_packed_load,
37 .expand_packed_store,
38 .expand_packed_agg_field_val,
39 .expand_packed_aggregate_init,
40 .expand_array_to_vector,
41
42 .scalarize_add,
43 .scalarize_add_optimized,
44 .scalarize_add_wrap,
45 .scalarize_add_sat,
46 .scalarize_sub,
47 .scalarize_sub_optimized,
48 .scalarize_sub_wrap,
49 .scalarize_sub_sat,
50 .scalarize_mul,
51 .scalarize_mul_optimized,
52 .scalarize_mul_wrap,
53 .scalarize_mul_sat,
54 .scalarize_div_float,
55 .scalarize_div_float_optimized,
56 .scalarize_div_trunc,
57 .scalarize_div_trunc_optimized,
58 .scalarize_div_floor,
59 .scalarize_div_floor_optimized,
60 .scalarize_div_ceil,
61 .scalarize_div_ceil_optimized,
62 .scalarize_div_exact,
63 .scalarize_div_exact_optimized,
64 .scalarize_rem,
65 .scalarize_rem_optimized,
66 .scalarize_mod,
67 .scalarize_mod_optimized,
68 .scalarize_max,
69 .scalarize_min,
70 .scalarize_add_with_overflow,
71 .scalarize_sub_with_overflow,
72 .scalarize_mul_with_overflow,
73 .scalarize_shl_with_overflow,
74 .scalarize_bit_and,
75 .scalarize_bit_or,
76 .scalarize_shr,
77 .scalarize_shr_exact,
78 .scalarize_shl,
79 .scalarize_shl_exact,
80 .scalarize_shl_sat,
81 .scalarize_xor,
82 .scalarize_not,
83 .scalarize_clz,
84 .scalarize_ctz,
85 .scalarize_popcount,
86 .scalarize_byte_swap,
87 .scalarize_bit_reverse,
88 .scalarize_sqrt,
89 .scalarize_sin,
90 .scalarize_cos,
91 .scalarize_tan,
92 .scalarize_exp,
93 .scalarize_exp2,
94 .scalarize_log,
95 .scalarize_log2,
96 .scalarize_log10,
97 .scalarize_abs,
98 .scalarize_floor,
99 .scalarize_ceil,
100 .scalarize_round,
101 .scalarize_trunc_float,
102 .scalarize_neg,
103 .scalarize_neg_optimized,
104 .scalarize_cmp_vector,
105 .scalarize_cmp_vector_optimized,
106 .scalarize_fptrunc,
107 .scalarize_fpext,
108 .scalarize_int_cast,
109 .scalarize_ptr_cast,
110 .scalarize_ptr_from_int,
111 .scalarize_int_from_ptr,
112 .scalarize_trunc,
113 .scalarize_int_from_float,
114 .scalarize_int_from_float_optimized,
115 .scalarize_float_from_int,
116 .scalarize_reduce,
117 .scalarize_reduce_optimized,
118 .scalarize_shuffle_one,
119 .scalarize_shuffle_two,
120 .scalarize_select,
121 .scalarize_mul_add,
122
123 .scalarize_bit_cast_padded_elems,
124 });
125}
126
127pub fn generate(
128 bin_file: *link.File,
129 pt: Zcu.PerThread,
130 func_index: InternPool.Index,
131 air: *const Air,
132 liveness: *const ?Air.Liveness,
133) link.Error!Mir {
134 _ = bin_file;
135 const zcu = pt.zcu;
136 const gpa = zcu.gpa;
137 const func = zcu.funcInfo(func_index);
138
139 var cg: CodeGen = .{
140 .gpa = gpa,
141 .pt = pt,
142 .air = air.*,
143 .liveness = liveness.*.?,
144 .owner_nav = func.owner_nav,
145 .func_index = func_index,
146 .mir_instructions = .empty,
147 .mir_extra = .empty,
148 };
149 defer cg.deinit();
150
151 // Generate MIR for function body
152 try cg.genBody(cg.air.getMainBody());
153
154 try cg.mir_extra.shrinkToLen(cg.gpa);
155
156 return .{
157 .instructions = cg.mir_instructions.toOwnedSlice(),
158 .extra = cg.mir_extra.toOwnedSliceAssert(),
159 };
160}
161
162pub fn deinit(cg: *CodeGen) void {
163 cg.* = undefined;
164}
165
166fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) codegen.Error!void {
167 const zcu = cg.pt.zcu;
168 const ip = &zcu.intern_pool;
169
170 for (body) |inst| {
171 if (cg.liveness.isUnused(inst) and !cg.air.mustLower(inst, ip)) continue;
172 try cg.genInst(inst);
173 }
174}
175
176fn genInst(cg: *CodeGen, inst: Air.Inst.Index) codegen.Error!void {
177 const air_tags = cg.air.instructions.items(.tag);
178 return switch (air_tags[@backingInt(inst)]) {
179 .inferred_alloc, .inferred_alloc_comptime => unreachable,
180
181 .add,
182 .add_sat,
183 .add_wrap,
184 .sub,
185 .sub_sat,
186 .sub_wrap,
187 .mul,
188 .mul_sat,
189 .mul_wrap,
190 .div_float,
191 .div_exact,
192 .div_trunc,
193 .div_floor,
194 .bit_and,
195 .bit_or,
196 .rem,
197 .mod,
198 .shl,
199 .shl_exact,
200 .shl_sat,
201 .shr,
202 .shr_exact,
203 .xor,
204 .max,
205 .min,
206 .mul_add,
207
208 .sqrt,
209 .sin,
210 .cos,
211 .tan,
212 .exp,
213 .exp2,
214 .log,
215 .log2,
216 .log10,
217 .floor,
218 .ceil,
219 .round,
220 .trunc_float,
221 .neg,
222
223 .abs,
224
225 .add_with_overflow,
226 .sub_with_overflow,
227 .shl_with_overflow,
228 .mul_with_overflow,
229
230 .clz,
231 .ctz,
232
233 .cmp_eq,
234 .cmp_gte,
235 .cmp_gt,
236 .cmp_lte,
237 .cmp_lt,
238 .cmp_neq,
239
240 .cmp_vector,
241
242 .array_elem_val,
243 .array_to_slice,
244 .alloc,
245 .arg,
246 .block,
247 .breakpoint,
248 .br,
249 .repeat,
250 .switch_dispatch,
251 .cond_br,
252 .fptrunc,
253 .fpext,
254 .int_from_float,
255 .float_from_int,
256 .get_union_tag,
257
258 .@"try",
259 .try_cold,
260 .try_ptr,
261 .try_ptr_cold,
262
263 .dbg_stmt,
264 .dbg_empty_stmt,
265 .dbg_inline_block,
266 .dbg_var_ptr,
267 .dbg_var_val,
268 .dbg_arg_inline,
269
270 .call,
271 .call_always_tail,
272 .call_never_tail,
273 .call_never_inline,
274
275 .is_err,
276 .is_non_err,
277
278 .is_null,
279 .is_non_null,
280 .is_null_ptr,
281 .is_non_null_ptr,
282
283 .load,
284 .loop,
285 .memset,
286 .memset_safe,
287 .not,
288 .optional_payload,
289 .optional_payload_ptr,
290 .optional_payload_ptr_set,
291 .ptr_add,
292 .ptr_sub,
293 .ptr_elem_ptr,
294 .ptr_elem_val,
295 .ret,
296 .ret_safe,
297 .ret_ptr,
298 .ret_load,
299 .splat,
300 .select,
301 .reduce,
302 .aggregate_init,
303 .union_init,
304 .prefetch,
305 .popcount,
306 .byte_swap,
307 .bit_reverse,
308
309 .slice,
310 .slice_len,
311 .slice_elem_val,
312 .slice_elem_ptr,
313 .slice_ptr,
314 .ptr_slice_len_ptr,
315 .ptr_slice_ptr_ptr,
316 .store,
317 .store_safe,
318
319 .set_union_tag,
320 .struct_field_ptr,
321 .struct_field_ptr_index_0,
322 .struct_field_ptr_index_1,
323 .struct_field_ptr_index_2,
324 .struct_field_ptr_index_3,
325 .field_parent_ptr,
326
327 .switch_br,
328 .loop_switch_br,
329 .trunc,
330
331 .wrap_optional,
332 .unwrap_errunion_payload,
333 .unwrap_errunion_payload_ptr,
334 .unwrap_errunion_err,
335 .unwrap_errunion_err_ptr,
336 .wrap_errunion_payload,
337 .wrap_errunion_err,
338 .errunion_payload_ptr_set,
339 .error_name,
340
341 .wasm_memory_size,
342 .wasm_memory_grow,
343
344 .memcpy,
345
346 .ret_addr,
347 .tag_name,
348
349 .error_set_has_value,
350 .frame_addr,
351
352 .is_err_ptr,
353 .is_non_err_ptr,
354
355 .err_return_trace,
356 .set_err_return_trace,
357 .save_err_return_trace_index,
358 .is_named_enum_value,
359 .addrspace_cast,
360 .c_va_arg,
361 .c_va_copy,
362 .c_va_end,
363 .c_va_start,
364 .memmove,
365
366 .atomic_load,
367 .atomic_store_unordered,
368 .atomic_store_monotonic,
369 .atomic_store_release,
370 .atomic_store_seq_cst,
371 .atomic_rmw,
372 .cmpxchg_weak,
373 .cmpxchg_strong,
374
375 .add_optimized,
376 .sub_optimized,
377 .mul_optimized,
378 .div_float_optimized,
379 .div_trunc_optimized,
380 .div_floor_optimized,
381 .div_exact_optimized,
382 .rem_optimized,
383 .mod_optimized,
384 .neg_optimized,
385 .cmp_lt_optimized,
386 .cmp_lte_optimized,
387 .cmp_eq_optimized,
388 .cmp_gte_optimized,
389 .cmp_gt_optimized,
390 .cmp_neq_optimized,
391 .cmp_vector_optimized,
392 .reduce_optimized,
393 .int_from_float_optimized,
394 .add_safe,
395 .sub_safe,
396 .mul_safe,
397 .div_ceil,
398 .div_ceil_optimized,
399 .bit_cast,
400 .bit_cast_safe,
401 .ptr_cast,
402 .ptr_from_int,
403 .int_from_ptr,
404 .error_cast,
405 .error_from_int,
406 .int_from_error,
407 .union_from_enum,
408 .int_cast,
409 .int_cast_safe,
410 .agg_field_val,
411 .array_to_vector,
412 .int_from_float_safe,
413 .int_from_float_optimized_safe,
414 .shuffle_one,
415 .shuffle_two,
416 .cmp_lte_errors_len,
417 .runtime_nav_ptr,
418 .spirv_runtime_array_len,
419 .legalize_vec_store_elem,
420 .legalize_vec_elem_val,
421 .legalize_compiler_rt_call,
422 => |tag| return cg.fail("TODO: implement spork8 inst: {t}", .{tag}),
423
424 .unreach => cg.airUnreachable(inst),
425 .assembly => cg.airAssembly(inst),
426 .trap => cg.airTrap(inst),
427
428 .work_item_id,
429 .work_group_size,
430 .work_group_id,
431 => unreachable,
432 };
433}
434
435fn airUnreachable(cg: *CodeGen, inst: Air.Inst.Index) codegen.Error!void {
436 _ = cg;
437 _ = inst;
438}
439
440fn airTrap(cg: *CodeGen, inst: Air.Inst.Index) codegen.Error!void {
441 _ = inst;
442 try cg.addTag(.halt);
443}
444
445fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) codegen.Error!void {
446 const unwrapped_asm = cg.air.unwrapAsm(inst);
447 const outputs = unwrapped_asm.outputs;
448 // const inputs = unwrapped_asm.inputs;
449
450 const zcu = cg.pt.zcu;
451 // const output_ty = cg.typeOfIndex(inst);
452
453 if (outputs.len != 0) {
454 @panic("TODO: Support assembly outputs");
455 }
456
457 var constValues: std.array_hash_map.String(u8) = .empty;
458 defer constValues.deinit(zcu.gpa);
459 {
460 var it = unwrapped_asm.iterateInputs();
461 while (it.next()) |input| {
462 const constraint = input.constraint;
463 if (!mem.eql(u8, constraint, "I")) {
464 return cg.fail("assembly constraint {q} not supported", .{constraint});
465 }
466 const operand = input.operand.toInterned() orelse {
467 return cg.fail("immediate argument to inline assembly must be compile-time value", .{});
468 };
469 const name = input.name;
470
471 const value = switch (zcu.intern_pool.indexToKey(operand)) {
472 .int => |val| v: {
473 if (val.ty != .u8_type) {
474 return cg.fail("non-u8 type used in inline assembly value: {}", .{val.ty});
475 }
476 break :v val.storage.u64;
477 },
478 else => return cg.fail("non-int operands not supported", .{}),
479 };
480
481 try constValues.put(zcu.gpa, name, @intCast(value));
482 }
483 }
484
485 {
486 var lines = mem.tokenizeScalar(u8, unwrapped_asm.source, '\n');
487 while (lines.next()) |line| {
488 var tokens = mem.tokenizeScalar(u8, line, ' ');
489 // If there's no tokens, then it must be a blank line, so just skip it.
490 const op = tokens.next() orelse continue;
491 const instType = std.meta.stringToEnum(AsmInstType, op) orelse return cg.fail("invalid asm instruction: {q}", .{op});
492 switch (instType) {
493 .LoadI => {
494 const registerString = tokens.next() orelse return cg.fail("missing register for LoadI instruction", .{});
495 const register = std.meta.stringToEnum(Register, registerString) orelse return cg.fail("invalid register: {q}", .{registerString});
496 const value = tokens.next() orelse return cg.fail("missing immediate value for LoadI", .{});
497 const intValue = v: {
498 if (mem.startsWith(u8, value, "%[")) {
499 const name = value[2 .. value.len - 1];
500 break :v constValues.get(name) orelse return cg.fail("constraint name {q} not included in constraints for inline asm", .{name});
501 } else {
502 break :v std.fmt.parseInt(u8, value, 0) catch |err|
503 return cg.fail("invalid LoadI immediate value: {t}", .{err});
504 }
505 };
506 if (register != .OutA) {
507 return cg.fail("TODO: support other variants of LoadI", .{});
508 }
509 try cg.addTagImm8(.load_i_outa, intValue);
510 },
511 else => return cg.fail("TODO: support asm instruction: {t}", .{instType}),
512 }
513 }
514 }
515}
516
517pub fn addInst(cg: *CodeGen, inst: Mir.Inst) error{OutOfMemory}!void {
518 try cg.mir_instructions.append(cg.gpa, inst);
519}
520
521pub fn addTag(cg: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
522 try cg.addInst(.{ .tag = tag, .data = .{ .nothing = {} } });
523}
524
525pub fn addTagImm8(cg: *CodeGen, tag: Mir.Inst.Tag, imm8: u8) error{OutOfMemory}!void {
526 try cg.addInst(.{ .tag = tag, .data = .{ .imm8 = imm8 } });
527}
528
529fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) codegen.Error {
530 const zcu = cg.pt.zcu;
531 const func = zcu.funcInfo(cg.func_index);
532 return zcu.codegenFail(func.owner_nav, fmt, args);
533}
534
535fn extraLen(cg: *const CodeGen) u32 {
536 return @intCast(cg.mir_extra.items.len - cg.start_mir_extra_off);
537}
538
539const AsmInstType = enum(u8) {
540 /// Set the memory address high byte to a register value.
541 SetPageReg,
542 /// Set the memory address high byte to a constant value.
543 SetPageI,
544 /// Set the memory address low byte to a register value.
545 SetAddrReg,
546 /// Set the memory address low byte to a constant value.
547 SetAddrI,
548 /// Load a value from a constant address into a register.
549 Load,
550 /// Load a constant value into a register.
551 LoadI,
552 /// Load a value from a constant address (setting low byte only) into a register.
553 LoadP,
554 /// Load a value from the currently set memory address into a register, and increment the address n times.
555 LoadInc,
556 /// Load a value from an offset on the current stack frame into a register.
557 LoadStck,
558 /// Store a value to a constant address from a register.
559 Store,
560 /// Store a constant value into a constant address.
561 StoreI,
562 /// Store a value to a constant address (low byte only) from a register.
563 StoreP,
564 /// Store a value from the currently set memory address from a register, and increment the address n times.
565 StoreInc,
566 /// Store a value to an offset on the current stack frame, from a register.
567 StoreStck,
568 /// Store a value to an offset on the next stack frame, from a register.
569 StoreNStck,
570 /// Store a value to an offset on the previous stack frame, from a register.
571 StorePStck,
572 /// Store a constant value to an offset on the current stack frame.
573 StoreStckI,
574 /// Store a constant value to an offset on the next stack frame.
575 StoreNStckI,
576 /// Store a constant value to an offset on the previous stack frame.
577 StorePStckI,
578 /// Copy a value from one register to another register.
579 Copy,
580 /// Jump to a constant location.
581 Jump,
582 /// Jump to a register A (high byte) + register B (low byte).
583 JumpReg,
584 /// Jump to a location pointed to by memory at the current memory address (high byte first).
585 JumpMem,
586 /// Call a function.
587 Call,
588 /// Return from a function.
589 Return,
590 /// Compare A to a constant value (sets flags, but discards result).
591 CmpI,
592 /// Compare A to a constant value with bitwise AND (sets flags, but discards result).
593 CmpAndI,
594 /// Compare A to a value from memory (sets flags, but discards result).
595 Cmp,
596 /// Compare A to a value in memory with bitwise AND (sets flags, but discards result).
597 CmpAnd,
598 /// Compare A to a value from a register (sets flags, but discards result).
599 CmpReg,
600 /// Compare A to a value from a register with bitwise AND (sets flags, but discards result).
601 CmpAndReg,
602 /// Shift B left by 1.
603 ShiftL,
604 /// Shift B right by 1.
605 ShiftR,
606 /// Rotate B left by 1.
607 RotateL,
608 /// Rotate B right by 1.
609 RotateR,
610 /// Add a constant value to A.
611 AddI,
612 /// Subtract a constant value from A.
613 SubI,
614 /// Bitwise-AND A with a constant value.
615 AndI,
616 /// Add a constant value to A, without updating flags.
617 AddINF,
618 /// Subtract a constant value from A, without updating flags.
619 SubINF,
620 /// Bitwise-AND A with a constant value, without updating flags.
621 AndINF,
622 /// Add register B to A -> A.
623 AccumulateAdd,
624 /// Subtract register B from A -> A.
625 AccumulateSub,
626 /// A & B -> A.
627 AccumulateAnd,
628 /// Bitwise OR B with A -> A.
629 OrI,
630 /// Bitwise OR a constant value with A -> A.
631 XorI,
632 /// Invert register A.
633 Not,
634 /// Add a value from memory to A.
635 Add,
636 /// Subtract a value from memory from A.
637 Sub,
638 /// AND A with a value from memory.
639 And,
640 /// OR A with a value from memory.
641 Or,
642 /// XOR A with a value from memory.
643 Xor,
644 /// No-op.
645 Nop,
646 /// No-op with 1 extra clock cycle.
647 Nop1,
648 /// No-op with 2 extra clock cycles.
649 Nop2,
650 /// Halt - stop the program forever (until reset).
651 Halt,
652};
653
654const Register = enum(u8) {
655 A,
656 B,
657 C,
658 PCnt,
659 MAdr,
660 Stack,
661 OutA,
662 Shift,
663 Swap,
664};