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", .{}); }