1//! Air -> Js.Tree
2gpa: Allocator,
3arena: Allocator,
4air: Air,
5ip: *InternPool,
6
7nodes: Node.List,
8extra: dod.ExtraStore(.js_tree, .@"var"),
9extern_symbols: std.AutoArrayHashMapUnmanaged(InternPool.String.WithLen, void),
10top_level_value_deps: std.AutoArrayHashMapUnmanaged(Value, void),
11
12param_count: u32,
13air_node_map: []MapEntry,
14scratch: std.ArrayListUnmanaged(u32),
15next_id: u32,
16next_label: u32,
17
18const Error = error{
19 OutOfMemory,
20};
21
22const MapEntry = union {
23 none: void,
24 node: Node.Index,
25 block: packed struct {
26 used: bool = false,
27 index: u31,
28 },
29};
30
31pub fn init(gpa: Allocator, arena: Allocator, ip: *InternPool, air: Air) !NodeGen {
32 const t = tracer.trace(@src());
33 defer t.end();
34
35 const air_node_map = try arena.alloc(MapEntry, air.insts.len);
36 if (core.runtime_safety) {
37 @memset(air_node_map, .{ .none = {} });
38 }
39 const param_count = air.paramCount();
40 return .{
41 .gpa = gpa,
42 .arena = arena,
43 .air = air,
44 .ip = ip,
45 .param_count = param_count,
46 .nodes = .{},
47 .extern_symbols = .{},
48 .top_level_value_deps = .{},
49 .air_node_map = air_node_map,
50 .extra = .empty,
51 .scratch = .empty,
52 .next_id = param_count,
53 .next_label = 0,
54 };
55}
56
57pub fn deinit(c: *NodeGen) void {
58 const t = tracer.trace(@src());
59 defer t.end();
60
61 defer c.* = undefined;
62 c.nodes.deinit(c.gpa);
63 c.extra.deinit(c.gpa);
64 c.extern_symbols.deinit(c.gpa);
65 c.top_level_value_deps.deinit(c.gpa);
66
67 // arena can deinit the rest
68}
69
70pub fn finalize(c: *NodeGen) !Tree {
71 const t = tracer.trace(@src());
72 defer t.end();
73
74 // todo: errdefer will probably leave memory in a fully invalid state
75 try c.nodes.setCapacity(c.gpa, c.nodes.len);
76
77 var extra = try c.extra.toOwnedStore(c.gpa);
78 errdefer extra.deinit(c.gpa);
79
80 // todo: i dont like this dupe
81 const extern_symbols = try c.gpa.dupe(InternPool.String.WithLen, c.extern_symbols.keys());
82 errdefer c.gpa.free(extern_symbols);
83
84 const top_level_value_deps = try c.gpa.dupe(Value, c.top_level_value_deps.keys());
85 errdefer c.gpa.free(top_level_value_deps);
86
87 defer c.deinit();
88
89 return .{
90 .param_count = c.param_count,
91 .nodes = c.nodes.toOwnedSlice(),
92 .extern_symbols = extern_symbols,
93 .top_level_value_deps = top_level_value_deps,
94 .extra = extra,
95 };
96}
97
98pub fn build(c: *NodeGen) !void {
99 const t = tracer.trace(@src());
100 defer t.end();
101
102 assert(c.air.instTag(.main_block) == .block); // expected Air to start with block
103 const statements = c.air.instData(.block, .main_block).get(&c.air);
104
105 const scratch_top = c.scratch.items.len;
106 defer c.scratch.shrinkRetainingCapacity(scratch_top);
107
108 _ = try c.add(.root, undefined);
109
110 if (statements.len > 0) {
111 for (statements[0..c.param_count], 1..) |stmt, expected_id| {
112 assert(c.air.instTag(stmt) == .arg); // expected Air to start with param nodes
113 assert(@intFromEnum(stmt) == expected_id);
114 c.air_node_map[@intFromEnum(stmt)] = .{ .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }) };
115 }
116
117 for (statements[c.param_count..]) |stmt| {
118 try c.codeGenInst(stmt);
119 }
120 }
121
122 const items = c.scratch.items[scratch_top..];
123 const top = c.extra.list.items.len;
124 try c.extra.list.appendSlice(c.gpa, items);
125 c.nodes.items(.data)[0] = .encode(.root, .{
126 .start = @intCast(top),
127 .len = @intCast(items.len),
128 });
129}
130
131fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
132 const t = tracer.traceFmt(@src(), "#{d} {s}", .{ @intFromEnum(inst), @tagName(c.air.instTag(inst)) });
133 defer t.end();
134
135 switch (c.air.instTag(inst)) {
136 .arg => unreachable, // codegen must skip over these instructions.
137
138 .block => try c.codeGenBlock(inst),
139
140 .memory => {
141 const i = @intFromEnum(inst);
142 const data: Air.Memory = c.air.instData(.memory, inst);
143 const value: Tree.Node.Index.Optional = if (data.init.isVoid())
144 .none
145 else
146 (try c.fetchExpression(data.init)).toOptional();
147
148 try c.addStmt(try c.add(.stmt_declare_local, .{
149 .identifier_index = c.next_id,
150 .init = value,
151 }));
152 const id = try c.add(.local, .{ .id = c.next_id });
153 c.next_id += 1;
154 c.air_node_map[i] = .{ .node = id };
155 },
156
157 inline .gt,
158 .lt,
159 .eq,
160 .ge,
161 .le,
162 .add,
163 .sub,
164 .mul,
165 .div,
166 => |tag| {
167 const data: Air.Bin = c.air.instData(tag, inst);
168 const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{
169 .lhs = try c.fetchExpression(data.lhs),
170 .rhs = try c.fetchExpression(data.rhs),
171 });
172 try c.markExpression(inst, node);
173 },
174
175 .field_array => {
176 const data: Air.Bin = c.air.instData(.field_array, inst);
177 const node = try c.add(.index, .{
178 .lhs = try c.fetchExpression(data.lhs),
179 .rhs = try c.fetchExpression(data.rhs),
180 });
181 try c.markExpression(inst, node);
182 },
183
184 .store => {
185 const data: Air.Store = c.air.instData(.store, inst);
186
187 const target = try c.fetchInstExpression(data.memory);
188
189 if (data.operand.kind == .index) {
190 const index = data.operand.decode().index;
191 switch (c.air.instTag(index)) {
192 inline .add,
193 .sub,
194 .mul,
195 .div,
196 => |tag| {
197 const inner_data: Air.Bin = c.air.instData(tag, index);
198 if (inner_data.lhs.eql(data.memory.toRef())) {
199 if ((tag == .add or tag == .sub) and
200 inner_data.rhs.kind == .value)
201 {
202 // if (inner_data.rhs.decode().value.toFloat(c.ip)) |f| {
203 // if (f == 1) {
204 // // convert this into ++ or --
205 // const node = try c.add(
206 // if (tag == .add)
207 // .pre_inc
208 // else
209 // .pre_dec,
210 // .{
211 // .operand = target,
212 // },
213 // );
214 // try c.markExpression(inst, node);
215
216 // return;
217 // }
218 // }
219 }
220
221 // convert into a compound assignment
222 const node = try c.add(
223 @field(Tree.Tag, "assign_" ++ @tagName(tag)),
224 .{
225 .lhs = target,
226 .rhs = try c.fetchExpression(inner_data.rhs),
227 },
228 );
229 try c.markExpression(inst, node);
230
231 return;
232 }
233 },
234 else => {},
235 }
236 }
237
238 const node = try c.add(.assign, .{
239 .lhs = target,
240 .rhs = try c.fetchExpression(data.operand),
241 });
242 try c.markExpression(inst, node);
243 },
244
245 inline .negate,
246 .bool_not,
247 => |tag| {
248 const data: Air.Un = c.air.instData(tag, inst);
249 const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{
250 .operand = try c.fetchExpression(data.operand),
251 });
252 try c.markExpression(inst, node);
253 },
254
255 .panic => {
256 const data: Air.Un = c.air.instData(.panic, inst);
257 const string = try c.fetchExpression(data.operand);
258
259 const error_constructor_string = (try c.ip.getOrPutString(c.gpa, "Error", .maybe_embedded_nulls)).withLen("Error".len);
260 try c.extern_symbols.put(c.gpa, error_constructor_string, {});
261
262 try c.extra.list.ensureUnusedCapacity(c.gpa, 1);
263 const start = c.extra.list.items.len;
264 c.extra.list.appendAssumeCapacity(@intFromEnum(string));
265
266 try c.addStmt(try c.add(.stmt_throw, .{
267 .operand = try c.add(.js_new, .{
268 .base = try c.add(.global, .{ .name = error_constructor_string }),
269 .args = try c.extra.append(c.gpa, Tree.IndexList, .{
270 .start = @intCast(start),
271 .len = 1,
272 }),
273 }),
274 }));
275 },
276
277 .field_opaque => {
278 const data: Air.Bin = c.air.instData(.field_opaque, inst);
279 if (data.rhs.value()) |field_value| {
280 if (field_value.typeOf(c.ip) == .string) {
281 const node = try c.add(.field, .{
282 .base = try c.fetchExpression(data.lhs),
283 .field = field_value.asString(c.ip).tryToNullTerminatedString(c.ip, c.gpa) catch @panic("TODO"),
284 });
285 try c.markExpression(inst, node);
286 return;
287 }
288 }
289
290 const node = try c.add(.index, .{
291 .lhs = try c.fetchExpression(data.lhs),
292 .rhs = try c.fetchExpression(data.rhs),
293 });
294 try c.markExpression(inst, node);
295 },
296
297 .field_struct => {
298 const data: Air.FieldStruct = c.air.instData(.field_struct, inst);
299 const node = try c.add(.field, .{
300 .base = try c.fetchInstExpression(data.base),
301 .field = field_name: {
302 break :field_name c.air.typeOfIndex(data.base, c.ip)
303 .toStructType(c.ip).?
304 .field_keys.at(c.ip, data.field_index);
305 },
306 });
307 try c.markExpression(inst, node);
308 },
309
310 .field_tuple => {
311 const data: Air.FieldStruct = c.air.instData(.field_tuple, inst);
312 const node = try c.add(.index, .{
313 .lhs = try c.fetchInstExpression(data.base),
314 .rhs = try c.makeValueInst(.{ .int32 = @intCast(data.field_index) }),
315 });
316 try c.markExpression(inst, node);
317 },
318
319 .array_len => {
320 const data: Air.Un = c.air.instData(.array_len, inst);
321 const node = try c.add(.field, .{
322 .base = try c.fetchInstExpression(
323 data.operand.index() orelse
324 unreachable, // TODO: make a second version of Un
325 ),
326 .field = try c.ip.getOrPutString(c.gpa, "length", .no_embedded_nulls),
327 });
328 try c.markExpression(inst, node);
329 },
330
331 inline .call, .js_new => |tag| {
332 const data: Air.Call = c.air.instData(tag, inst);
333 const call_data = data.call_data.get(&c.air);
334
335 try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len);
336 const start = c.extra.list.items.len;
337 c.extra.list.items.len += call_data.args.len;
338
339 const items = c.extra.list.items[start..][0..call_data.args.len];
340
341 for (call_data.args.get(&c.air), items) |arg, *dest| {
342 dest.* = @intFromEnum(try c.fetchExpression(arg));
343 }
344
345 const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{
346 .base = try c.fetchExpression(data.base),
347 .args = try c.extra.append(c.gpa, Tree.IndexList, .{
348 .start = @intCast(start),
349 .len = call_data.args.len,
350 }),
351 });
352 try c.markExpression(inst, node);
353 },
354
355 .@"if" => {
356 const data: Air.If = c.air.instData(.@"if", inst);
357 const extra = data.data.get(&c.air);
358 var cond = try c.fetchExpression(data.cond);
359
360 if (c.canLowerToExpression(extra.yes) and
361 c.canLowerToExpression(extra.no))
362 {
363 if (extra.no.value()) |no_value| {
364 if (no_value == .bool_false) {
365 const yes = try c.lowerToExpression(extra.yes);
366
367 const conditional = try c.add(.@"and", .{
368 .lhs = cond,
369 .rhs = yes,
370 });
371 try c.markExpression(inst, conditional);
372 return;
373 }
374 }
375
376 if (extra.yes.value()) |yes_value| {
377 if (yes_value == .bool_true) {
378 const no = try c.lowerToExpression(extra.no);
379
380 const conditional = try c.add(.@"or", .{
381 .lhs = cond,
382 .rhs = no,
383 });
384 try c.markExpression(inst, conditional);
385 return;
386 }
387 }
388
389 const yes = try c.lowerToExpression(extra.yes);
390 const no = try c.lowerToExpression(extra.no);
391
392 const ternary = try c.add(.ternary, .{
393 .cond = cond,
394 .data = try c.extra.append(c.gpa, Tree.Ternary.Data, .{
395 .yes = yes,
396 .no = no,
397 }),
398 });
399
400 try c.markExpression(inst, ternary);
401 } else {
402 const flags = c.air.instFlags(inst);
403 const uses = flags.uses;
404
405 const target = if (uses != .none)
406 try c.addTemporary()
407 else
408 null;
409
410 var yes = try c.codeGenBranch(extra.yes, target);
411 var no = try c.codeGenBranch(extra.no, target);
412 assert(!((yes == null) and (no == null))); // no "void else void" branches
413
414 // when `yes` is void, swap the condition so that the `else` is omitted
415 if (yes == null) {
416 const temp_yes = yes;
417 yes = no;
418 no = temp_yes;
419 cond = try c.add(.bool_not, .{
420 .operand = cond,
421 });
422 }
423
424 const main_expr = try c.add(.stmt_if, .{
425 .cond = cond,
426 .data = try c.extra.append(c.gpa, Tree.StmtIf.Data, .{
427 .yes = yes.?.list,
428 .no = if (no) |branch|
429 branch.list
430 else
431 .{ .start = 0, .len = 0 },
432 .flags = .{
433 .block_label = @intCast(c.next_label + 1),
434 .yes_label = yes.?.requires_label,
435 .no_label = if (no) |branch| branch.requires_label else false,
436 },
437 }),
438 });
439
440 try c.addStmt(main_expr);
441
442 if (target) |node| {
443 try c.markExpression(inst, node);
444 }
445 }
446 },
447
448 .@"break" => {
449 const brk: Air.Break = c.air.instData(.@"break", inst);
450
451 // break #0 -> return
452 if (brk.block == .main_block) {
453 if (brk.value.isVoid()) {
454 // If this is a void break at the end of the main block, nothing
455 // needs to be emitted as that is already what happens at the end
456 // of a block.
457 //
458 // This is also required for the main function, which cannot return
459 // anything, so this prevents that invalid codegen.
460 const main_insts = c.air.instData(.block, .main_block).get(&c.air);
461 if (main_insts[main_insts.len - 1] == inst) {
462 return;
463 }
464
465 try c.addStmt(try c.add(.stmt_return, .{ .operand = .none }));
466 } else {
467 try c.addStmt(try c.add(.stmt_return, .{
468 .operand = (try c.fetchExpression(brk.value)).toOptional(),
469 }));
470 }
471 } else {
472 const operand = try c.fetchExpression(brk.value);
473 const block = &c.air_node_map[@intFromEnum(brk.block)].block;
474
475 const block_insts = c.air.instData(.block, brk.block).get(&c.air);
476 if (block_insts[block_insts.len - 1] != inst) {
477 block.used = true;
478 }
479
480 const assignment: Node.Index.Optional = @enumFromInt(c.scratch.items[block.index]);
481 if (assignment.unwrap()) |target| {
482 try c.addStmt(try c.add(.assign, .{
483 .lhs = target,
484 .rhs = operand,
485 }));
486 }
487
488 try c.addStmt(try c.add(.stmt_break, .{
489 .label = c.scratch.items[block.index + 1],
490 }));
491 }
492 },
493
494 .struct_init => {
495 const struct_init: Air.StructInit = c.air.instData(.struct_init, inst);
496 const struct_type = struct_init.ty.toStructType(c.ip).?;
497
498 const property_count: u32 = @intCast(struct_type.field_types.len);
499 const store_offset = try c.extra.alloc(c.gpa, property_count * 2);
500
501 for (
502 struct_type.field_keys.get(c.ip),
503 struct_init.valuesPtr(&c.air),
504 store_offset..,
505 store_offset + property_count..,
506 ) |key, value, key_i, value_i| {
507 const key_encoded = Tree.ObjectLiteral.Key.Decoded.encode(.{ .static = key });
508 c.extra.list.items[key_i] = @bitCast(key_encoded);
509 const in = try c.fetchExpression(value);
510 c.extra.list.items[value_i] = @intFromEnum(in);
511 }
512
513 const node = try c.add(.object_literal, .{
514 .extra_index = store_offset,
515 .property_count = property_count,
516 });
517 try c.markExpression(inst, node);
518 },
519
520 .array_init => {
521 const array_init: Air.ArrayInit = c.air.instData(.array_init, inst);
522
523 const items = array_init.list.get(&c.air).get(&c.air);
524 const store_offset = try c.extra.alloc(c.gpa, items.len);
525
526 for (
527 items,
528 store_offset..,
529 ) |item, item_i| {
530 const in = try c.fetchExpression(item);
531 c.extra.list.items[item_i] = @intFromEnum(in);
532 }
533
534 const node = try c.add(.array, .{
535 .start = store_offset,
536 .len = @intCast(items.len),
537 });
538 try c.markExpression(inst, node);
539 },
540
541 .tuple_init => {
542 const tuple_init: Air.TupleInit = c.air.instData(.tuple_init, inst);
543
544 const field_count = tuple_init.ty.toTupleType(c.ip).?.items.len;
545 const items = tuple_init.field_values;
546 const store_offset = try c.extra.alloc(c.gpa, field_count);
547
548 for (
549 items..items + field_count,
550 store_offset..,
551 ) |in_i, expr_i| {
552 const in = try c.fetchExpression(@bitCast(c.air.extra.list.items[in_i]));
553 c.extra.list.items[expr_i] = @intFromEnum(in);
554 }
555
556 const node = try c.add(.array, .{
557 .start = store_offset,
558 .len = @intCast(field_count),
559 });
560 try c.markExpression(inst, node);
561 },
562 }
563}
564
565fn markExpression(c: *NodeGen, inst: Air.Inst.Index, node: Node.Index) !void {
566 const t = tracer.trace(@src());
567 defer t.end();
568
569 const flags: Air.Flags = c.air.instFlags(inst);
570 const i = @intFromEnum(inst);
571 switch (flags.uses) {
572 .none => {
573 try c.addStmt(node);
574 },
575 .single => {
576 // TODO: this is not aware of side effect fences
577 c.air_node_map[i] = .{ .node = node };
578 },
579 .multi => {
580 const decl = try c.add(.stmt_declare_local, .{
581 .identifier_index = c.next_id,
582 .init = node.toOptional(),
583 });
584 try c.addStmt(decl);
585 const id = try c.add(.local, .{ .id = c.next_id });
586 c.next_id += 1;
587 c.air_node_map[i] = .{ .node = id };
588 },
589 }
590}
591
592fn fetchExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index {
593 const t = tracer.trace(@src());
594 defer t.end();
595
596 return switch (ref.decode()) {
597 .value => |v| c.valueToExpression(v),
598 .index => |index| fetchInstExpression(c, index),
599 };
600}
601
602fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index {
603 const t = tracer.trace(@src());
604 defer t.end();
605
606 const data = c.air_node_map[@intFromEnum(index)];
607 if (core.runtime_safety) {
608 const flags: Air.Flags = c.air.instFlags(index);
609 switch (flags.uses) {
610 .none => {
611 // incorrectly marked instruction as unused
612 std.debug.panic("called fetchInstExpression(#{d}), but it was marked as unused", .{
613 @intFromEnum(index),
614 });
615 },
616 .single => {},
617 .multi => {
618 // multi-references can only duplicate identifier nodes.
619 assert(switch (c.nodes.items(.tag)[@intFromEnum(data.node)]) {
620 .local, .global => true,
621 else => false,
622 });
623 },
624 }
625 }
626 return data.node;
627}
628
629fn valueToExpression(c: *NodeGen, v: Value) !Node.Index {
630 const t = tracer.trace(@src());
631 defer t.end();
632
633 const intern_tag = v.valueTag(c.ip);
634 switch (intern_tag) {
635 .removed => unreachable, // UAF
636
637 .function_type,
638 .simple_type,
639 .struct_type,
640 .array_type,
641 .tuple_type,
642 => unreachable, // Type values are not allowed here
643
644 .int32,
645 .float,
646 .comptime_float,
647 .string,
648 .simple_value,
649 .struct_value,
650 .array_value,
651 .tuple_value,
652 => return try c.add(.plain_value, .{ .value = v }),
653
654 .external_value => {
655 const external = v.toKey(c.ip).external_value;
656 const first_key = external.key.at(c.ip, 0);
657 try c.extern_symbols.put(
658 c.gpa,
659 first_key.toString().withLen(first_key.length(c.ip)),
660 {},
661 );
662 return try c.add(.plain_value, .{ .value = v });
663 },
664
665 .opaque_value => return c.valueToExpression(v.toKey(c.ip).opaque_value),
666
667 // Hoist functions as top level values
668 // TODO: Consider hoisting large values too
669 .func => {
670 try c.top_level_value_deps.put(c.gpa, v, {});
671 return try c.add(.top_level_value, .{ .value = v });
672 },
673 }
674}
675
676fn makeValueInst(c: *NodeGen, k: InternPool.Key) !Node.Index {
677 return try c.valueToExpression(c.ip.get(c.gpa, k) catch |err| switch (err) {
678 error.TooManyValues => @panic("TODO: handle TooManyValues"),
679 else => |e| return e,
680 });
681}
682
683fn addTemporary(c: *NodeGen) !Node.Index {
684 const t = tracer.trace(@src());
685 defer t.end();
686
687 try c.addStmt(try c.add(.stmt_declare_local, .{
688 .identifier_index = c.next_id,
689 .init = .none,
690 }));
691 const id = try c.add(.local, .{ .id = c.next_id });
692 c.next_id += 1;
693 return id;
694}
695
696const Branch = struct {
697 requires_label: bool,
698 list: Tree.IndexList,
699};
700
701fn codeGenBranch(c: *NodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Branch {
702 const t = tracer.trace(@src());
703 defer t.end();
704
705 const scratch_top = c.scratch.items.len;
706 defer c.scratch.shrinkRetainingCapacity(scratch_top);
707
708 c.next_label += 1;
709 defer c.next_label -= 1;
710 const this_label = c.next_label;
711
712 var requires_label = false;
713 var items = switch (inst.decode()) {
714 .value => |v| items: {
715 if (v == .void)
716 return null;
717 if (temporary) |temp| {
718 const node = try c.valueToExpression(v);
719 try c.addStmt(try c.add(.assign, .{
720 .lhs = temp,
721 .rhs = node,
722 }));
723 }
724 break :items c.scratch.items[scratch_top..];
725 },
726 .index => |i| items: {
727 assert(c.air.instTag(i) == .block);
728 c.air_node_map[@intFromEnum(i)] = .{
729 .block = .{ .index = @intCast(scratch_top) },
730 };
731
732 try c.scratch.appendSlice(c.arena, &.{
733 @intFromEnum(if (temporary) |temp| temp.toOptional() else .none),
734 this_label,
735 });
736
737 const block: Air.IndexList = c.air.instData(.block, i);
738 for (block.get(&c.air)) |item| {
739 try c.codeGenInst(item);
740 }
741
742 requires_label = c.air_node_map[@intFromEnum(i)].block.used;
743
744 break :items c.scratch.items[scratch_top + 2 ..];
745 },
746 };
747
748 const last_node = c.scratch.items[c.scratch.items.len - 1];
749 if (c.nodes.items(.tag)[last_node] == .stmt_break and
750 c.nodes.items(.data)[last_node].decode(.stmt_break, .stmt_break).label == this_label)
751 {
752 items.len -= 1;
753 c.nodes.len -= 1;
754 }
755
756 const top = c.extra.list.items.len;
757 try c.extra.list.appendSlice(c.gpa, items);
758 return .{
759 .requires_label = requires_label,
760 .list = .{
761 .start = @intCast(top),
762 .len = @intCast(items.len),
763 },
764 };
765}
766
767fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void {
768 const t = tracer.trace(@src());
769 defer t.end();
770
771 const scratch_top = c.scratch.items.len;
772 errdefer c.scratch.shrinkRetainingCapacity(scratch_top);
773
774 c.next_label += 1;
775 defer c.next_label -= 1;
776
777 const next_label = c.next_label;
778
779 try c.scratch.appendSlice(c.arena, &.{
780 @intFromEnum(Node.Index.Optional.none),
781 next_label,
782 });
783
784 c.air_node_map[@intFromEnum(inst)] = .{
785 .block = .{ .index = @intCast(scratch_top) },
786 };
787 const block: Air.IndexList = c.air.instData(.block, inst);
788
789 for (block.get(&c.air)) |item| {
790 try c.codeGenInst(item);
791 }
792
793 const items = c.scratch.items[scratch_top + 2 ..];
794 const top = c.extra.list.items.len;
795 try c.extra.list.appendSlice(c.gpa, items);
796 c.scratch.shrinkRetainingCapacity(scratch_top);
797 try c.addStmt(try c.add(.stmt_block, .{
798 .label = c.next_label,
799 .content = try c.extra.append(c.gpa, Tree.IndexList, .{
800 .start = @intCast(top),
801 .len = @intCast(items.len),
802 }),
803 }));
804}
805
806fn codeGenBlockAsExpression(c: *NodeGen, inst: Air.Inst.Index) !Node.Index {
807 const t = tracer.trace(@src());
808 defer t.end();
809
810 const scratch_top = c.scratch.items.len;
811 errdefer c.scratch.shrinkRetainingCapacity(scratch_top);
812
813 try c.scratch.appendSlice(c.arena, &.{
814 @intFromEnum(Node.Index.Optional.none),
815 0,
816 });
817
818 c.air_node_map[@intFromEnum(inst)] = .{
819 .block = .{ .index = @intCast(scratch_top) },
820 };
821 const block: Air.IndexList = c.air.instData(.block, inst);
822 const instructions = block.get(&c.air);
823 for (instructions[0 .. instructions.len - 1]) |item| {
824 try c.codeGenInst(item);
825 }
826
827 const last_break: Air.Break = c.air.instData(.@"break", instructions[instructions.len - 1]);
828 const last_break_operand = try c.fetchExpression(last_break.value);
829
830 try c.scratch.append(c.arena, @intFromEnum(last_break_operand));
831
832 const items = c.scratch.items[scratch_top + 2 ..];
833 const top = c.extra.list.items.len;
834 defer c.scratch.shrinkRetainingCapacity(scratch_top);
835 assert(items.len > 0);
836 if (items.len == 1) {
837 return @enumFromInt(items[0]);
838 }
839 try c.extra.list.appendSlice(c.gpa, items);
840 return try c.add(.comma_chain, .{
841 .start = @intCast(top),
842 .len = @intCast(items.len),
843 });
844}
845
846// Ideally, we convert everything to an expression. This allows for much
847// more compact code. When that isnt possible (`return`, `while`, etc),
848// we detect that here and ensure to lower `if` to actual if statements,
849// and so on.
850fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
851 const t = tracer.trace(@src());
852 defer t.end();
853
854 return switch (inst.decode()) {
855 .value => true,
856 .index => |index| switch (c.air.instTag(index)) {
857 .call,
858 .js_new,
859 .arg,
860 .field_opaque,
861 .field_struct,
862 .field_tuple,
863 .field_array,
864 .add,
865 .sub,
866 .mul,
867 .div,
868 .eq,
869 .lt,
870 .gt,
871 .ge,
872 .le,
873 .negate,
874 .bool_not,
875 .store,
876 .struct_init,
877 .array_init,
878 .array_len,
879 .tuple_init,
880 => true,
881
882 // JavaScript variable declarations require statements.
883 // Later, this could be made true if we can hoist temporaries up.
884 .memory => false,
885
886 // these must be lowered to js statements
887 .@"break", .panic => false,
888
889 // A block can become a comma expression if all expressions
890 // are lowerable. The break at the end just becomes the result.
891 .block => {
892 const block: Air.IndexList = c.air.instData(.block, index);
893 const items = block.get(&c.air);
894 for (items[0 .. items.len - 1]) |item| {
895 if (c.air.instFlags(item).uses == .multi)
896 return false;
897 if (!c.canLowerToExpression(item.toRef()))
898 return false;
899 }
900 const last_item = items[items.len - 1];
901 if (c.air.instTag(last_item) != .@"break") {
902 return c.canLowerToExpression(last_item.toRef());
903 }
904 const brk = c.air.instData(.@"break", last_item);
905 // The last break will only be lowerable if it
906 // breaks out of this specific block
907 return brk.block == index;
908 },
909
910 .@"if" => {
911 const call = c.air.instData(.@"if", index);
912 const data = call.data.get(&c.air);
913
914 return c.canLowerToExpression(call.cond) and
915 c.canLowerToExpression(data.yes) and
916 c.canLowerToExpression(data.no);
917 },
918 },
919 };
920}
921
922fn lowerToExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index {
923 const t = tracer.trace(@src());
924 defer t.end();
925
926 assert(c.canLowerToExpression(ref));
927 return switch (ref.decode()) {
928 .value => |v| try c.valueToExpression(v),
929 .index => |index| {
930 if (c.air.instTag(index) == .block) {
931 return try c.codeGenBlockAsExpression(index);
932 }
933 try c.codeGenInst(index);
934 return c.fetchExpression(ref);
935 },
936 };
937}
938
939fn add(c: *NodeGen, comptime tag: Tree.Tag, data: tag.Data()) !Node.Index {
940 const t = tracer.trace(@src());
941 defer t.end();
942
943 try c.nodes.append(c.gpa, .{
944 .tag = tag,
945 .data = .encode(tag, data),
946 });
947 return @enumFromInt(c.nodes.len - 1);
948}
949
950fn addStmt(c: *NodeGen, stmt: Node.Index) !void {
951 const t = tracer.trace(@src());
952 defer t.end();
953
954 try c.scratch.append(c.arena, @intFromEnum(stmt));
955}
956
957const NodeGen = @This();
958
959const std = @import("std");
960const assert = std.debug.assert;
961const Allocator = std.mem.Allocator;
962
963const core = @import("../mod.zig");
964const InternPool = core.InternPool;
965const Value = InternPool.Value;
966const NullTerminatedString = InternPool.NullTerminatedString;
967const Comp = core.Comp;
968const Decl = Comp.Decl;
969const Tree = core.Js.Tree;
970const EncodedData = Tree.EncodedData;
971const Node = Tree.Node;
972const LocalIndex = Tree.LocalIndex;
973const Air = core.Air;
974const dod = core.array;
975
976const tracer = @import("tracer");