zig/lib/compiler/aro/aro/CodeGen.zig
Andrew Kelley 240d0b68f6 make aro-based translate-c lazily built from source
Part of #19063.

Primarily, this moves Aro from deps/ to lib/compiler/ so that it can be
lazily compiled from source. src/aro_translate_c.zig is moved to
lib/compiler/aro_translate_c.zig and some of Zig CLI logic moved to a
main() function there.

aro_translate_c.zig becomes the "common" import for clang-based
translate-c.

Not all of the compiler was able to be detangled from Aro, however, so
it still, for now, remains being compiled with the main compiler
sources due to the clang-based translate-c depending on it. Once
aro-based translate-c achieves feature parity with the clang-based
translate-c implementation, the clang-based one can be removed from Zig.

Aro made it unnecessarily difficult to depend on with these .def files
and all these Zig module requirements. I looked at the .def files and
made these observations:

- The canonical source is llvm .def files.
- Therefore there is an update process to sync with llvm that involves
  regenerating the .def files in Aro.
- Therefore you might as well just regenerate the .zig files directly
  and check those into Aro.
- Also with a small amount of tinkering, the file size on disk of these
  generated .zig files can be made many times smaller, without
  compromising type safety in the usage of the data.

This would make things much easier on Zig as downstream project,
particularly we could remove those pesky stubs when bootstrapping.

I have gone ahead with these changes since they unblock me and I will
have a chat with Vexu to see what he thinks.
2024-02-28 13:21:05 -07:00

1295 lines
51 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const backend = @import("../backend.zig");
const Interner = backend.Interner;
const Ir = backend.Ir;
const Builtins = @import("Builtins.zig");
const Builtin = Builtins.Builtin;
const Compilation = @import("Compilation.zig");
const Builder = Ir.Builder;
const StrInt = @import("StringInterner.zig");
const StringId = StrInt.StringId;
const Tree = @import("Tree.zig");
const NodeIndex = Tree.NodeIndex;
const Type = @import("Type.zig");
const Value = @import("Value.zig");
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;
const CodeGen = @This();
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) = .{},
record_cache: std.AutoHashMapUnmanaged(*Type.Record, 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,
fn fail(c: *CodeGen, comptime fmt: []const u8, args: anytype) error{ FatalError, OutOfMemory } {
try c.comp.diagnostics.list.append(c.comp.gpa, .{
.tag = .cli_error,
.kind = .@"fatal error",
.extra = .{ .str = try std.fmt.allocPrint(c.comp.diagnostics.arena.allocator(), fmt, args) },
});
return error.FatalError;
}
pub fn genIr(tree: Tree) Compilation.Error!Ir {
const gpa = tree.comp.gpa;
var c = CodeGen{
.builder = .{
.gpa = tree.comp.gpa,
.interner = &tree.comp.interner,
.arena = std.heap.ArenaAllocator.init(gpa),
},
.tree = tree,
.comp = tree.comp,
.node_tag = tree.nodes.items(.tag),
.node_data = tree.nodes.items(.data),
.node_ty = tree.nodes.items(.ty),
};
defer c.symbols.deinit(gpa);
defer c.ret_nodes.deinit(gpa);
defer c.phi_nodes.deinit(gpa);
defer c.record_elem_buf.deinit(gpa);
defer c.record_cache.deinit(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(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,
}
}
return c.builder.finish();
}
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" => {
if (c.record_cache.get(ty.data.record)) |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.fail("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);
}
return c.builder.interner.put(c.builder.gpa, .{
.record_ty = c.record_elem_buf.items[elem_buf_top..],
});
},
.@"union" => {
return c.fail("TODO lower union types", .{});
},
else => {},
}
if (ty.isPtr()) return .ptr;
if (ty.isFunc()) return .func;
if (!ty.isReal()) return c.fail("TODO lower complex types", .{});
if (ty.isInt()) {
const bits = ty.bitSizeof(c.comp).?;
key = .{ .int_ty = @intCast(bits) };
} else if (ty.isFloat()) {
const bits = ty.bitSizeof(c.comp).?;
key = .{ .float_ty = @intCast(bits) };
} else if (ty.isArray()) {
const elem = try c.genType(ty.elemType());
key = .{ .array_ty = .{ .child = elem, .len = ty.arrayLen().? } };
} else if (ty.specifier == .vector) {
const elem = try c.genType(ty.elemType());
key = .{ .vector_ty = .{ .child = elem, .len = @intCast(ty.data.array.len) } };
} else if (ty.is(.nullptr_t)) {
return c.fail("TODO lower nullptr_t", .{});
}
return c.builder.interner.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);
}
try c.builder.finishFn(name);
}
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((try Value.int(@intFromBool(value), c.comp)).ref(), .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.ref(), 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,
.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 StrInt.intern(c.comp, 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 = val.ref(),
.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(.zero, 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.fail("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(.zero, 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(.zero, 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(.one, 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(.one, 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(.one, 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(.one, 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(.zero, 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.fail("TODO CodeGen gen CastKind {}\n", .{data.cast.kind}),
},
.binary_cond_expr => {
if (c.tree.value_map.get(data.if3.cond)) |cond| {
if (cond.toBool(c.comp)) {
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.toBool(c.comp)) {
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| {
if (!lhs.toBool(c.comp)) {
return c.builder.addConstant(.one, 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| {
if (!lhs.toBool(c.comp)) {
return c.builder.addConstant(.zero, 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.toBool(c.comp)) {
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.fail("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(c.tree.isLval(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.ref(), .ptr);
},
.paren_expr => return c.genLval(data.un),
.decl_ref_expr => {
const slice = c.tree.tokSlice(data.decl_ref);
const name = try StrInt.intern(c.comp, 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.toBool(c.comp)) {
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,
.static_compound_literal_expr,
.thread_local_compound_literal_expr,
.static_thread_local_compound_literal_expr,
=> return c.fail("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| {
if (lhs.toBool(c.comp)) {
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(.one, ty);
return c.genBoolExpr(data.bin.rhs, true_label, false_label);
},
.bool_and_expr => {
if (c.tree.value_map.get(data.bin.lhs)) |lhs| {
if (!lhs.toBool(c.comp)) {
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(.one, 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(.zero, 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.toBool(c.comp)) {
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(.one, 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.toBool(c.comp)) {
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(.one, 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.toBool(c.comp)) {
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(.zero, 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: Builtin, arg_nodes: []const NodeIndex, ty: Type) Error!Ir.Ref {
_ = arg_nodes;
_ = ty;
return c.fail("TODO CodeGen.genBuiltinCall {s}\n", .{Builtin.nameFromTag(builtin.tag).span()});
}
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 StrInt.intern(c.comp, 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((try Value.int(size, c.comp)).ref(), 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.fail("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.ref(), .ptr);
if (dest_ty.isArray()) {
return c.fail("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.fail("TODO CodeGen.genVar\n", .{});
}