aboutsummaryrefslogtreecommitdiff
path: root/deps/aro/CodeGen.zig
diff options
context:
space:
mode:
authorVeikka Tuominen <git@vexu.eu>2023-10-02 07:08:53 +0300
committerGitHub <noreply@github.com>2023-10-02 07:08:53 +0300
commitfc4d53e2ea6b41440e37caf32d2fd236d0f58c93 (patch)
treebe400bc7033d3f198978ad04c05c14f15b8c5324 /deps/aro/CodeGen.zig
parent0f1652dc603ad43be733cfdd721cedf38d9e45d9 (diff)
parent5792570197f44b2c7599fb756f5c1e9d59bd0a9a (diff)
downloadzig-fc4d53e2ea6b41440e37caf32d2fd236d0f58c93.tar.gz
zig-fc4d53e2ea6b41440e37caf32d2fd236d0f58c93.zip
Merge pull request #17221 from Vexu/aro-translate-c
Aro translate-c
Diffstat (limited to 'deps/aro/CodeGen.zig')
-rw-r--r--deps/aro/CodeGen.zig1291
1 files changed, 1291 insertions, 0 deletions
diff --git a/deps/aro/CodeGen.zig b/deps/aro/CodeGen.zig
new file mode 100644
index 0000000000..6febbbb917
--- /dev/null
+++ b/deps/aro/CodeGen.zig
@@ -0,0 +1,1291 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
+const BuiltinFunction = @import("builtins/BuiltinFunction.zig");
+const Compilation = @import("Compilation.zig");
+const Interner = @import("Interner.zig");
+const Ir = @import("Ir.zig");
+const Builder = Ir.Builder;
+const StringId = @import("StringInterner.zig").StringId;
+const Tree = @import("Tree.zig");
+const NodeIndex = Tree.NodeIndex;
+const Type = @import("Type.zig");
+const Value = @import("Value.zig");
+
+const CodeGen = @This();
+
+const WipSwitch = struct {
+ cases: Cases = .{},
+ default: ?Ir.Ref = null,
+ size: u64,
+
+ const Cases = std.MultiArrayList(struct {
+ val: Interner.Ref,
+ label: Ir.Ref,
+ });
+};
+
+const Symbol = struct {
+ name: StringId,
+ val: Ir.Ref,
+};
+
+const Error = Compilation.Error;
+
+tree: Tree,
+comp: *Compilation,
+builder: Builder,
+node_tag: []const Tree.Tag,
+node_data: []const Tree.Node.Data,
+node_ty: []const Type,
+wip_switch: *WipSwitch = undefined,
+symbols: std.ArrayListUnmanaged(Symbol) = .{},
+ret_nodes: std.ArrayListUnmanaged(Ir.Inst.Phi.Input) = .{},
+phi_nodes: std.ArrayListUnmanaged(Ir.Inst.Phi.Input) = .{},
+record_elem_buf: std.ArrayListUnmanaged(Interner.Ref) = .{},
+cond_dummy_ty: ?Interner.Ref = null,
+bool_invert: bool = false,
+bool_end_label: Ir.Ref = .none,
+cond_dummy_ref: Ir.Ref = undefined,
+continue_label: Ir.Ref = undefined,
+break_label: Ir.Ref = undefined,
+return_label: Ir.Ref = undefined,
+
+pub fn generateTree(comp: *Compilation, tree: Tree) Compilation.Error!void {
+ var c = CodeGen{
+ .builder = .{
+ .gpa = comp.gpa,
+ .arena = std.heap.ArenaAllocator.init(comp.gpa),
+ },
+ .tree = tree,
+ .comp = comp,
+ .node_tag = tree.nodes.items(.tag),
+ .node_data = tree.nodes.items(.data),
+ .node_ty = tree.nodes.items(.ty),
+ };
+ defer c.symbols.deinit(c.comp.gpa);
+ defer c.ret_nodes.deinit(c.comp.gpa);
+ defer c.phi_nodes.deinit(c.comp.gpa);
+ defer c.record_elem_buf.deinit(c.comp.gpa);
+ defer c.builder.deinit();
+
+ const node_tags = tree.nodes.items(.tag);
+ for (tree.root_decls) |decl| {
+ c.builder.arena.deinit();
+ c.builder.arena = std.heap.ArenaAllocator.init(comp.gpa);
+
+ switch (node_tags[@intFromEnum(decl)]) {
+ .static_assert,
+ .typedef,
+ .struct_decl_two,
+ .union_decl_two,
+ .enum_decl_two,
+ .struct_decl,
+ .union_decl,
+ .enum_decl,
+ => {},
+
+ .fn_proto,
+ .static_fn_proto,
+ .inline_fn_proto,
+ .inline_static_fn_proto,
+ .extern_var,
+ .threadlocal_extern_var,
+ => {},
+
+ .fn_def,
+ .static_fn_def,
+ .inline_fn_def,
+ .inline_static_fn_def,
+ => c.genFn(decl) catch |err| switch (err) {
+ error.FatalError => return error.FatalError,
+ error.OutOfMemory => return error.OutOfMemory,
+ },
+
+ .@"var",
+ .static_var,
+ .threadlocal_var,
+ .threadlocal_static_var,
+ => c.genVar(decl) catch |err| switch (err) {
+ error.FatalError => return error.FatalError,
+ error.OutOfMemory => return error.OutOfMemory,
+ },
+ else => unreachable,
+ }
+ }
+}
+
+fn genType(c: *CodeGen, base_ty: Type) !Interner.Ref {
+ var key: Interner.Key = undefined;
+ const ty = base_ty.canonicalize(.standard);
+ switch (ty.specifier) {
+ .void => return .void,
+ .bool => return .i1,
+ .@"struct" => {
+ key = .{
+ .record = .{
+ .user_ptr = ty.data.record,
+ .elements = undefined, // Not needed for hash lookup.
+ },
+ };
+ if (c.builder.pool.has(key)) |some| return some;
+ const elem_buf_top = c.record_elem_buf.items.len;
+ defer c.record_elem_buf.items.len = elem_buf_top;
+
+ for (ty.data.record.fields) |field| {
+ if (!field.isRegularField()) {
+ return c.comp.diag.fatalNoSrc("TODO lower struct bitfields", .{});
+ }
+ // TODO handle padding bits
+ const field_ref = try c.genType(field.ty);
+ try c.record_elem_buf.append(c.builder.gpa, field_ref);
+ }
+
+ key.record.elements = try c.builder.arena.allocator().dupe(Interner.Ref, c.record_elem_buf.items[elem_buf_top..]);
+ return c.builder.pool.put(c.builder.gpa, key);
+ },
+ .@"union" => {
+ return c.comp.diag.fatalNoSrc("TODO lower union types", .{});
+ },
+ else => {},
+ }
+ if (ty.isPtr()) return .ptr;
+ if (ty.isFunc()) return .func;
+ if (!ty.isReal()) return c.comp.diag.fatalNoSrc("TODO lower complex types", .{});
+ if (ty.isInt()) {
+ const bits = ty.bitSizeof(c.comp).?;
+ key = .{ .int = @intCast(bits) };
+ } else if (ty.isFloat()) {
+ const bits = ty.bitSizeof(c.comp).?;
+ key = .{ .float = @intCast(bits) };
+ } else if (ty.isArray()) {
+ const elem = try c.genType(ty.elemType());
+ key = .{ .array = .{ .child = elem, .len = ty.arrayLen().? } };
+ } else if (ty.specifier == .vector) {
+ const elem = try c.genType(ty.elemType());
+ key = .{ .vector = .{ .child = elem, .len = @intCast(ty.data.array.len) } };
+ } else if (ty.is(.nullptr_t)) {
+ return c.comp.diag.fatalNoSrc("TODO lower nullptr_t", .{});
+ }
+ return c.builder.pool.put(c.builder.gpa, key);
+}
+
+fn genFn(c: *CodeGen, decl: NodeIndex) Error!void {
+ const name = c.tree.tokSlice(c.node_data[@intFromEnum(decl)].decl.name);
+ const func_ty = c.node_ty[@intFromEnum(decl)].canonicalize(.standard);
+ c.ret_nodes.items.len = 0;
+
+ try c.builder.startFn();
+
+ for (func_ty.data.func.params) |param| {
+ // TODO handle calling convention here
+ const arg = try c.builder.addArg(try c.genType(param.ty));
+
+ const size: u32 = @intCast(param.ty.sizeof(c.comp).?); // TODO add error in parser
+ const @"align" = param.ty.alignof(c.comp);
+ const alloc = try c.builder.addAlloc(size, @"align");
+ try c.builder.addStore(alloc, arg);
+ try c.symbols.append(c.comp.gpa, .{ .name = param.name, .val = alloc });
+ }
+
+ // Generate body
+ c.return_label = try c.builder.makeLabel("return");
+ try c.genStmt(c.node_data[@intFromEnum(decl)].decl.node);
+
+ // Relocate returns
+ if (c.ret_nodes.items.len == 0) {
+ _ = try c.builder.addInst(.ret, .{ .un = .none }, .noreturn);
+ } else if (c.ret_nodes.items.len == 1) {
+ c.builder.body.items.len -= 1;
+ _ = try c.builder.addInst(.ret, .{ .un = c.ret_nodes.items[0].value }, .noreturn);
+ } else {
+ try c.builder.startBlock(c.return_label);
+ const phi = try c.builder.addPhi(c.ret_nodes.items, try c.genType(func_ty.returnType()));
+ _ = try c.builder.addInst(.ret, .{ .un = phi }, .noreturn);
+ }
+
+ var res = Ir{
+ .pool = c.builder.pool,
+ .instructions = c.builder.instructions,
+ .arena = c.builder.arena.state,
+ .body = c.builder.body,
+ .strings = c.tree.strings,
+ };
+ res.dump(c.builder.gpa, name, c.comp.diag.color, std.io.getStdOut().writer()) catch {};
+}
+
+fn addUn(c: *CodeGen, tag: Ir.Inst.Tag, operand: Ir.Ref, ty: Type) !Ir.Ref {
+ return c.builder.addInst(tag, .{ .un = operand }, try c.genType(ty));
+}
+
+fn addBin(c: *CodeGen, tag: Ir.Inst.Tag, lhs: Ir.Ref, rhs: Ir.Ref, ty: Type) !Ir.Ref {
+ return c.builder.addInst(tag, .{ .bin = .{ .lhs = lhs, .rhs = rhs } }, try c.genType(ty));
+}
+
+fn addBranch(c: *CodeGen, cond: Ir.Ref, true_label: Ir.Ref, false_label: Ir.Ref) !void {
+ if (true_label == c.bool_end_label) {
+ if (false_label == c.bool_end_label) {
+ try c.phi_nodes.append(c.comp.gpa, .{ .label = c.builder.current_label, .value = cond });
+ return;
+ }
+ try c.addBoolPhi(!c.bool_invert);
+ }
+ if (false_label == c.bool_end_label) {
+ try c.addBoolPhi(c.bool_invert);
+ }
+ return c.builder.addBranch(cond, true_label, false_label);
+}
+
+fn addBoolPhi(c: *CodeGen, value: bool) !void {
+ const val = try c.builder.addConstant(Value.int(@intFromBool(value)), .i1);
+ try c.phi_nodes.append(c.comp.gpa, .{ .label = c.builder.current_label, .value = val });
+}
+
+fn genStmt(c: *CodeGen, node: NodeIndex) Error!void {
+ _ = try c.genExpr(node);
+}
+
+fn genExpr(c: *CodeGen, node: NodeIndex) Error!Ir.Ref {
+ std.debug.assert(node != .none);
+ const ty = c.node_ty[@intFromEnum(node)];
+ if (c.tree.value_map.get(node)) |val| {
+ return c.builder.addConstant(val, try c.genType(ty));
+ }
+ const data = c.node_data[@intFromEnum(node)];
+ switch (c.node_tag[@intFromEnum(node)]) {
+ .enumeration_ref,
+ .bool_literal,
+ .int_literal,
+ .char_literal,
+ .float_literal,
+ .double_literal,
+ .imaginary_literal,
+ .string_literal_expr,
+ .alignof_expr,
+ => unreachable, // These should have an entry in value_map.
+ .fn_def,
+ .static_fn_def,
+ .inline_fn_def,
+ .inline_static_fn_def,
+ .invalid,
+ .threadlocal_var,
+ => unreachable,
+ .static_assert,
+ .fn_proto,
+ .static_fn_proto,
+ .inline_fn_proto,
+ .inline_static_fn_proto,
+ .extern_var,
+ .threadlocal_extern_var,
+ .typedef,
+ .struct_decl_two,
+ .union_decl_two,
+ .enum_decl_two,
+ .struct_decl,
+ .union_decl,
+ .enum_decl,
+ .enum_field_decl,
+ .record_field_decl,
+ .indirect_record_field_decl,
+ .struct_forward_decl,
+ .union_forward_decl,
+ .enum_forward_decl,
+ .null_stmt,
+ => {},
+ .static_var,
+ .implicit_static_var,
+ .threadlocal_static_var,
+ => try c.genVar(node), // TODO
+ .@"var" => {
+ const size: u32 = @intCast(ty.sizeof(c.comp).?); // TODO add error in parser
+ const @"align" = ty.alignof(c.comp);
+ const alloc = try c.builder.addAlloc(size, @"align");
+ const name = try c.comp.intern(c.tree.tokSlice(data.decl.name));
+ try c.symbols.append(c.comp.gpa, .{ .name = name, .val = alloc });
+ if (data.decl.node != .none) {
+ try c.genInitializer(alloc, ty, data.decl.node);
+ }
+ },
+ .labeled_stmt => {
+ const label = try c.builder.makeLabel("label");
+ try c.builder.startBlock(label);
+ try c.genStmt(data.decl.node);
+ },
+ .compound_stmt_two => {
+ const old_sym_len = c.symbols.items.len;
+ c.symbols.items.len = old_sym_len;
+
+ if (data.bin.lhs != .none) try c.genStmt(data.bin.lhs);
+ if (data.bin.rhs != .none) try c.genStmt(data.bin.rhs);
+ },
+ .compound_stmt => {
+ const old_sym_len = c.symbols.items.len;
+ c.symbols.items.len = old_sym_len;
+
+ for (c.tree.data[data.range.start..data.range.end]) |stmt| try c.genStmt(stmt);
+ },
+ .if_then_else_stmt => {
+ const then_label = try c.builder.makeLabel("if.then");
+ const else_label = try c.builder.makeLabel("if.else");
+ const end_label = try c.builder.makeLabel("if.end");
+
+ try c.genBoolExpr(data.if3.cond, then_label, else_label);
+
+ try c.builder.startBlock(then_label);
+ try c.genStmt(c.tree.data[data.if3.body]); // then
+ try c.builder.addJump(end_label);
+
+ try c.builder.startBlock(else_label);
+ try c.genStmt(c.tree.data[data.if3.body + 1]); // else
+
+ try c.builder.startBlock(end_label);
+ },
+ .if_then_stmt => {
+ const then_label = try c.builder.makeLabel("if.then");
+ const end_label = try c.builder.makeLabel("if.end");
+
+ try c.genBoolExpr(data.bin.lhs, then_label, end_label);
+
+ try c.builder.startBlock(then_label);
+ try c.genStmt(data.bin.rhs); // then
+ try c.builder.startBlock(end_label);
+ },
+ .switch_stmt => {
+ var wip_switch = WipSwitch{
+ .size = c.node_ty[@intFromEnum(data.bin.lhs)].sizeof(c.comp).?,
+ };
+ defer wip_switch.cases.deinit(c.builder.gpa);
+
+ const old_wip_switch = c.wip_switch;
+ defer c.wip_switch = old_wip_switch;
+ c.wip_switch = &wip_switch;
+
+ const old_break_label = c.break_label;
+ defer c.break_label = old_break_label;
+ const end_ref = try c.builder.makeLabel("switch.end");
+ c.break_label = end_ref;
+
+ const cond = try c.genExpr(data.bin.lhs);
+ const switch_index = c.builder.instructions.len;
+ _ = try c.builder.addInst(.@"switch", undefined, .noreturn);
+
+ try c.genStmt(data.bin.rhs); // body
+
+ const default_ref = wip_switch.default orelse end_ref;
+ try c.builder.startBlock(end_ref);
+
+ const a = c.builder.arena.allocator();
+ const switch_data = try a.create(Ir.Inst.Switch);
+ switch_data.* = .{
+ .target = cond,
+ .cases_len = @intCast(wip_switch.cases.len),
+ .case_vals = (try a.dupe(Interner.Ref, wip_switch.cases.items(.val))).ptr,
+ .case_labels = (try a.dupe(Ir.Ref, wip_switch.cases.items(.label))).ptr,
+ .default = default_ref,
+ };
+ c.builder.instructions.items(.data)[switch_index] = .{ .@"switch" = switch_data };
+ },
+ .case_stmt => {
+ const val = c.tree.value_map.get(data.bin.lhs).?;
+ const label = try c.builder.makeLabel("case");
+ try c.builder.startBlock(label);
+ try c.wip_switch.cases.append(c.builder.gpa, .{
+ .val = try c.builder.pool.put(c.builder.gpa, .{ .value = val }),
+ .label = label,
+ });
+ try c.genStmt(data.bin.rhs);
+ },
+ .default_stmt => {
+ const default = try c.builder.makeLabel("default");
+ try c.builder.startBlock(default);
+ c.wip_switch.default = default;
+ try c.genStmt(data.un);
+ },
+ .while_stmt => {
+ const old_break_label = c.break_label;
+ defer c.break_label = old_break_label;
+
+ const old_continue_label = c.continue_label;
+ defer c.continue_label = old_continue_label;
+
+ const cond_label = try c.builder.makeLabel("while.cond");
+ const then_label = try c.builder.makeLabel("while.then");
+ const end_label = try c.builder.makeLabel("while.end");
+
+ c.continue_label = cond_label;
+ c.break_label = end_label;
+
+ try c.builder.startBlock(cond_label);
+ try c.genBoolExpr(data.bin.lhs, then_label, end_label);
+
+ try c.builder.startBlock(then_label);
+ try c.genStmt(data.bin.rhs);
+ try c.builder.addJump(cond_label);
+ try c.builder.startBlock(end_label);
+ },
+ .do_while_stmt => {
+ const old_break_label = c.break_label;
+ defer c.break_label = old_break_label;
+
+ const old_continue_label = c.continue_label;
+ defer c.continue_label = old_continue_label;
+
+ const then_label = try c.builder.makeLabel("do.then");
+ const cond_label = try c.builder.makeLabel("do.cond");
+ const end_label = try c.builder.makeLabel("do.end");
+
+ c.continue_label = cond_label;
+ c.break_label = end_label;
+
+ try c.builder.startBlock(then_label);
+ try c.genStmt(data.bin.rhs);
+
+ try c.builder.startBlock(cond_label);
+ try c.genBoolExpr(data.bin.lhs, then_label, end_label);
+
+ try c.builder.startBlock(end_label);
+ },
+ .for_decl_stmt => {
+ const old_break_label = c.break_label;
+ defer c.break_label = old_break_label;
+
+ const old_continue_label = c.continue_label;
+ defer c.continue_label = old_continue_label;
+
+ const for_decl = data.forDecl(c.tree);
+ for (for_decl.decls) |decl| try c.genStmt(decl);
+
+ const then_label = try c.builder.makeLabel("for.then");
+ var cond_label = then_label;
+ const cont_label = try c.builder.makeLabel("for.cont");
+ const end_label = try c.builder.makeLabel("for.end");
+
+ c.continue_label = cont_label;
+ c.break_label = end_label;
+
+ if (for_decl.cond != .none) {
+ cond_label = try c.builder.makeLabel("for.cond");
+ try c.builder.startBlock(cond_label);
+ try c.genBoolExpr(for_decl.cond, then_label, end_label);
+ }
+ try c.builder.startBlock(then_label);
+ try c.genStmt(for_decl.body);
+ if (for_decl.incr != .none) {
+ _ = try c.genExpr(for_decl.incr);
+ }
+ try c.builder.addJump(cond_label);
+ try c.builder.startBlock(end_label);
+ },
+ .forever_stmt => {
+ const old_break_label = c.break_label;
+ defer c.break_label = old_break_label;
+
+ const old_continue_label = c.continue_label;
+ defer c.continue_label = old_continue_label;
+
+ const then_label = try c.builder.makeLabel("for.then");
+ const end_label = try c.builder.makeLabel("for.end");
+
+ c.continue_label = then_label;
+ c.break_label = end_label;
+
+ try c.builder.startBlock(then_label);
+ try c.genStmt(data.un);
+ try c.builder.startBlock(end_label);
+ },
+ .for_stmt => {
+ const old_break_label = c.break_label;
+ defer c.break_label = old_break_label;
+
+ const old_continue_label = c.continue_label;
+ defer c.continue_label = old_continue_label;
+
+ const for_stmt = data.forStmt(c.tree);
+ if (for_stmt.init != .none) _ = try c.genExpr(for_stmt.init);
+
+ const then_label = try c.builder.makeLabel("for.then");
+ var cond_label = then_label;
+ const cont_label = try c.builder.makeLabel("for.cont");
+ const end_label = try c.builder.makeLabel("for.end");
+
+ c.continue_label = cont_label;
+ c.break_label = end_label;
+
+ if (for_stmt.cond != .none) {
+ cond_label = try c.builder.makeLabel("for.cond");
+ try c.builder.startBlock(cond_label);
+ try c.genBoolExpr(for_stmt.cond, then_label, end_label);
+ }
+ try c.builder.startBlock(then_label);
+ try c.genStmt(for_stmt.body);
+ if (for_stmt.incr != .none) {
+ _ = try c.genExpr(for_stmt.incr);
+ }
+ try c.builder.addJump(cond_label);
+ try c.builder.startBlock(end_label);
+ },
+ .continue_stmt => try c.builder.addJump(c.continue_label),
+ .break_stmt => try c.builder.addJump(c.break_label),
+ .return_stmt => {
+ if (data.un != .none) {
+ const operand = try c.genExpr(data.un);
+ try c.ret_nodes.append(c.comp.gpa, .{ .value = operand, .label = c.builder.current_label });
+ }
+ try c.builder.addJump(c.return_label);
+ },
+ .implicit_return => {
+ if (data.return_zero) {
+ const operand = try c.builder.addConstant(Value.int(0), try c.genType(ty));
+ try c.ret_nodes.append(c.comp.gpa, .{ .value = operand, .label = c.builder.current_label });
+ }
+ // No need to emit a jump since implicit_return is always the last instruction.
+ },
+ .case_range_stmt,
+ .goto_stmt,
+ .computed_goto_stmt,
+ .nullptr_literal,
+ => return c.comp.diag.fatalNoSrc("TODO CodeGen.genStmt {}\n", .{c.node_tag[@intFromEnum(node)]}),
+ .comma_expr => {
+ _ = try c.genExpr(data.bin.lhs);
+ return c.genExpr(data.bin.rhs);
+ },
+ .assign_expr => {
+ const rhs = try c.genExpr(data.bin.rhs);
+ const lhs = try c.genLval(data.bin.lhs);
+ try c.builder.addStore(lhs, rhs);
+ return rhs;
+ },
+ .mul_assign_expr => return c.genCompoundAssign(node, .mul),
+ .div_assign_expr => return c.genCompoundAssign(node, .div),
+ .mod_assign_expr => return c.genCompoundAssign(node, .mod),
+ .add_assign_expr => return c.genCompoundAssign(node, .add),
+ .sub_assign_expr => return c.genCompoundAssign(node, .sub),
+ .shl_assign_expr => return c.genCompoundAssign(node, .bit_shl),
+ .shr_assign_expr => return c.genCompoundAssign(node, .bit_shr),
+ .bit_and_assign_expr => return c.genCompoundAssign(node, .bit_and),
+ .bit_xor_assign_expr => return c.genCompoundAssign(node, .bit_xor),
+ .bit_or_assign_expr => return c.genCompoundAssign(node, .bit_or),
+ .bit_or_expr => return c.genBinOp(node, .bit_or),
+ .bit_xor_expr => return c.genBinOp(node, .bit_xor),
+ .bit_and_expr => return c.genBinOp(node, .bit_and),
+ .equal_expr => {
+ const cmp = try c.genComparison(node, .cmp_eq);
+ return c.addUn(.zext, cmp, ty);
+ },
+ .not_equal_expr => {
+ const cmp = try c.genComparison(node, .cmp_ne);
+ return c.addUn(.zext, cmp, ty);
+ },
+ .less_than_expr => {
+ const cmp = try c.genComparison(node, .cmp_lt);
+ return c.addUn(.zext, cmp, ty);
+ },
+ .less_than_equal_expr => {
+ const cmp = try c.genComparison(node, .cmp_lte);
+ return c.addUn(.zext, cmp, ty);
+ },
+ .greater_than_expr => {
+ const cmp = try c.genComparison(node, .cmp_gt);
+ return c.addUn(.zext, cmp, ty);
+ },
+ .greater_than_equal_expr => {
+ const cmp = try c.genComparison(node, .cmp_gte);
+ return c.addUn(.zext, cmp, ty);
+ },
+ .shl_expr => return c.genBinOp(node, .bit_shl),
+ .shr_expr => return c.genBinOp(node, .bit_shr),
+ .add_expr => {
+ if (ty.isPtr()) {
+ const lhs_ty = c.node_ty[@intFromEnum(data.bin.lhs)];
+ if (lhs_ty.isPtr()) {
+ const ptr = try c.genExpr(data.bin.lhs);
+ const offset = try c.genExpr(data.bin.rhs);
+ const offset_ty = c.node_ty[@intFromEnum(data.bin.rhs)];
+ return c.genPtrArithmetic(ptr, offset, offset_ty, ty);
+ } else {
+ const offset = try c.genExpr(data.bin.lhs);
+ const ptr = try c.genExpr(data.bin.rhs);
+ const offset_ty = lhs_ty;
+ return c.genPtrArithmetic(ptr, offset, offset_ty, ty);
+ }
+ }
+ return c.genBinOp(node, .add);
+ },
+ .sub_expr => {
+ if (ty.isPtr()) {
+ const ptr = try c.genExpr(data.bin.lhs);
+ const offset = try c.genExpr(data.bin.rhs);
+ const offset_ty = c.node_ty[@intFromEnum(data.bin.rhs)];
+ return c.genPtrArithmetic(ptr, offset, offset_ty, ty);
+ }
+ return c.genBinOp(node, .sub);
+ },
+ .mul_expr => return c.genBinOp(node, .mul),
+ .div_expr => return c.genBinOp(node, .div),
+ .mod_expr => return c.genBinOp(node, .mod),
+ .addr_of_expr => return try c.genLval(data.un),
+ .deref_expr => {
+ const un_data = c.node_data[@intFromEnum(data.un)];
+ if (c.node_tag[@intFromEnum(data.un)] == .implicit_cast and un_data.cast.kind == .function_to_pointer) {
+ return c.genExpr(data.un);
+ }
+ const operand = try c.genLval(data.un);
+ return c.addUn(.load, operand, ty);
+ },
+ .plus_expr => return c.genExpr(data.un),
+ .negate_expr => {
+ const zero = try c.builder.addConstant(Value.int(0), try c.genType(ty));
+ const operand = try c.genExpr(data.un);
+ return c.addBin(.sub, zero, operand, ty);
+ },
+ .bit_not_expr => {
+ const operand = try c.genExpr(data.un);
+ return c.addUn(.bit_not, operand, ty);
+ },
+ .bool_not_expr => {
+ const zero = try c.builder.addConstant(Value.int(0), try c.genType(ty));
+ const operand = try c.genExpr(data.un);
+ return c.addBin(.cmp_ne, zero, operand, ty);
+ },
+ .pre_inc_expr => {
+ const operand = try c.genLval(data.un);
+ const val = try c.addUn(.load, operand, ty);
+ const one = try c.builder.addConstant(Value.int(1), try c.genType(ty));
+ const plus_one = try c.addBin(.add, val, one, ty);
+ try c.builder.addStore(operand, plus_one);
+ return plus_one;
+ },
+ .pre_dec_expr => {
+ const operand = try c.genLval(data.un);
+ const val = try c.addUn(.load, operand, ty);
+ const one = try c.builder.addConstant(Value.int(1), try c.genType(ty));
+ const plus_one = try c.addBin(.sub, val, one, ty);
+ try c.builder.addStore(operand, plus_one);
+ return plus_one;
+ },
+ .post_inc_expr => {
+ const operand = try c.genLval(data.un);
+ const val = try c.addUn(.load, operand, ty);
+ const one = try c.builder.addConstant(Value.int(1), try c.genType(ty));
+ const plus_one = try c.addBin(.add, val, one, ty);
+ try c.builder.addStore(operand, plus_one);
+ return val;
+ },
+ .post_dec_expr => {
+ const operand = try c.genLval(data.un);
+ const val = try c.addUn(.load, operand, ty);
+ const one = try c.builder.addConstant(Value.int(1), try c.genType(ty));
+ const plus_one = try c.addBin(.sub, val, one, ty);
+ try c.builder.addStore(operand, plus_one);
+ return val;
+ },
+ .paren_expr => return c.genExpr(data.un),
+ .decl_ref_expr => unreachable, // Lval expression.
+ .explicit_cast, .implicit_cast => switch (data.cast.kind) {
+ .no_op => return c.genExpr(data.cast.operand),
+ .to_void => {
+ _ = try c.genExpr(data.cast.operand);
+ return .none;
+ },
+ .lval_to_rval => {
+ const operand = try c.genLval(data.cast.operand);
+ return c.addUn(.load, operand, ty);
+ },
+ .function_to_pointer, .array_to_pointer => {
+ return c.genLval(data.cast.operand);
+ },
+ .int_cast => {
+ const operand = try c.genExpr(data.cast.operand);
+ const src_ty = c.node_ty[@intFromEnum(data.cast.operand)];
+ const src_bits = src_ty.bitSizeof(c.comp).?;
+ const dest_bits = ty.bitSizeof(c.comp).?;
+ if (src_bits == dest_bits) {
+ return operand;
+ } else if (src_bits < dest_bits) {
+ if (src_ty.isUnsignedInt(c.comp))
+ return c.addUn(.zext, operand, ty)
+ else
+ return c.addUn(.sext, operand, ty);
+ } else {
+ return c.addUn(.trunc, operand, ty);
+ }
+ },
+ .bool_to_int => {
+ const operand = try c.genExpr(data.cast.operand);
+ return c.addUn(.zext, operand, ty);
+ },
+ .pointer_to_bool, .int_to_bool, .float_to_bool => {
+ const lhs = try c.genExpr(data.cast.operand);
+ const rhs = try c.builder.addConstant(Value.int(0), try c.genType(c.node_ty[@intFromEnum(node)]));
+ return c.builder.addInst(.cmp_ne, .{ .bin = .{ .lhs = lhs, .rhs = rhs } }, .i1);
+ },
+ .bitcast,
+ .pointer_to_int,
+ .bool_to_float,
+ .bool_to_pointer,
+ .int_to_float,
+ .complex_int_to_complex_float,
+ .int_to_pointer,
+ .float_to_int,
+ .complex_float_to_complex_int,
+ .complex_int_cast,
+ .complex_int_to_real,
+ .real_to_complex_int,
+ .float_cast,
+ .complex_float_cast,
+ .complex_float_to_real,
+ .real_to_complex_float,
+ .null_to_pointer,
+ .union_cast,
+ .vector_splat,
+ => return c.comp.diag.fatalNoSrc("TODO CodeGen gen CastKind {}\n", .{data.cast.kind}),
+ },
+ .binary_cond_expr => {
+ if (c.tree.value_map.get(data.if3.cond)) |cond| {
+ if (cond.getBool()) {
+ c.cond_dummy_ref = try c.genExpr(data.if3.cond);
+ return c.genExpr(c.tree.data[data.if3.body]); // then
+ } else {
+ return c.genExpr(c.tree.data[data.if3.body + 1]); // else
+ }
+ }
+
+ const then_label = try c.builder.makeLabel("ternary.then");
+ const else_label = try c.builder.makeLabel("ternary.else");
+ const end_label = try c.builder.makeLabel("ternary.end");
+ const cond_ty = c.node_ty[@intFromEnum(data.if3.cond)];
+ {
+ const old_cond_dummy_ty = c.cond_dummy_ty;
+ defer c.cond_dummy_ty = old_cond_dummy_ty;
+ c.cond_dummy_ty = try c.genType(cond_ty);
+
+ try c.genBoolExpr(data.if3.cond, then_label, else_label);
+ }
+
+ try c.builder.startBlock(then_label);
+ if (c.builder.instructions.items(.ty)[@intFromEnum(c.cond_dummy_ref)] == .i1) {
+ c.cond_dummy_ref = try c.addUn(.zext, c.cond_dummy_ref, cond_ty);
+ }
+ const then_val = try c.genExpr(c.tree.data[data.if3.body]); // then
+ try c.builder.addJump(end_label);
+ const then_exit = c.builder.current_label;
+
+ try c.builder.startBlock(else_label);
+ const else_val = try c.genExpr(c.tree.data[data.if3.body + 1]); // else
+ const else_exit = c.builder.current_label;
+
+ try c.builder.startBlock(end_label);
+
+ var phi_buf: [2]Ir.Inst.Phi.Input = .{
+ .{ .value = then_val, .label = then_exit },
+ .{ .value = else_val, .label = else_exit },
+ };
+ return c.builder.addPhi(&phi_buf, try c.genType(ty));
+ },
+ .cond_dummy_expr => return c.cond_dummy_ref,
+ .cond_expr => {
+ if (c.tree.value_map.get(data.if3.cond)) |cond| {
+ if (cond.getBool()) {
+ return c.genExpr(c.tree.data[data.if3.body]); // then
+ } else {
+ return c.genExpr(c.tree.data[data.if3.body + 1]); // else
+ }
+ }
+
+ const then_label = try c.builder.makeLabel("ternary.then");
+ const else_label = try c.builder.makeLabel("ternary.else");
+ const end_label = try c.builder.makeLabel("ternary.end");
+
+ try c.genBoolExpr(data.if3.cond, then_label, else_label);
+
+ try c.builder.startBlock(then_label);
+ const then_val = try c.genExpr(c.tree.data[data.if3.body]); // then
+ try c.builder.addJump(end_label);
+ const then_exit = c.builder.current_label;
+
+ try c.builder.startBlock(else_label);
+ const else_val = try c.genExpr(c.tree.data[data.if3.body + 1]); // else
+ const else_exit = c.builder.current_label;
+
+ try c.builder.startBlock(end_label);
+
+ var phi_buf: [2]Ir.Inst.Phi.Input = .{
+ .{ .value = then_val, .label = then_exit },
+ .{ .value = else_val, .label = else_exit },
+ };
+ return c.builder.addPhi(&phi_buf, try c.genType(ty));
+ },
+ .call_expr_one => if (data.bin.rhs == .none) {
+ return c.genCall(data.bin.lhs, &.{}, ty);
+ } else {
+ return c.genCall(data.bin.lhs, &.{data.bin.rhs}, ty);
+ },
+ .call_expr => {
+ return c.genCall(c.tree.data[data.range.start], c.tree.data[data.range.start + 1 .. data.range.end], ty);
+ },
+ .bool_or_expr => {
+ if (c.tree.value_map.get(data.bin.lhs)) |lhs| {
+ const cond = lhs.getBool();
+ if (!cond) {
+ return c.builder.addConstant(Value.int(1), try c.genType(ty));
+ }
+ return c.genExpr(data.bin.rhs);
+ }
+
+ const false_label = try c.builder.makeLabel("bool_false");
+ const exit_label = try c.builder.makeLabel("bool_exit");
+
+ const old_bool_end_label = c.bool_end_label;
+ defer c.bool_end_label = old_bool_end_label;
+ c.bool_end_label = exit_label;
+
+ const phi_nodes_top = c.phi_nodes.items.len;
+ defer c.phi_nodes.items.len = phi_nodes_top;
+
+ try c.genBoolExpr(data.bin.lhs, exit_label, false_label);
+
+ try c.builder.startBlock(false_label);
+ try c.genBoolExpr(data.bin.rhs, exit_label, exit_label);
+
+ try c.builder.startBlock(exit_label);
+
+ const phi = try c.builder.addPhi(c.phi_nodes.items[phi_nodes_top..], .i1);
+ return c.addUn(.zext, phi, ty);
+ },
+ .bool_and_expr => {
+ if (c.tree.value_map.get(data.bin.lhs)) |lhs| {
+ const cond = lhs.getBool();
+ if (!cond) {
+ return c.builder.addConstant(Value.int(0), try c.genType(ty));
+ }
+ return c.genExpr(data.bin.rhs);
+ }
+
+ const true_label = try c.builder.makeLabel("bool_true");
+ const exit_label = try c.builder.makeLabel("bool_exit");
+
+ const old_bool_end_label = c.bool_end_label;
+ defer c.bool_end_label = old_bool_end_label;
+ c.bool_end_label = exit_label;
+
+ const phi_nodes_top = c.phi_nodes.items.len;
+ defer c.phi_nodes.items.len = phi_nodes_top;
+
+ try c.genBoolExpr(data.bin.lhs, true_label, exit_label);
+
+ try c.builder.startBlock(true_label);
+ try c.genBoolExpr(data.bin.rhs, exit_label, exit_label);
+
+ try c.builder.startBlock(exit_label);
+
+ const phi = try c.builder.addPhi(c.phi_nodes.items[phi_nodes_top..], .i1);
+ return c.addUn(.zext, phi, ty);
+ },
+ .builtin_choose_expr => {
+ const cond = c.tree.value_map.get(data.if3.cond).?;
+ if (cond.getBool()) {
+ return c.genExpr(c.tree.data[data.if3.body]);
+ } else {
+ return c.genExpr(c.tree.data[data.if3.body + 1]);
+ }
+ },
+ .generic_expr_one => {
+ const index = @intFromEnum(data.bin.rhs);
+ switch (c.node_tag[index]) {
+ .generic_association_expr, .generic_default_expr => {
+ return c.genExpr(c.node_data[index].un);
+ },
+ else => unreachable,
+ }
+ },
+ .generic_expr => {
+ const index = @intFromEnum(c.tree.data[data.range.start + 1]);
+ switch (c.node_tag[index]) {
+ .generic_association_expr, .generic_default_expr => {
+ return c.genExpr(c.node_data[index].un);
+ },
+ else => unreachable,
+ }
+ },
+ .generic_association_expr, .generic_default_expr => unreachable,
+ .stmt_expr => switch (c.node_tag[@intFromEnum(data.un)]) {
+ .compound_stmt_two => {
+ const old_sym_len = c.symbols.items.len;
+ c.symbols.items.len = old_sym_len;
+
+ const stmt_data = c.node_data[@intFromEnum(data.un)];
+ if (stmt_data.bin.rhs == .none) return c.genExpr(stmt_data.bin.lhs);
+ try c.genStmt(stmt_data.bin.lhs);
+ return c.genExpr(stmt_data.bin.rhs);
+ },
+ .compound_stmt => {
+ const old_sym_len = c.symbols.items.len;
+ c.symbols.items.len = old_sym_len;
+
+ const stmt_data = c.node_data[@intFromEnum(data.un)];
+ for (c.tree.data[stmt_data.range.start .. stmt_data.range.end - 1]) |stmt| try c.genStmt(stmt);
+ return c.genExpr(c.tree.data[stmt_data.range.end]);
+ },
+ else => unreachable,
+ },
+ .builtin_call_expr_one => {
+ const name = c.tree.tokSlice(data.decl.name);
+ const builtin = c.comp.builtins.lookup(name).builtin;
+ if (data.decl.node == .none) {
+ return c.genBuiltinCall(builtin, &.{}, ty);
+ } else {
+ return c.genBuiltinCall(builtin, &.{data.decl.node}, ty);
+ }
+ },
+ .builtin_call_expr => {
+ const name_node_idx = c.tree.data[data.range.start];
+ const name = c.tree.tokSlice(@intFromEnum(name_node_idx));
+ const builtin = c.comp.builtins.lookup(name).builtin;
+ return c.genBuiltinCall(builtin, c.tree.data[data.range.start + 1 .. data.range.end], ty);
+ },
+ .addr_of_label,
+ .imag_expr,
+ .real_expr,
+ .sizeof_expr,
+ .special_builtin_call_one,
+ => return c.comp.diag.fatalNoSrc("TODO CodeGen.genExpr {}\n", .{c.node_tag[@intFromEnum(node)]}),
+ else => unreachable, // Not an expression.
+ }
+ return .none;
+}
+
+fn genLval(c: *CodeGen, node: NodeIndex) Error!Ir.Ref {
+ std.debug.assert(node != .none);
+ assert(Tree.isLval(c.tree.nodes, c.tree.data, c.tree.value_map, node));
+ const data = c.node_data[@intFromEnum(node)];
+ switch (c.node_tag[@intFromEnum(node)]) {
+ .string_literal_expr => {
+ const val = c.tree.value_map.get(node).?;
+ return c.builder.addConstant(val, .ptr);
+ },
+ .paren_expr => return c.genLval(data.un),
+ .decl_ref_expr => {
+ const slice = c.tree.tokSlice(data.decl_ref);
+ const name = try c.comp.intern(slice);
+ var i = c.symbols.items.len;
+ while (i > 0) {
+ i -= 1;
+ if (c.symbols.items[i].name == name) {
+ return c.symbols.items[i].val;
+ }
+ }
+
+ const duped_name = try c.builder.arena.allocator().dupeZ(u8, slice);
+ const ref: Ir.Ref = @enumFromInt(c.builder.instructions.len);
+ try c.builder.instructions.append(c.builder.gpa, .{ .tag = .symbol, .data = .{ .label = duped_name }, .ty = .ptr });
+ return ref;
+ },
+ .deref_expr => return c.genExpr(data.un),
+ .compound_literal_expr => {
+ const ty = c.node_ty[@intFromEnum(node)];
+ const size: u32 = @intCast(ty.sizeof(c.comp).?); // TODO add error in parser
+ const @"align" = ty.alignof(c.comp);
+ const alloc = try c.builder.addAlloc(size, @"align");
+ try c.genInitializer(alloc, ty, data.un);
+ return alloc;
+ },
+ .builtin_choose_expr => {
+ const cond = c.tree.value_map.get(data.if3.cond).?;
+ if (cond.getBool()) {
+ return c.genLval(c.tree.data[data.if3.body]);
+ } else {
+ return c.genLval(c.tree.data[data.if3.body + 1]);
+ }
+ },
+ .member_access_expr,
+ .member_access_ptr_expr,
+ .array_access_expr,
+ => return c.comp.diag.fatalNoSrc("TODO CodeGen.genLval {}\n", .{c.node_tag[@intFromEnum(node)]}),
+ else => unreachable, // Not an lval expression.
+ }
+}
+
+fn genBoolExpr(c: *CodeGen, base: NodeIndex, true_label: Ir.Ref, false_label: Ir.Ref) Error!void {
+ var node = base;
+ while (true) switch (c.node_tag[@intFromEnum(node)]) {
+ .paren_expr => {
+ node = c.node_data[@intFromEnum(node)].un;
+ },
+ else => break,
+ };
+
+ const data = c.node_data[@intFromEnum(node)];
+ switch (c.node_tag[@intFromEnum(node)]) {
+ .bool_or_expr => {
+ if (c.tree.value_map.get(data.bin.lhs)) |lhs| {
+ const cond = lhs.getBool();
+ if (cond) {
+ if (true_label == c.bool_end_label) {
+ return c.addBoolPhi(!c.bool_invert);
+ }
+ return c.builder.addJump(true_label);
+ }
+ return c.genBoolExpr(data.bin.rhs, true_label, false_label);
+ }
+
+ const new_false_label = try c.builder.makeLabel("bool_false");
+ try c.genBoolExpr(data.bin.lhs, true_label, new_false_label);
+ try c.builder.startBlock(new_false_label);
+
+ if (c.cond_dummy_ty) |ty| c.cond_dummy_ref = try c.builder.addConstant(Value.int(1), ty);
+ return c.genBoolExpr(data.bin.rhs, true_label, false_label);
+ },
+ .bool_and_expr => {
+ if (c.tree.value_map.get(data.bin.lhs)) |lhs| {
+ const cond = lhs.getBool();
+ if (!cond) {
+ if (false_label == c.bool_end_label) {
+ return c.addBoolPhi(c.bool_invert);
+ }
+ return c.builder.addJump(false_label);
+ }
+ return c.genBoolExpr(data.bin.rhs, true_label, false_label);
+ }
+
+ const new_true_label = try c.builder.makeLabel("bool_true");
+ try c.genBoolExpr(data.bin.lhs, new_true_label, false_label);
+ try c.builder.startBlock(new_true_label);
+
+ if (c.cond_dummy_ty) |ty| c.cond_dummy_ref = try c.builder.addConstant(Value.int(1), ty);
+ return c.genBoolExpr(data.bin.rhs, true_label, false_label);
+ },
+ .bool_not_expr => {
+ c.bool_invert = !c.bool_invert;
+ defer c.bool_invert = !c.bool_invert;
+
+ if (c.cond_dummy_ty) |ty| c.cond_dummy_ref = try c.builder.addConstant(Value.int(0), ty);
+ return c.genBoolExpr(data.un, false_label, true_label);
+ },
+ .equal_expr => {
+ const cmp = try c.genComparison(node, .cmp_eq);
+ if (c.cond_dummy_ty != null) c.cond_dummy_ref = cmp;
+ return c.addBranch(cmp, true_label, false_label);
+ },
+ .not_equal_expr => {
+ const cmp = try c.genComparison(node, .cmp_ne);
+ if (c.cond_dummy_ty != null) c.cond_dummy_ref = cmp;
+ return c.addBranch(cmp, true_label, false_label);
+ },
+ .less_than_expr => {
+ const cmp = try c.genComparison(node, .cmp_lt);
+ if (c.cond_dummy_ty != null) c.cond_dummy_ref = cmp;
+ return c.addBranch(cmp, true_label, false_label);
+ },
+ .less_than_equal_expr => {
+ const cmp = try c.genComparison(node, .cmp_lte);
+ if (c.cond_dummy_ty != null) c.cond_dummy_ref = cmp;
+ return c.addBranch(cmp, true_label, false_label);
+ },
+ .greater_than_expr => {
+ const cmp = try c.genComparison(node, .cmp_gt);
+ if (c.cond_dummy_ty != null) c.cond_dummy_ref = cmp;
+ return c.addBranch(cmp, true_label, false_label);
+ },
+ .greater_than_equal_expr => {
+ const cmp = try c.genComparison(node, .cmp_gte);
+ if (c.cond_dummy_ty != null) c.cond_dummy_ref = cmp;
+ return c.addBranch(cmp, true_label, false_label);
+ },
+ .explicit_cast, .implicit_cast => switch (data.cast.kind) {
+ .bool_to_int => {
+ const operand = try c.genExpr(data.cast.operand);
+ if (c.cond_dummy_ty != null) c.cond_dummy_ref = operand;
+ return c.addBranch(operand, true_label, false_label);
+ },
+ else => {},
+ },
+ .binary_cond_expr => {
+ if (c.tree.value_map.get(data.if3.cond)) |cond| {
+ if (cond.getBool()) {
+ return c.genBoolExpr(c.tree.data[data.if3.body], true_label, false_label); // then
+ } else {
+ return c.genBoolExpr(c.tree.data[data.if3.body + 1], true_label, false_label); // else
+ }
+ }
+
+ const new_false_label = try c.builder.makeLabel("ternary.else");
+ try c.genBoolExpr(data.if3.cond, true_label, new_false_label);
+
+ try c.builder.startBlock(new_false_label);
+ if (c.cond_dummy_ty) |ty| c.cond_dummy_ref = try c.builder.addConstant(Value.int(1), ty);
+ return c.genBoolExpr(c.tree.data[data.if3.body + 1], true_label, false_label); // else
+ },
+ .cond_expr => {
+ if (c.tree.value_map.get(data.if3.cond)) |cond| {
+ if (cond.getBool()) {
+ return c.genBoolExpr(c.tree.data[data.if3.body], true_label, false_label); // then
+ } else {
+ return c.genBoolExpr(c.tree.data[data.if3.body + 1], true_label, false_label); // else
+ }
+ }
+
+ const new_true_label = try c.builder.makeLabel("ternary.then");
+ const new_false_label = try c.builder.makeLabel("ternary.else");
+ try c.genBoolExpr(data.if3.cond, new_true_label, new_false_label);
+
+ try c.builder.startBlock(new_true_label);
+ try c.genBoolExpr(c.tree.data[data.if3.body], true_label, false_label); // then
+ try c.builder.startBlock(new_false_label);
+ if (c.cond_dummy_ty) |ty| c.cond_dummy_ref = try c.builder.addConstant(Value.int(1), ty);
+ return c.genBoolExpr(c.tree.data[data.if3.body + 1], true_label, false_label); // else
+ },
+ else => {},
+ }
+
+ if (c.tree.value_map.get(node)) |value| {
+ if (value.getBool()) {
+ if (true_label == c.bool_end_label) {
+ return c.addBoolPhi(!c.bool_invert);
+ }
+ return c.builder.addJump(true_label);
+ } else {
+ if (false_label == c.bool_end_label) {
+ return c.addBoolPhi(c.bool_invert);
+ }
+ return c.builder.addJump(false_label);
+ }
+ }
+
+ // Assume int operand.
+ const lhs = try c.genExpr(node);
+ const rhs = try c.builder.addConstant(Value.int(0), try c.genType(c.node_ty[@intFromEnum(node)]));
+ const cmp = try c.builder.addInst(.cmp_ne, .{ .bin = .{ .lhs = lhs, .rhs = rhs } }, .i1);
+ if (c.cond_dummy_ty != null) c.cond_dummy_ref = cmp;
+ try c.addBranch(cmp, true_label, false_label);
+}
+
+fn genBuiltinCall(c: *CodeGen, builtin: BuiltinFunction, arg_nodes: []const NodeIndex, ty: Type) Error!Ir.Ref {
+ _ = arg_nodes;
+ _ = ty;
+ return c.comp.diag.fatalNoSrc("TODO CodeGen.genBuiltinCall {s}\n", .{@tagName(builtin.tag)});
+}
+
+fn genCall(c: *CodeGen, fn_node: NodeIndex, arg_nodes: []const NodeIndex, ty: Type) Error!Ir.Ref {
+ // Detect direct calls.
+ const fn_ref = blk: {
+ const data = c.node_data[@intFromEnum(fn_node)];
+ if (c.node_tag[@intFromEnum(fn_node)] != .implicit_cast or data.cast.kind != .function_to_pointer) {
+ break :blk try c.genExpr(fn_node);
+ }
+
+ var cur = @intFromEnum(data.cast.operand);
+ while (true) switch (c.node_tag[cur]) {
+ .paren_expr, .addr_of_expr, .deref_expr => {
+ cur = @intFromEnum(c.node_data[cur].un);
+ },
+ .implicit_cast => {
+ const cast = c.node_data[cur].cast;
+ if (cast.kind != .function_to_pointer) {
+ break :blk try c.genExpr(fn_node);
+ }
+ cur = @intFromEnum(cast.operand);
+ },
+ .decl_ref_expr => {
+ const slice = c.tree.tokSlice(c.node_data[cur].decl_ref);
+ const name = try c.comp.intern(slice);
+ var i = c.symbols.items.len;
+ while (i > 0) {
+ i -= 1;
+ if (c.symbols.items[i].name == name) {
+ break :blk try c.genExpr(fn_node);
+ }
+ }
+
+ const duped_name = try c.builder.arena.allocator().dupeZ(u8, slice);
+ const ref: Ir.Ref = @enumFromInt(c.builder.instructions.len);
+ try c.builder.instructions.append(c.builder.gpa, .{ .tag = .symbol, .data = .{ .label = duped_name }, .ty = .ptr });
+ break :blk ref;
+ },
+ else => break :blk try c.genExpr(fn_node),
+ };
+ };
+
+ const args = try c.builder.arena.allocator().alloc(Ir.Ref, arg_nodes.len);
+ for (arg_nodes, args) |node, *arg| {
+ // TODO handle calling convention here
+ arg.* = try c.genExpr(node);
+ }
+ // TODO handle variadic call
+ const call = try c.builder.arena.allocator().create(Ir.Inst.Call);
+ call.* = .{
+ .func = fn_ref,
+ .args_len = @intCast(args.len),
+ .args_ptr = args.ptr,
+ };
+ return c.builder.addInst(.call, .{ .call = call }, try c.genType(ty));
+}
+
+fn genCompoundAssign(c: *CodeGen, node: NodeIndex, tag: Ir.Inst.Tag) Error!Ir.Ref {
+ const bin = c.node_data[@intFromEnum(node)].bin;
+ const ty = c.node_ty[@intFromEnum(node)];
+ const rhs = try c.genExpr(bin.rhs);
+ const lhs = try c.genLval(bin.lhs);
+ const res = try c.addBin(tag, lhs, rhs, ty);
+ try c.builder.addStore(lhs, res);
+ return res;
+}
+
+fn genBinOp(c: *CodeGen, node: NodeIndex, tag: Ir.Inst.Tag) Error!Ir.Ref {
+ const bin = c.node_data[@intFromEnum(node)].bin;
+ const ty = c.node_ty[@intFromEnum(node)];
+ const lhs = try c.genExpr(bin.lhs);
+ const rhs = try c.genExpr(bin.rhs);
+ return c.addBin(tag, lhs, rhs, ty);
+}
+
+fn genComparison(c: *CodeGen, node: NodeIndex, tag: Ir.Inst.Tag) Error!Ir.Ref {
+ const bin = c.node_data[@intFromEnum(node)].bin;
+ const lhs = try c.genExpr(bin.lhs);
+ const rhs = try c.genExpr(bin.rhs);
+
+ return c.builder.addInst(tag, .{ .bin = .{ .lhs = lhs, .rhs = rhs } }, .i1);
+}
+
+fn genPtrArithmetic(c: *CodeGen, ptr: Ir.Ref, offset: Ir.Ref, offset_ty: Type, ty: Type) Error!Ir.Ref {
+ // TODO consider adding a getelemptr instruction
+ const size = ty.elemType().sizeof(c.comp).?;
+ if (size == 1) {
+ return c.builder.addInst(.add, .{ .bin = .{ .lhs = ptr, .rhs = offset } }, try c.genType(ty));
+ }
+
+ const size_inst = try c.builder.addConstant(Value.int(size), try c.genType(offset_ty));
+ const offset_inst = try c.addBin(.mul, offset, size_inst, offset_ty);
+ return c.addBin(.add, ptr, offset_inst, offset_ty);
+}
+
+fn genInitializer(c: *CodeGen, ptr: Ir.Ref, dest_ty: Type, initializer: NodeIndex) Error!void {
+ std.debug.assert(initializer != .none);
+ switch (c.node_tag[@intFromEnum(initializer)]) {
+ .array_init_expr_two,
+ .array_init_expr,
+ .struct_init_expr_two,
+ .struct_init_expr,
+ .union_init_expr,
+ .array_filler_expr,
+ .default_init_expr,
+ => return c.comp.diag.fatalNoSrc("TODO CodeGen.genInitializer {}\n", .{c.node_tag[@intFromEnum(initializer)]}),
+ .string_literal_expr => {
+ const val = c.tree.value_map.get(initializer).?;
+ const str_ptr = try c.builder.addConstant(val, .ptr);
+ if (dest_ty.isArray()) {
+ return c.comp.diag.fatalNoSrc("TODO memcpy\n", .{});
+ } else {
+ try c.builder.addStore(ptr, str_ptr);
+ }
+ },
+ else => {
+ const res = try c.genExpr(initializer);
+ try c.builder.addStore(ptr, res);
+ },
+ }
+}
+
+fn genVar(c: *CodeGen, decl: NodeIndex) Error!void {
+ _ = decl;
+ return c.comp.diag.fatalNoSrc("TODO CodeGen.genVar\n", .{});
+}