Commit ef91b11295
Changed files (8)
src-self-hosted
test
stage2
src-self-hosted/codegen/c.zig
@@ -92,9 +92,9 @@ fn genFn(file: *C, decl: *Decl) !void {
for (instructions) |inst| {
try writer.writeAll("\n\t");
switch (inst.tag) {
- .assembly => try genAsm(file, inst.cast(Inst.Assembly).?, decl),
- .call => try genCall(file, inst.cast(Inst.Call).?, decl),
- .ret => try genRet(file, inst.cast(Inst.Ret).?, decl, tv.ty.fnReturnType()),
+ .assembly => try genAsm(file, inst.castTag(.assembly).?, decl),
+ .call => try genCall(file, inst.castTag(.call).?, decl),
+ .ret => try genRet(file, inst.castTag(.ret).?, decl, tv.ty.fnReturnType()),
.retvoid => try file.main.writer().print("return;", .{}),
else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
}
@@ -105,9 +105,9 @@ fn genFn(file: *C, decl: *Decl) !void {
try writer.writeAll("}\n\n");
}
-fn genRet(file: *C, inst: *Inst.Ret, decl: *Decl, expected_return_type: Type) !void {
+fn genRet(file: *C, inst: *Inst.UnOp, decl: *Decl, expected_return_type: Type) !void {
const writer = file.main.writer();
- const ret_value = inst.args.operand;
+ const ret_value = inst.operand;
const value = ret_value.value().?;
if (expected_return_type.eql(ret_value.ty))
return file.fail(decl.src(), "TODO return {}", .{expected_return_type})
@@ -126,7 +126,7 @@ fn genRet(file: *C, inst: *Inst.Ret, decl: *Decl, expected_return_type: Type) !v
fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
const writer = file.main.writer();
const header = file.header.writer();
- if (inst.args.func.cast(Inst.Constant)) |func_inst| {
+ if (inst.func.castTag(.constant)) |func_inst| {
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const target = func_val.func.owner_decl;
const target_ty = target.typed_value.most_recent.typed_value.ty;
@@ -144,7 +144,7 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
} else {
return file.fail(decl.src(), "TODO non-function call target?", .{});
}
- if (inst.args.args.len != 0) {
+ if (inst.args.len != 0) {
return file.fail(decl.src(), "TODO function arguments", .{});
}
} else {
@@ -152,14 +152,13 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
}
}
-fn genAsm(file: *C, inst: *Inst.Assembly, decl: *Decl) !void {
- const as = inst.args;
+fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void {
const writer = file.main.writer();
for (as.inputs) |i, index| {
if (i[0] == '{' and i[i.len - 1] == '}') {
const reg = i[1 .. i.len - 1];
const arg = as.args[index];
- if (arg.cast(Inst.Constant)) |c| {
+ if (arg.castTag(.constant)) |c| {
if (c.val.tag() == .int_u64) {
try writer.writeAll("register ");
try renderType(file, writer, arg.ty, decl.src());
@@ -190,7 +189,7 @@ fn genAsm(file: *C, inst: *Inst.Assembly, decl: *Decl) !void {
if (index > 0) {
try writer.writeAll(", ");
}
- if (arg.cast(Inst.Constant)) |c| {
+ if (arg.castTag(.constant)) |c| {
try writer.print("\"\"({}_constant)", .{reg});
} else {
// This is blocked by the earlier test
src-self-hosted/astgen.zig
@@ -173,8 +173,8 @@ fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.In
const if_src = tree.token_locs[if_node.if_token].start;
const condbr = try mod.addZIRInstSpecial(&block_scope.base, if_src, zir.Inst.CondBr, .{
.condition = cond,
- .true_body = undefined, // populated below
- .false_body = undefined, // populated below
+ .then_body = undefined, // populated below
+ .else_body = undefined, // populated below
}, .{});
const block = try mod.addZIRInstBlock(scope, if_src, .{
@@ -196,7 +196,7 @@ fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.In
.operand = then_result,
}, .{});
}
- condbr.positionals.true_body = .{
+ condbr.positionals.then_body = .{
.instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items),
};
@@ -225,7 +225,7 @@ fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.In
.block = block,
}, .{});
}
- condbr.positionals.false_body = .{
+ condbr.positionals.else_body = .{
.instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
};
src-self-hosted/codegen.zig
@@ -290,6 +290,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
next_stack_offset: u32 = 0,
fn markRegUsed(self: *Branch, reg: Register) void {
+ if (FreeRegInt == u0) return;
const index = reg.allocIndex() orelse return;
const ShiftInt = std.math.Log2Int(FreeRegInt);
const shift = @intCast(ShiftInt, index);
@@ -297,6 +298,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn markRegFree(self: *Branch, reg: Register) void {
+ if (FreeRegInt == u0) return;
const index = reg.allocIndex() orelse return;
const ShiftInt = std.math.Log2Int(FreeRegInt);
const shift = @intCast(ShiftInt, index);
@@ -407,40 +409,64 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
for (body.instructions) |inst| {
const new_inst = try self.genFuncInst(inst);
try inst_table.putNoClobber(self.gpa, inst, new_inst);
- // TODO process operand deaths
+
+ var i: ir.Inst.DeathsBitIndex = 0;
+ while (inst.getOperand(i)) |operand| : (i += 1) {
+ if (inst.operandDies(i))
+ self.processDeath(operand);
+ }
+ }
+ }
+
+ fn processDeath(self: *Self, inst: *ir.Inst) void {
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ const entry = branch.inst_table.getEntry(inst) orelse return;
+ const prev_value = entry.value;
+ entry.value = .dead;
+ switch (prev_value) {
+ .register => |reg| {
+ _ = branch.registers.remove(reg);
+ branch.markRegFree(reg);
+ },
+ else => {}, // TODO process stack allocation death
}
}
fn genFuncInst(self: *Self, inst: *ir.Inst) !MCValue {
switch (inst.tag) {
- .add => return self.genAdd(inst.cast(ir.Inst.Add).?),
- .arg => return self.genArg(inst.cast(ir.Inst.Arg).?),
- .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?),
- .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?),
- .block => return self.genBlock(inst.cast(ir.Inst.Block).?),
- .br => return self.genBr(inst.cast(ir.Inst.Br).?),
+ .add => return self.genAdd(inst.castTag(.add).?),
+ .arg => return self.genArg(inst.castTag(.arg).?),
+ .assembly => return self.genAsm(inst.castTag(.assembly).?),
+ .bitcast => return self.genBitCast(inst.castTag(.bitcast).?),
+ .block => return self.genBlock(inst.castTag(.block).?),
+ .br => return self.genBr(inst.castTag(.br).?),
.breakpoint => return self.genBreakpoint(inst.src),
- .brvoid => return self.genBrVoid(inst.cast(ir.Inst.BrVoid).?),
- .call => return self.genCall(inst.cast(ir.Inst.Call).?),
- .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?),
- .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?),
+ .brvoid => return self.genBrVoid(inst.castTag(.brvoid).?),
+ .call => return self.genCall(inst.castTag(.call).?),
+ .cmp_lt => return self.genCmp(inst.castTag(.cmp_lt).?, .lt),
+ .cmp_lte => return self.genCmp(inst.castTag(.cmp_lte).?, .lte),
+ .cmp_eq => return self.genCmp(inst.castTag(.cmp_eq).?, .eq),
+ .cmp_gte => return self.genCmp(inst.castTag(.cmp_gte).?, .gte),
+ .cmp_gt => return self.genCmp(inst.castTag(.cmp_gt).?, .gt),
+ .cmp_neq => return self.genCmp(inst.castTag(.cmp_neq).?, .neq),
+ .condbr => return self.genCondBr(inst.castTag(.condbr).?),
.constant => unreachable, // excluded from function bodies
- .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?),
- .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?),
- .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?),
- .ret => return self.genRet(inst.cast(ir.Inst.Ret).?),
- .retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?),
- .sub => return self.genSub(inst.cast(ir.Inst.Sub).?),
+ .isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?),
+ .isnull => return self.genIsNull(inst.castTag(.isnull).?),
+ .ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?),
+ .ret => return self.genRet(inst.castTag(.ret).?),
+ .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?),
+ .sub => return self.genSub(inst.castTag(.sub).?),
.unreach => return MCValue{ .unreach = {} },
- .not => return self.genNot(inst.cast(ir.Inst.Not).?),
+ .not => return self.genNot(inst.castTag(.not).?),
}
}
- fn genNot(self: *Self, inst: *ir.Inst.Not) !MCValue {
+ fn genNot(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
- const operand = try self.resolveInst(inst.args.operand);
+ const operand = try self.resolveInst(inst.operand);
switch (operand) {
.dead => unreachable,
.unreach => unreachable,
@@ -473,36 +499,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.base = .{
.tag = .constant,
.deaths = 0,
- .ty = inst.args.operand.ty,
- .src = inst.args.operand.src,
+ .ty = inst.operand.ty,
+ .src = inst.operand.src,
},
.val = Value.initTag(.bool_true),
};
- return try self.genX8664BinMath(&inst.base, inst.args.operand, &imm.base, 6, 0x30);
+ return try self.genX8664BinMath(&inst.base, inst.operand, &imm.base, 6, 0x30);
},
else => return self.fail(inst.base.src, "TODO implement NOT for {}", .{self.target.cpu.arch}),
}
}
- fn genAdd(self: *Self, inst: *ir.Inst.Add) !MCValue {
+ fn genAdd(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
switch (arch) {
.x86_64 => {
- return try self.genX8664BinMath(&inst.base, inst.args.lhs, inst.args.rhs, 0, 0x00);
+ return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 0, 0x00);
},
else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}),
}
}
- fn genSub(self: *Self, inst: *ir.Inst.Sub) !MCValue {
+ fn genSub(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
switch (arch) {
.x86_64 => {
- return try self.genX8664BinMath(&inst.base, inst.args.lhs, inst.args.rhs, 5, 0x28);
+ return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 5, 0x28);
},
else => return self.fail(inst.base.src, "TODO implement sub for {}", .{self.target.cpu.arch}),
}
@@ -625,7 +651,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn genArg(self: *Self, inst: *ir.Inst.Arg) !MCValue {
+ fn genArg(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
if (FreeRegInt == u0) {
return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch});
}
@@ -659,7 +685,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn genCall(self: *Self, inst: *ir.Inst.Call) !MCValue {
- const fn_ty = inst.args.func.ty;
+ const fn_ty = inst.func.ty;
const cc = fn_ty.fnCallingConvention();
const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
defer self.gpa.free(param_types);
@@ -671,8 +697,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (arch) {
.x86_64 => {
for (mc_args) |mc_arg, arg_i| {
- const arg = inst.args.args[arg_i];
- const arg_mcv = try self.resolveInst(inst.args.args[arg_i]);
+ const arg = inst.args[arg_i];
+ const arg_mcv = try self.resolveInst(inst.args[arg_i]);
switch (mc_arg) {
.none => continue,
.register => |reg| {
@@ -694,7 +720,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {
+ if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const func = func_val.func;
const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
@@ -742,16 +768,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return .unreach;
}
- fn genRet(self: *Self, inst: *ir.Inst.Ret) !MCValue {
- const operand = try self.resolveInst(inst.args.operand);
+ fn genRet(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+ const operand = try self.resolveInst(inst.operand);
return self.ret(inst.base.src, operand);
}
- fn genRetVoid(self: *Self, inst: *ir.Inst.RetVoid) !MCValue {
+ fn genRetVoid(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
return self.ret(inst.base.src, .none);
}
- fn genCmp(self: *Self, inst: *ir.Inst.Cmp) !MCValue {
+ fn genCmp(self: *Self, inst: *ir.Inst.BinOp, op: std.math.CompareOperator) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
@@ -759,25 +785,25 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.x86_64 => {
try self.code.ensureCapacity(self.code.items.len + 8);
- const lhs = try self.resolveInst(inst.args.lhs);
- const rhs = try self.resolveInst(inst.args.rhs);
+ const lhs = try self.resolveInst(inst.lhs);
+ const rhs = try self.resolveInst(inst.rhs);
// There are 2 operands, destination and source.
// Either one, but not both, can be a memory operand.
// Source operand can be an immediate, 8 bits or 32 bits.
const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory()))
- try self.copyToNewRegister(inst.args.lhs)
+ try self.copyToNewRegister(inst.lhs)
else
lhs;
// This instruction supports only signed 32-bit immediates at most.
- const src_mcv = try self.limitImmediateType(inst.args.rhs, i32);
+ const src_mcv = try self.limitImmediateType(inst.rhs, i32);
try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38);
- const info = inst.args.lhs.ty.intInfo(self.target.*);
+ const info = inst.lhs.ty.intInfo(self.target.*);
if (info.signed) {
- return MCValue{ .compare_flags_signed = inst.args.op };
+ return MCValue{ .compare_flags_signed = op };
} else {
- return MCValue{ .compare_flags_unsigned = inst.args.op };
+ return MCValue{ .compare_flags_unsigned = op };
}
},
else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}),
@@ -789,7 +815,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.x86_64 => {
try self.code.ensureCapacity(self.code.items.len + 6);
- const cond = try self.resolveInst(inst.args.condition);
+ const cond = try self.resolveInst(inst.condition);
switch (cond) {
.compare_flags_signed => |cmp_op| {
// Here we map to the opposite opcode because the jump is to the false branch.
@@ -838,19 +864,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode });
const reloc = Reloc{ .rel32 = self.code.items.len };
self.code.items.len += 4;
- try self.genBody(inst.args.true_body);
+ try self.genBody(inst.then_body);
try self.performReloc(inst.base.src, reloc);
- try self.genBody(inst.args.false_body);
+ try self.genBody(inst.else_body);
return MCValue.unreach;
}
- fn genIsNull(self: *Self, inst: *ir.Inst.IsNull) !MCValue {
+ fn genIsNull(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
switch (arch) {
else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.target.cpu.arch}),
}
}
- fn genIsNonNull(self: *Self, inst: *ir.Inst.IsNonNull) !MCValue {
+ fn genIsNonNull(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
// Here you can specialize this instruction if it makes sense to, otherwise the default
// will call genIsNull and invert the result.
switch (arch) {
@@ -864,7 +890,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
// A block is nothing but a setup to be able to jump to the end.
defer inst.codegen.relocs.deinit(self.gpa);
- try self.genBody(inst.args.body);
+ try self.genBody(inst.body);
for (inst.codegen.relocs.items) |reloc| try self.performReloc(inst.base.src, reloc);
@@ -883,17 +909,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn genBr(self: *Self, inst: *ir.Inst.Br) !MCValue {
- if (!inst.args.operand.ty.hasCodeGenBits())
- return self.brVoid(inst.base.src, inst.args.block);
+ if (!inst.operand.ty.hasCodeGenBits())
+ return self.brVoid(inst.base.src, inst.block);
- const operand = try self.resolveInst(inst.args.operand);
+ const operand = try self.resolveInst(inst.operand);
switch (arch) {
else => return self.fail(inst.base.src, "TODO implement br for {}", .{self.target.cpu.arch}),
}
}
fn genBrVoid(self: *Self, inst: *ir.Inst.BrVoid) !MCValue {
- return self.brVoid(inst.base.src, inst.args.block);
+ return self.brVoid(inst.base.src, inst.block);
}
fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue {
@@ -915,29 +941,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn genAsm(self: *Self, inst: *ir.Inst.Assembly) !MCValue {
- if (!inst.args.is_volatile and inst.base.isUnused())
+ if (!inst.is_volatile and inst.base.isUnused())
return MCValue.dead;
if (arch != .x86_64 and arch != .i386) {
return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{});
}
- for (inst.args.inputs) |input, i| {
+ for (inst.inputs) |input, i| {
if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input});
}
const reg_name = input[1 .. input.len - 1];
const reg = parseRegName(reg_name) orelse
return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
- const arg = try self.resolveInst(inst.args.args[i]);
+ const arg = try self.resolveInst(inst.args[i]);
try self.genSetReg(inst.base.src, reg, arg);
}
- if (mem.eql(u8, inst.args.asm_source, "syscall")) {
+ if (mem.eql(u8, inst.asm_source, "syscall")) {
try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 });
} else {
return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{});
}
- if (inst.args.output) |output| {
+ if (inst.output) |output| {
if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output});
}
@@ -1169,13 +1195,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn genPtrToInt(self: *Self, inst: *ir.Inst.PtrToInt) !MCValue {
+ fn genPtrToInt(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
// no-op
- return self.resolveInst(inst.args.ptr);
+ return self.resolveInst(inst.operand);
}
- fn genBitCast(self: *Self, inst: *ir.Inst.BitCast) !MCValue {
- const operand = try self.resolveInst(inst.args.operand);
+ fn genBitCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+ const operand = try self.resolveInst(inst.operand);
return operand;
}
src-self-hosted/ir.zig
@@ -55,7 +55,12 @@ pub const Inst = struct {
breakpoint,
brvoid,
call,
- cmp,
+ cmp_lt,
+ cmp_lte,
+ cmp_eq,
+ cmp_gte,
+ cmp_gt,
+ cmp_neq,
condbr,
constant,
isnonnull,
@@ -66,13 +71,80 @@ pub const Inst = struct {
sub,
unreach,
not,
+
+ /// There is one-to-one correspondence between tag and type for now,
+ /// but this will not always be the case. For example, binary operations
+ /// such as + and - will have different tags but the same type.
+ pub fn Type(tag: Tag) type {
+ return switch (tag) {
+ .retvoid,
+ .unreach,
+ .arg,
+ .breakpoint,
+ => NoOp,
+
+ .ret,
+ .bitcast,
+ .not,
+ .isnonnull,
+ .isnull,
+ .ptrtoint,
+ => UnOp,
+
+ .add,
+ .sub,
+ .cmp_lt,
+ .cmp_lte,
+ .cmp_eq,
+ .cmp_gte,
+ .cmp_gt,
+ .cmp_neq,
+ => BinOp,
+
+ .assembly => Assembly,
+ .block => Block,
+ .br => Br,
+ .brvoid => BrVoid,
+ .call => Call,
+ .condbr => CondBr,
+ .constant => Constant,
+ };
+ }
+
+ pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
+ return switch (op) {
+ .lt => .cmp_lt,
+ .lte => .cmp_lte,
+ .eq => .cmp_eq,
+ .gte => .cmp_gte,
+ .gt => .cmp_gt,
+ .neq => .cmp_neq,
+ };
+ }
};
+ /// Prefer `castTag` to this.
pub fn cast(base: *Inst, comptime T: type) ?*T {
- if (base.tag != T.base_tag)
- return null;
+ if (@hasField(T, "base_tag")) {
+ return base.castTag(T.base_tag);
+ }
+ inline for (@typeInfo(Tag).Enum.fields) |field| {
+ const tag = @intToEnum(Tag, field.value);
+ if (base.tag == tag) {
+ if (T == tag.Type()) {
+ return @fieldParentPtr(T, "base", base);
+ }
+ return null;
+ }
+ }
+ unreachable;
+ }
- return @fieldParentPtr(T, "base", base);
+ pub fn castTag(base: *Inst, comptime tag: Tag) ?*tag.Type() {
+ if (base.tag == tag) {
+ return @fieldParentPtr(tag.Type(), "base", base);
+ }
+ return null;
}
pub fn Args(comptime T: type) type {
@@ -88,186 +160,219 @@ pub const Inst = struct {
return inst.val;
}
- pub const Add = struct {
- pub const base_tag = Tag.add;
+ pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator {
+ return switch (self.base.tag) {
+ .cmp_lt => .lt,
+ .cmp_lte => .lte,
+ .cmp_eq => .eq,
+ .cmp_gte => .gte,
+ .cmp_gt => .gt,
+ .cmp_neq => .neq,
+ else => null,
+ };
+ }
+
+ pub fn operandCount(base: *Inst) usize {
+ inline for (@typeInfo(Tag).Enum.fields) |field| {
+ const tag = @intToEnum(Tag, field.value);
+ if (tag == base.tag) {
+ return @fieldParentPtr(tag.Type(), "base", base).operandCount();
+ }
+ }
+ unreachable;
+ }
+
+ pub fn getOperand(base: *Inst, index: usize) ?*Inst {
+ inline for (@typeInfo(Tag).Enum.fields) |field| {
+ const tag = @intToEnum(Tag, field.value);
+ if (tag == base.tag) {
+ return @fieldParentPtr(tag.Type(), "base", base).getOperand(index);
+ }
+ }
+ unreachable;
+ }
+
+ pub const NoOp = struct {
base: Inst,
- args: struct {
- lhs: *Inst,
- rhs: *Inst,
- },
+ pub fn operandCount(self: *const NoOp) usize {
+ return 0;
+ }
+ pub fn getOperand(self: *const NoOp, index: usize) ?*Inst {
+ return null;
+ }
};
- pub const Arg = struct {
- pub const base_tag = Tag.arg;
+ pub const UnOp = struct {
base: Inst,
- args: void,
+ operand: *Inst,
+
+ pub fn operandCount(self: *const UnOp) usize {
+ return 1;
+ }
+ pub fn getOperand(self: *const UnOp, index: usize) ?*Inst {
+ if (index == 0)
+ return self.operand;
+ return null;
+ }
};
- pub const Assembly = struct {
- pub const base_tag = Tag.assembly;
+ pub const BinOp = struct {
base: Inst,
+ lhs: *Inst,
+ rhs: *Inst,
- args: struct {
- asm_source: []const u8,
- is_volatile: bool,
- output: ?[]const u8,
- inputs: []const []const u8,
- clobbers: []const []const u8,
- args: []const *Inst,
- },
+ pub fn operandCount(self: *const BinOp) usize {
+ return 2;
+ }
+ pub fn getOperand(self: *const BinOp, index: usize) ?*Inst {
+ var i = index;
+
+ if (i < 1)
+ return self.lhs;
+ i -= 1;
+
+ if (i < 1)
+ return self.rhs;
+ i -= 1;
+
+ return null;
+ }
};
- pub const BitCast = struct {
- pub const base_tag = Tag.bitcast;
+ pub const Assembly = struct {
+ pub const base_tag = Tag.assembly;
base: Inst,
- args: struct {
- operand: *Inst,
- },
+ asm_source: []const u8,
+ is_volatile: bool,
+ output: ?[]const u8,
+ inputs: []const []const u8,
+ clobbers: []const []const u8,
+ args: []const *Inst,
+
+ pub fn operandCount(self: *const Assembly) usize {
+ return self.args.len;
+ }
+ pub fn getOperand(self: *const Assembly, index: usize) ?*Inst {
+ if (index < self.args.len)
+ return self.args[index];
+ return null;
+ }
};
pub const Block = struct {
pub const base_tag = Tag.block;
+
base: Inst,
- args: struct {
- body: Body,
- },
+ body: Body,
/// This memory is reserved for codegen code to do whatever it needs to here.
codegen: codegen.BlockData = .{},
+
+ pub fn operandCount(self: *const Block) usize {
+ return 0;
+ }
+ pub fn getOperand(self: *const Block, index: usize) ?*Inst {
+ return null;
+ }
};
pub const Br = struct {
pub const base_tag = Tag.br;
- base: Inst,
- args: struct {
- block: *Block,
- operand: *Inst,
- },
- };
- pub const Breakpoint = struct {
- pub const base_tag = Tag.breakpoint;
base: Inst,
- args: void,
+ block: *Block,
+ operand: *Inst,
+
+ pub fn operandCount(self: *const Br) usize {
+ return 0;
+ }
+ pub fn getOperand(self: *const Br, index: usize) ?*Inst {
+ if (index == 0)
+ return self.operand;
+ return null;
+ }
};
pub const BrVoid = struct {
pub const base_tag = Tag.brvoid;
+
base: Inst,
- args: struct {
- block: *Block,
- },
+ block: *Block,
+
+ pub fn operandCount(self: *const BrVoid) usize {
+ return 0;
+ }
+ pub fn getOperand(self: *const BrVoid, index: usize) ?*Inst {
+ return null;
+ }
};
pub const Call = struct {
pub const base_tag = Tag.call;
+
base: Inst,
- args: struct {
- func: *Inst,
- args: []const *Inst,
- },
- };
+ func: *Inst,
+ args: []const *Inst,
- pub const Cmp = struct {
- pub const base_tag = Tag.cmp;
+ pub fn operandCount(self: *const Call) usize {
+ return self.args.len + 1;
+ }
+ pub fn getOperand(self: *const Call, index: usize) ?*Inst {
+ var i = index;
- base: Inst,
- args: struct {
- lhs: *Inst,
- op: std.math.CompareOperator,
- rhs: *Inst,
- },
+ if (i < 1)
+ return self.func;
+ i -= 1;
+
+ if (i < self.args.len)
+ return self.args[i];
+ i -= self.args.len;
+
+ return null;
+ }
};
pub const CondBr = struct {
pub const base_tag = Tag.condbr;
base: Inst,
- args: struct {
- condition: *Inst,
- true_body: Body,
- false_body: Body,
- },
+ condition: *Inst,
+ then_body: Body,
+ else_body: Body,
/// Set of instructions whose lifetimes end at the start of one of the branches.
/// The `true` branch is first: `deaths[0..true_death_count]`.
/// The `false` branch is next: `(deaths + true_death_count)[..false_death_count]`.
deaths: [*]*Inst = undefined,
true_death_count: u32 = 0,
false_death_count: u32 = 0,
- };
- pub const Not = struct {
- pub const base_tag = Tag.not;
+ pub fn operandCount(self: *const CondBr) usize {
+ return 1;
+ }
+ pub fn getOperand(self: *const CondBr, index: usize) ?*Inst {
+ var i = index;
- base: Inst,
- args: struct {
- operand: *Inst,
- },
+ if (i < 1)
+ return self.condition;
+ i -= 1;
+
+ return null;
+ }
};
pub const Constant = struct {
pub const base_tag = Tag.constant;
- base: Inst,
-
- val: Value,
- };
-
- pub const IsNonNull = struct {
- pub const base_tag = Tag.isnonnull;
-
- base: Inst,
- args: struct {
- operand: *Inst,
- },
- };
-
- pub const IsNull = struct {
- pub const base_tag = Tag.isnull;
base: Inst,
- args: struct {
- operand: *Inst,
- },
- };
-
- pub const PtrToInt = struct {
- pub const base_tag = Tag.ptrtoint;
-
- base: Inst,
- args: struct {
- ptr: *Inst,
- },
- };
-
- pub const Ret = struct {
- pub const base_tag = Tag.ret;
- base: Inst,
- args: struct {
- operand: *Inst,
- },
- };
-
- pub const RetVoid = struct {
- pub const base_tag = Tag.retvoid;
- base: Inst,
- args: void,
- };
-
- pub const Sub = struct {
- pub const base_tag = Tag.sub;
- base: Inst,
-
- args: struct {
- lhs: *Inst,
- rhs: *Inst,
- },
- };
+ val: Value,
- pub const Unreach = struct {
- pub const base_tag = Tag.unreach;
- base: Inst,
- args: void,
+ pub fn operandCount(self: *const Constant) usize {
+ return 0;
+ }
+ pub fn getOperand(self: *const Constant, index: usize) ?*Inst {
+ return null;
+ }
};
};
src-self-hosted/liveness.zig
@@ -25,53 +25,38 @@ fn analyzeWithTable(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst,
while (i != 0) {
i -= 1;
const base = body.instructions[i];
- try analyzeInstGeneric(arena, table, base);
+ try analyzeInst(arena, table, base);
}
}
-fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), base: *ir.Inst) error{OutOfMemory}!void {
- // Obtain the corresponding instruction type based on the tag type.
- inline for (std.meta.declarations(ir.Inst)) |decl| {
- switch (decl.data) {
- .Type => |T| {
- if (@typeInfo(T) == .Struct and @hasDecl(T, "base_tag")) {
- if (T.base_tag == base.tag) {
- return analyzeInst(arena, table, T, @fieldParentPtr(T, "base", base));
- }
- }
- },
- else => {},
- }
- }
- unreachable;
-}
-
-fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), comptime T: type, inst: *T) error{OutOfMemory}!void {
- if (table.contains(&inst.base)) {
- inst.base.deaths = 0;
+fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), base: *ir.Inst) error{OutOfMemory}!void {
+ if (table.contains(base)) {
+ base.deaths = 0;
} else {
// No tombstone for this instruction means it is never referenced,
// and its birth marks its own death. Very metal ๐ค
- inst.base.deaths = 1 << ir.Inst.unreferenced_bit_index;
+ base.deaths = 1 << ir.Inst.unreferenced_bit_index;
}
- switch (T) {
- ir.Inst.Constant => return,
- ir.Inst.Block => {
- try analyzeWithTable(arena, table, inst.args.body);
+ switch (base.tag) {
+ .constant => return,
+ .block => {
+ const inst = base.castTag(.block).?;
+ try analyzeWithTable(arena, table, inst.body);
// We let this continue so that it can possibly mark the block as
// unreferenced below.
},
- ir.Inst.CondBr => {
+ .condbr => {
+ const inst = base.castTag(.condbr).?;
var true_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);
defer true_table.deinit();
- try true_table.ensureCapacity(inst.args.true_body.instructions.len);
- try analyzeWithTable(arena, &true_table, inst.args.true_body);
+ try true_table.ensureCapacity(inst.then_body.instructions.len);
+ try analyzeWithTable(arena, &true_table, inst.then_body);
var false_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);
defer false_table.deinit();
- try false_table.ensureCapacity(inst.args.false_body.instructions.len);
- try analyzeWithTable(arena, &false_table, inst.args.false_body);
+ try false_table.ensureCapacity(inst.else_body.instructions.len);
+ try analyzeWithTable(arena, &false_table, inst.else_body);
// Each death that occurs inside one branch, but not the other, needs
// to be added as a death immediately upon entering the other branch.
@@ -112,47 +97,22 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void
// instruction, and the deaths flag for the CondBr instruction will indicate whether the
// condition's lifetime ends immediately before entering any branch.
},
- ir.Inst.Call => {
- // Call instructions have a runtime-known number of operands so we have to handle them ourselves here.
- const needed_bits = 1 + inst.args.args.len;
- if (needed_bits <= ir.Inst.deaths_bits) {
- var bit_i: ir.Inst.DeathsBitIndex = 0;
- {
- const prev = try table.fetchPut(inst.args.func, {});
- if (prev == null) inst.base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i;
- bit_i += 1;
- }
- for (inst.args.args) |arg| {
- const prev = try table.fetchPut(arg, {});
- if (prev == null) inst.base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i;
- bit_i += 1;
- }
- } else {
- @panic("Handle liveness analysis for function calls with many parameters");
- }
- },
else => {},
}
- const Args = ir.Inst.Args(T);
- if (Args == void) {
- return;
- }
-
- comptime var arg_index: usize = 0;
- inline for (std.meta.fields(Args)) |field| {
- if (field.field_type == *ir.Inst) {
- if (arg_index >= 6) {
- @compileError("out of bits to mark deaths of operands");
- }
- const prev = try table.fetchPut(@field(inst.args, field.name), {});
+ const needed_bits = base.operandCount();
+ if (needed_bits <= ir.Inst.deaths_bits) {
+ var bit_i: ir.Inst.DeathsBitIndex = 0;
+ while (base.getOperand(bit_i)) |operand| : (bit_i += 1) {
+ const prev = try table.fetchPut(operand, {});
if (prev == null) {
// Death.
- inst.base.deaths |= 1 << arg_index;
+ base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i;
}
- arg_index += 1;
}
+ } else {
+ @panic("Handle liveness analysis for instructions with many parameters");
}
- std.log.debug(.liveness, "analyze {}: 0b{b}\n", .{ inst.base.tag, inst.base.deaths });
+ std.log.debug(.liveness, "analyze {}: 0b{b}\n", .{ base.tag, base.deaths });
}
src-self-hosted/Module.zig
@@ -1349,8 +1349,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
fn analyzeBodyValueAsType(self: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type {
try self.analyzeBody(&block_scope.base, body);
for (block_scope.instructions.items) |inst| {
- if (inst.cast(Inst.Ret)) |ret| {
- const val = try self.resolveConstValue(&block_scope.base, ret.args.operand);
+ if (inst.castTag(.ret)) |ret| {
+ const val = try self.resolveConstValue(&block_scope.base, ret.operand);
return val.toType();
} else {
return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});
@@ -1938,16 +1938,132 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
};
}
-fn addNewInstArgs(
+fn addNoOp(
self: *Module,
block: *Scope.Block,
src: usize,
ty: Type,
- comptime T: type,
- args: Inst.Args(T),
+ comptime tag: Inst.Tag,
+) !*Inst {
+ const inst = try block.arena.create(tag.Type());
+ inst.* = .{
+ .base = .{
+ .tag = tag,
+ .ty = ty,
+ .src = src,
+ },
+ };
+ try block.instructions.append(self.gpa, &inst.base);
+ return &inst.base;
+}
+
+fn addUnOp(
+ self: *Module,
+ block: *Scope.Block,
+ src: usize,
+ ty: Type,
+ tag: Inst.Tag,
+ operand: *Inst,
+) !*Inst {
+ const inst = try block.arena.create(Inst.UnOp);
+ inst.* = .{
+ .base = .{
+ .tag = tag,
+ .ty = ty,
+ .src = src,
+ },
+ .operand = operand,
+ };
+ try block.instructions.append(self.gpa, &inst.base);
+ return &inst.base;
+}
+
+fn addBinOp(
+ self: *Module,
+ block: *Scope.Block,
+ src: usize,
+ ty: Type,
+ tag: Inst.Tag,
+ lhs: *Inst,
+ rhs: *Inst,
+) !*Inst {
+ const inst = try block.arena.create(Inst.BinOp);
+ inst.* = .{
+ .base = .{
+ .tag = tag,
+ .ty = ty,
+ .src = src,
+ },
+ .lhs = lhs,
+ .rhs = rhs,
+ };
+ try block.instructions.append(self.gpa, &inst.base);
+ return &inst.base;
+}
+
+fn addBr(
+ self: *Module,
+ scope_block: *Scope.Block,
+ src: usize,
+ target_block: *Inst.Block,
+ operand: *Inst,
+) !*Inst {
+ const inst = try scope_block.arena.create(Inst.Br);
+ inst.* = .{
+ .base = .{
+ .tag = .br,
+ .ty = Type.initTag(.noreturn),
+ .src = src,
+ },
+ .operand = operand,
+ .block = target_block,
+ };
+ try scope_block.instructions.append(self.gpa, &inst.base);
+ return &inst.base;
+}
+
+fn addCondBr(
+ self: *Module,
+ block: *Scope.Block,
+ src: usize,
+ condition: *Inst,
+ then_body: ir.Body,
+ else_body: ir.Body,
) !*Inst {
- const inst = try self.addNewInst(block, src, ty, T);
- inst.args = args;
+ const inst = try block.arena.create(Inst.CondBr);
+ inst.* = .{
+ .base = .{
+ .tag = .condbr,
+ .ty = Type.initTag(.noreturn),
+ .src = src,
+ },
+ .condition = condition,
+ .then_body = then_body,
+ .else_body = else_body,
+ };
+ try block.instructions.append(self.gpa, &inst.base);
+ return &inst.base;
+}
+
+fn addCall(
+ self: *Module,
+ block: *Scope.Block,
+ src: usize,
+ ty: Type,
+ func: *Inst,
+ args: []const *Inst,
+) !*Inst {
+ const inst = try block.arena.create(Inst.Call);
+ inst.* = .{
+ .base = .{
+ .tag = .call,
+ .ty = ty,
+ .src = src,
+ },
+ .func = func,
+ .args = args,
+ };
+ try block.instructions.append(self.gpa, &inst.base);
return &inst.base;
}
@@ -2017,7 +2133,6 @@ fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime
.ty = ty,
.src = src,
},
- .args = undefined,
};
try block.instructions.append(self.gpa, &inst.base);
return inst;
@@ -2269,7 +2384,7 @@ fn analyzeInstArg(self: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!
});
}
const param_type = fn_ty.fnParamType(param_index);
- return self.addNewInstArgs(b, inst.base.src, param_type, Inst.Arg, {});
+ return self.addNoOp(b, inst.base.src, param_type, .arg);
}
fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst {
@@ -2285,7 +2400,7 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr
.ty = undefined, // Set after analysis.
.src = inst.base.src,
},
- .args = undefined,
+ .body = undefined,
};
var child_block: Scope.Block = .{
@@ -2316,13 +2431,13 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr
// to emit a jump instruction to after the block when it encounters the break.
try parent_block.instructions.append(self.gpa, &block_inst.base);
block_inst.base.ty = try self.resolvePeerTypes(scope, label.results.items);
- block_inst.args.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) };
+ block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) };
return &block_inst.base;
}
fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.Breakpoint) InnerError!*Inst {
const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, {});
+ return self.addNoOp(b, inst.base.src, Type.initTag(.void), .breakpoint);
}
fn analyzeInstBreak(self: *Module, scope: *Scope, inst: *zir.Inst.Break) InnerError!*Inst {
@@ -2350,10 +2465,7 @@ fn analyzeBreak(
if (label.zir_block == zir_block) {
try label.results.append(self.gpa, operand);
const b = try self.requireRuntimeBlock(scope, src);
- return self.addNewInstArgs(b, src, Type.initTag(.noreturn), Inst.Br, .{
- .block = label.block_inst,
- .operand = operand,
- });
+ return self.addBr(b, src, label.block_inst, operand);
}
}
opt_block = block.parent;
@@ -2484,10 +2596,7 @@ fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerErro
}
const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Call, .{
- .func = func,
- .args = casted_args,
- });
+ return self.addCall(b, inst.base.src, Type.initTag(.void), func, casted_args);
}
fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst {
@@ -2570,14 +2679,14 @@ fn analyzeInstAs(self: *Module, scope: *Scope, as: *zir.Inst.As) InnerError!*Ins
}
fn analyzeInstPtrToInt(self: *Module, scope: *Scope, ptrtoint: *zir.Inst.PtrToInt) InnerError!*Inst {
- const ptr = try self.resolveInst(scope, ptrtoint.positionals.ptr);
+ const ptr = try self.resolveInst(scope, ptrtoint.positionals.operand);
if (ptr.ty.zigTypeTag() != .Pointer) {
- return self.fail(scope, ptrtoint.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty});
+ return self.fail(scope, ptrtoint.positionals.operand.src, "expected pointer, found '{}'", .{ptr.ty});
}
// TODO handle known-pointer-address
const b = try self.requireRuntimeBlock(scope, ptrtoint.base.src);
const ty = Type.initTag(.usize);
- return self.addNewInstArgs(b, ptrtoint.base.src, ty, Inst.PtrToInt, .{ .ptr = ptr });
+ return self.addUnOp(b, ptrtoint.base.src, ty, .ptrtoint, ptr);
}
fn analyzeInstFieldPtr(self: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr) InnerError!*Inst {
@@ -2734,10 +2843,7 @@ fn analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError!
}
const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNewInstArgs(b, inst.base.src, lhs.ty, Inst.Add, .{
- .lhs = lhs,
- .rhs = rhs,
- });
+ return self.addBinOp(b, inst.base.src, lhs.ty, .add, lhs, rhs);
}
return self.fail(scope, inst.base.src, "TODO analyze add for {} + {}", .{ lhs.ty.zigTypeTag(), rhs.ty.zigTypeTag() });
}
@@ -2783,14 +2889,22 @@ fn analyzeInstAsm(self: *Module, scope: *Scope, assembly: *zir.Inst.Asm) InnerEr
}
const b = try self.requireRuntimeBlock(scope, assembly.base.src);
- return self.addNewInstArgs(b, assembly.base.src, return_type, Inst.Assembly, .{
+ const inst = try b.arena.create(Inst.Assembly);
+ inst.* = .{
+ .base = .{
+ .tag = .assembly,
+ .ty = return_type,
+ .src = assembly.base.src,
+ },
.asm_source = asm_source,
.is_volatile = assembly.kw_args.@"volatile",
.output = output,
.inputs = inputs,
.clobbers = clobbers,
.args = args,
- });
+ };
+ try b.instructions.append(self.gpa, &inst.base);
+ return &inst.base;
}
fn analyzeInstCmp(self: *Module, scope: *Scope, inst: *zir.Inst.Cmp) InnerError!*Inst {
@@ -2818,15 +2932,12 @@ fn analyzeInstCmp(self: *Module, scope: *Scope, inst: *zir.Inst.Cmp) InnerError!
return self.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null);
}
const b = try self.requireRuntimeBlock(scope, inst.base.src);
- switch (op) {
- .eq => return self.addNewInstArgs(b, inst.base.src, Type.initTag(.bool), Inst.IsNull, .{
- .operand = opt_operand,
- }),
- .neq => return self.addNewInstArgs(b, inst.base.src, Type.initTag(.bool), Inst.IsNonNull, .{
- .operand = opt_operand,
- }),
+ const inst_tag: Inst.Tag = switch (op) {
+ .eq => .isnull,
+ .neq => .isnonnull,
else => unreachable,
- }
+ };
+ return self.addUnOp(b, inst.base.src, Type.initTag(.bool), inst_tag, opt_operand);
} else if (is_equality_cmp and
((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))
{
@@ -2861,7 +2972,7 @@ fn analyzeInstBoolNot(self: *Module, scope: *Scope, inst: *zir.Inst.BoolNot) Inn
return self.constBool(scope, inst.base.src, !val.toBool());
}
const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNewInstArgs(b, inst.base.src, bool_type, Inst.Not, .{ .operand = operand });
+ return self.addUnOp(b, inst.base.src, bool_type, .not, operand);
}
fn analyzeInstIsNull(self: *Module, scope: *Scope, inst: *zir.Inst.IsNull) InnerError!*Inst {
@@ -2879,7 +2990,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner
const cond = try self.coerce(scope, Type.initTag(.bool), uncasted_cond);
if (try self.resolveDefinedValue(scope, cond)) |cond_val| {
- const body = if (cond_val.toBool()) &inst.positionals.true_body else &inst.positionals.false_body;
+ const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body;
try self.analyzeBody(scope, body.*);
return self.constVoid(scope, inst.base.src);
}
@@ -2894,7 +3005,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner
.arena = parent_block.arena,
};
defer true_block.instructions.deinit(self.gpa);
- try self.analyzeBody(&true_block.base, inst.positionals.true_body);
+ try self.analyzeBody(&true_block.base, inst.positionals.then_body);
var false_block: Scope.Block = .{
.parent = parent_block,
@@ -2904,13 +3015,11 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner
.arena = parent_block.arena,
};
defer false_block.instructions.deinit(self.gpa);
- try self.analyzeBody(&false_block.base, inst.positionals.false_body);
+ try self.analyzeBody(&false_block.base, inst.positionals.else_body);
- return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.noreturn), Inst.CondBr, Inst.Args(Inst.CondBr){
- .condition = cond,
- .true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) },
- .false_body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) },
- });
+ const then_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) };
+ const else_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) };
+ return self.addCondBr(parent_block, inst.base.src, cond, then_body, else_body);
}
fn wantSafety(self: *Module, scope: *Scope) bool {
@@ -2926,20 +3035,20 @@ fn analyzeInstUnreachable(self: *Module, scope: *Scope, unreach: *zir.Inst.Unrea
const b = try self.requireRuntimeBlock(scope, unreach.base.src);
if (self.wantSafety(scope)) {
// TODO Once we have a panic function to call, call it here instead of this.
- _ = try self.addNewInstArgs(b, unreach.base.src, Type.initTag(.void), Inst.Breakpoint, {});
+ _ = try self.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint);
}
- return self.addNewInstArgs(b, unreach.base.src, Type.initTag(.noreturn), Inst.Unreach, {});
+ return self.addNoOp(b, unreach.base.src, Type.initTag(.noreturn), .unreach);
}
fn analyzeInstRet(self: *Module, scope: *Scope, inst: *zir.Inst.Return) InnerError!*Inst {
const operand = try self.resolveInst(scope, inst.positionals.operand);
const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.Ret, .{ .operand = operand });
+ return self.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand);
}
fn analyzeInstRetVoid(self: *Module, scope: *Scope, inst: *zir.Inst.ReturnVoid) InnerError!*Inst {
const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.RetVoid, {});
+ return self.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid);
}
fn analyzeBody(self: *Module, scope: *Scope, body: zir.Module.Body) !void {
@@ -3027,11 +3136,7 @@ fn cmpNumeric(
};
const casted_lhs = try self.coerce(scope, dest_type, lhs);
const casted_rhs = try self.coerce(scope, dest_type, rhs);
- return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, .{
- .lhs = casted_lhs,
- .rhs = casted_rhs,
- .op = op,
- });
+ return self.addBinOp(b, src, dest_type, Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs);
}
// For mixed unsigned integer sizes, implicit cast both operands to the larger integer.
// For mixed signed and unsigned integers, implicit cast both operands to a signed
@@ -3131,11 +3236,7 @@ fn cmpNumeric(
const casted_lhs = try self.coerce(scope, dest_type, lhs);
const casted_rhs = try self.coerce(scope, dest_type, rhs);
- return self.addNewInstArgs(b, src, Type.initTag(.bool), Inst.Cmp, .{
- .lhs = casted_lhs,
- .rhs = casted_rhs,
- .op = op,
- });
+ return self.addBinOp(b, src, Type.initTag(.bool), Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs);
}
fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type {
@@ -3236,7 +3337,7 @@ fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
}
// TODO validate the type size and other compile errors
const b = try self.requireRuntimeBlock(scope, inst.src);
- return self.addNewInstArgs(b, inst.src, dest_type, Inst.BitCast, .{ .operand = inst });
+ return self.addUnOp(b, inst.src, dest_type, .bitcast, inst);
}
fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
src-self-hosted/zir.zig
@@ -337,7 +337,7 @@ pub const Inst = struct {
base: Inst,
positionals: struct {
- ptr: *Inst,
+ operand: *Inst,
},
kw_args: struct {},
};
@@ -629,8 +629,8 @@ pub const Inst = struct {
positionals: struct {
condition: *Inst,
- true_body: Module.Body,
- false_body: Module.Body,
+ then_body: Module.Body,
+ else_body: Module.Body,
},
kw_args: struct {},
};
@@ -1615,7 +1615,7 @@ const EmitZIR = struct {
}
}
- fn emitTrivial(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst {
+ fn emitNoOp(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst {
const new_inst = try self.arena.allocator.create(T);
new_inst.* = .{
.base = .{
@@ -1628,6 +1628,72 @@ const EmitZIR = struct {
return &new_inst.base;
}
+ fn emitCmp(
+ self: *EmitZIR,
+ src: usize,
+ new_body: ZirBody,
+ old_inst: *ir.Inst.BinOp,
+ op: std.math.CompareOperator,
+ ) Allocator.Error!*Inst {
+ const new_inst = try self.arena.allocator.create(Inst.Cmp);
+ new_inst.* = .{
+ .base = .{
+ .src = src,
+ .tag = Inst.Cmp.base_tag,
+ },
+ .positionals = .{
+ .lhs = try self.resolveInst(new_body, old_inst.lhs),
+ .rhs = try self.resolveInst(new_body, old_inst.rhs),
+ .op = op,
+ },
+ .kw_args = .{},
+ };
+ return &new_inst.base;
+ }
+
+ fn emitUnOp(
+ self: *EmitZIR,
+ src: usize,
+ new_body: ZirBody,
+ old_inst: *ir.Inst.UnOp,
+ comptime I: type,
+ ) Allocator.Error!*Inst {
+ const new_inst = try self.arena.allocator.create(I);
+ new_inst.* = .{
+ .base = .{
+ .src = src,
+ .tag = I.base_tag,
+ },
+ .positionals = .{
+ .operand = try self.resolveInst(new_body, old_inst.operand),
+ },
+ .kw_args = .{},
+ };
+ return &new_inst.base;
+ }
+
+ fn emitBinOp(
+ self: *EmitZIR,
+ src: usize,
+ new_body: ZirBody,
+ old_inst: *ir.Inst.BinOp,
+ comptime I: type,
+ ) Allocator.Error!*Inst {
+ const new_inst = try self.arena.allocator.create(I);
+ new_inst.* = .{
+ .base = .{
+ .src = src,
+ .tag = I.base_tag,
+ },
+ .positionals = .{
+ .lhs = try self.resolveInst(new_body, old_inst.lhs),
+ .rhs = try self.resolveInst(new_body, old_inst.rhs),
+ },
+ .kw_args = .{},
+ };
+ return &new_inst.base;
+ }
+
fn emitBody(
self: *EmitZIR,
body: ir.Body,
@@ -1640,69 +1706,48 @@ const EmitZIR = struct {
};
for (body.instructions) |inst| {
const new_inst = switch (inst.tag) {
- .not => blk: {
- const old_inst = inst.cast(ir.Inst.Not).?;
- assert(inst.ty.zigTypeTag() == .Bool);
- const new_inst = try self.arena.allocator.create(Inst.BoolNot);
- new_inst.* = .{
- .base = .{
- .src = inst.src,
- .tag = Inst.BoolNot.base_tag,
- },
- .positionals = .{
- .operand = try self.resolveInst(new_body, old_inst.args.operand),
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
- .add => blk: {
- const old_inst = inst.cast(ir.Inst.Add).?;
- const new_inst = try self.arena.allocator.create(Inst.Add);
- new_inst.* = .{
- .base = .{
- .src = inst.src,
- .tag = Inst.Add.base_tag,
- },
- .positionals = .{
- .lhs = try self.resolveInst(new_body, old_inst.args.lhs),
- .rhs = try self.resolveInst(new_body, old_inst.args.rhs),
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
- .sub => blk: {
- const old_inst = inst.cast(ir.Inst.Sub).?;
- const new_inst = try self.arena.allocator.create(Inst.Sub);
+ .constant => unreachable, // excluded from function bodies
+
+ .arg => try self.emitNoOp(inst.src, Inst.Arg),
+ .breakpoint => try self.emitNoOp(inst.src, Inst.Breakpoint),
+ .unreach => try self.emitNoOp(inst.src, Inst.Unreachable),
+ .retvoid => try self.emitNoOp(inst.src, Inst.ReturnVoid),
+
+ .not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, Inst.BoolNot),
+ .ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, Inst.Return),
+ .ptrtoint => try self.emitUnOp(inst.src, new_body, inst.castTag(.ptrtoint).?, Inst.PtrToInt),
+ .isnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnull).?, Inst.IsNull),
+ .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, Inst.IsNonNull),
+
+ .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, Inst.Add),
+ .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, Inst.Sub),
+
+ .cmp_lt => try self.emitCmp(inst.src, new_body, inst.castTag(.cmp_lt).?, .lt),
+ .cmp_lte => try self.emitCmp(inst.src, new_body, inst.castTag(.cmp_lte).?, .lte),
+ .cmp_eq => try self.emitCmp(inst.src, new_body, inst.castTag(.cmp_eq).?, .eq),
+ .cmp_gte => try self.emitCmp(inst.src, new_body, inst.castTag(.cmp_gte).?, .gte),
+ .cmp_gt => try self.emitCmp(inst.src, new_body, inst.castTag(.cmp_gt).?, .gt),
+ .cmp_neq => try self.emitCmp(inst.src, new_body, inst.castTag(.cmp_neq).?, .neq),
+
+ .bitcast => blk: {
+ const old_inst = inst.castTag(.bitcast).?;
+ const new_inst = try self.arena.allocator.create(Inst.BitCast);
new_inst.* = .{
.base = .{
.src = inst.src,
- .tag = Inst.Sub.base_tag,
+ .tag = Inst.BitCast.base_tag,
},
.positionals = .{
- .lhs = try self.resolveInst(new_body, old_inst.args.lhs),
- .rhs = try self.resolveInst(new_body, old_inst.args.rhs),
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
- .arg => blk: {
- const old_inst = inst.cast(ir.Inst.Arg).?;
- const new_inst = try self.arena.allocator.create(Inst.Arg);
- new_inst.* = .{
- .base = .{
- .src = inst.src,
- .tag = Inst.Arg.base_tag,
+ .dest_type = (try self.emitType(inst.src, inst.ty)).inst,
+ .operand = try self.resolveInst(new_body, old_inst.operand),
},
- .positionals = .{},
.kw_args = .{},
};
break :blk &new_inst.base;
},
+
.block => blk: {
- const old_inst = inst.cast(ir.Inst.Block).?;
+ const old_inst = inst.castTag(.block).?;
const new_inst = try self.arena.allocator.create(Inst.Block);
try self.block_table.put(old_inst, new_inst);
@@ -1710,7 +1755,7 @@ const EmitZIR = struct {
var block_body = std.ArrayList(*Inst).init(self.allocator);
defer block_body.deinit();
- try self.emitBody(old_inst.args.body, inst_table, &block_body);
+ try self.emitBody(old_inst.body, inst_table, &block_body);
new_inst.* = .{
.base = .{
@@ -1725,47 +1770,49 @@ const EmitZIR = struct {
break :blk &new_inst.base;
},
- .br => blk: {
- const old_inst = inst.cast(ir.Inst.Br).?;
- const new_block = self.block_table.get(old_inst.args.block).?;
- const new_inst = try self.arena.allocator.create(Inst.Break);
+
+ .brvoid => blk: {
+ const old_inst = inst.cast(ir.Inst.BrVoid).?;
+ const new_block = self.block_table.get(old_inst.block).?;
+ const new_inst = try self.arena.allocator.create(Inst.BreakVoid);
new_inst.* = .{
.base = .{
.src = inst.src,
- .tag = Inst.Break.base_tag,
+ .tag = Inst.BreakVoid.base_tag,
},
.positionals = .{
.block = new_block,
- .operand = try self.resolveInst(new_body, old_inst.args.operand),
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
- .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint),
- .brvoid => blk: {
- const old_inst = inst.cast(ir.Inst.BrVoid).?;
- const new_block = self.block_table.get(old_inst.args.block).?;
- const new_inst = try self.arena.allocator.create(Inst.BreakVoid);
+
+ .br => blk: {
+ const old_inst = inst.castTag(.br).?;
+ const new_block = self.block_table.get(old_inst.block).?;
+ const new_inst = try self.arena.allocator.create(Inst.Break);
new_inst.* = .{
.base = .{
.src = inst.src,
- .tag = Inst.BreakVoid.base_tag,
+ .tag = Inst.Break.base_tag,
},
.positionals = .{
.block = new_block,
+ .operand = try self.resolveInst(new_body, old_inst.operand),
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
+
.call => blk: {
- const old_inst = inst.cast(ir.Inst.Call).?;
+ const old_inst = inst.castTag(.call).?;
const new_inst = try self.arena.allocator.create(Inst.Call);
- const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len);
+ const args = try self.arena.allocator.alloc(*Inst, old_inst.args.len);
for (args) |*elem, i| {
- elem.* = try self.resolveInst(new_body, old_inst.args.args[i]);
+ elem.* = try self.resolveInst(new_body, old_inst.args[i]);
}
new_inst.* = .{
.base = .{
@@ -1773,48 +1820,31 @@ const EmitZIR = struct {
.tag = Inst.Call.base_tag,
},
.positionals = .{
- .func = try self.resolveInst(new_body, old_inst.args.func),
+ .func = try self.resolveInst(new_body, old_inst.func),
.args = args,
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
- .unreach => try self.emitTrivial(inst.src, Inst.Unreachable),
- .ret => blk: {
- const old_inst = inst.cast(ir.Inst.Ret).?;
- const new_inst = try self.arena.allocator.create(Inst.Return);
- new_inst.* = .{
- .base = .{
- .src = inst.src,
- .tag = Inst.Return.base_tag,
- },
- .positionals = .{
- .operand = try self.resolveInst(new_body, old_inst.args.operand),
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
- .retvoid => try self.emitTrivial(inst.src, Inst.ReturnVoid),
- .constant => unreachable, // excluded from function bodies
+
.assembly => blk: {
- const old_inst = inst.cast(ir.Inst.Assembly).?;
+ const old_inst = inst.castTag(.assembly).?;
const new_inst = try self.arena.allocator.create(Inst.Asm);
- const inputs = try self.arena.allocator.alloc(*Inst, old_inst.args.inputs.len);
+ const inputs = try self.arena.allocator.alloc(*Inst, old_inst.inputs.len);
for (inputs) |*elem, i| {
- elem.* = (try self.emitStringLiteral(inst.src, old_inst.args.inputs[i])).inst;
+ elem.* = (try self.emitStringLiteral(inst.src, old_inst.inputs[i])).inst;
}
- const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.args.clobbers.len);
+ const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.clobbers.len);
for (clobbers) |*elem, i| {
- elem.* = (try self.emitStringLiteral(inst.src, old_inst.args.clobbers[i])).inst;
+ elem.* = (try self.emitStringLiteral(inst.src, old_inst.clobbers[i])).inst;
}
- const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len);
+ const args = try self.arena.allocator.alloc(*Inst, old_inst.args.len);
for (args) |*elem, i| {
- elem.* = try self.resolveInst(new_body, old_inst.args.args[i]);
+ elem.* = try self.resolveInst(new_body, old_inst.args[i]);
}
new_inst.* = .{
@@ -1823,12 +1853,12 @@ const EmitZIR = struct {
.tag = Inst.Asm.base_tag,
},
.positionals = .{
- .asm_source = (try self.emitStringLiteral(inst.src, old_inst.args.asm_source)).inst,
+ .asm_source = (try self.emitStringLiteral(inst.src, old_inst.asm_source)).inst,
.return_type = (try self.emitType(inst.src, inst.ty)).inst,
},
.kw_args = .{
- .@"volatile" = old_inst.args.is_volatile,
- .output = if (old_inst.args.output) |o|
+ .@"volatile" = old_inst.is_volatile,
+ .output = if (old_inst.output) |o|
(try self.emitStringLiteral(inst.src, o)).inst
else
null,
@@ -1839,65 +1869,18 @@ const EmitZIR = struct {
};
break :blk &new_inst.base;
},
- .ptrtoint => blk: {
- const old_inst = inst.cast(ir.Inst.PtrToInt).?;
- const new_inst = try self.arena.allocator.create(Inst.PtrToInt);
- new_inst.* = .{
- .base = .{
- .src = inst.src,
- .tag = Inst.PtrToInt.base_tag,
- },
- .positionals = .{
- .ptr = try self.resolveInst(new_body, old_inst.args.ptr),
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
- .bitcast => blk: {
- const old_inst = inst.cast(ir.Inst.BitCast).?;
- const new_inst = try self.arena.allocator.create(Inst.BitCast);
- new_inst.* = .{
- .base = .{
- .src = inst.src,
- .tag = Inst.BitCast.base_tag,
- },
- .positionals = .{
- .dest_type = (try self.emitType(inst.src, inst.ty)).inst,
- .operand = try self.resolveInst(new_body, old_inst.args.operand),
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
- .cmp => blk: {
- const old_inst = inst.cast(ir.Inst.Cmp).?;
- const new_inst = try self.arena.allocator.create(Inst.Cmp);
- new_inst.* = .{
- .base = .{
- .src = inst.src,
- .tag = Inst.Cmp.base_tag,
- },
- .positionals = .{
- .lhs = try self.resolveInst(new_body, old_inst.args.lhs),
- .rhs = try self.resolveInst(new_body, old_inst.args.rhs),
- .op = old_inst.args.op,
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
+
.condbr => blk: {
- const old_inst = inst.cast(ir.Inst.CondBr).?;
+ const old_inst = inst.castTag(.condbr).?;
- var true_body = std.ArrayList(*Inst).init(self.allocator);
- var false_body = std.ArrayList(*Inst).init(self.allocator);
+ var then_body = std.ArrayList(*Inst).init(self.allocator);
+ var else_body = std.ArrayList(*Inst).init(self.allocator);
- defer true_body.deinit();
- defer false_body.deinit();
+ defer then_body.deinit();
+ defer else_body.deinit();
- try self.emitBody(old_inst.args.true_body, inst_table, &true_body);
- try self.emitBody(old_inst.args.false_body, inst_table, &false_body);
+ try self.emitBody(old_inst.then_body, inst_table, &then_body);
+ try self.emitBody(old_inst.else_body, inst_table, &else_body);
const new_inst = try self.arena.allocator.create(Inst.CondBr);
new_inst.* = .{
@@ -1906,39 +1889,9 @@ const EmitZIR = struct {
.tag = Inst.CondBr.base_tag,
},
.positionals = .{
- .condition = try self.resolveInst(new_body, old_inst.args.condition),
- .true_body = .{ .instructions = true_body.toOwnedSlice() },
- .false_body = .{ .instructions = false_body.toOwnedSlice() },
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
- .isnull => blk: {
- const old_inst = inst.cast(ir.Inst.IsNull).?;
- const new_inst = try self.arena.allocator.create(Inst.IsNull);
- new_inst.* = .{
- .base = .{
- .src = inst.src,
- .tag = Inst.IsNull.base_tag,
- },
- .positionals = .{
- .operand = try self.resolveInst(new_body, old_inst.args.operand),
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
- .isnonnull => blk: {
- const old_inst = inst.cast(ir.Inst.IsNonNull).?;
- const new_inst = try self.arena.allocator.create(Inst.IsNonNull);
- new_inst.* = .{
- .base = .{
- .src = inst.src,
- .tag = Inst.IsNonNull.base_tag,
- },
- .positionals = .{
- .operand = try self.resolveInst(new_body, old_inst.args.operand),
+ .condition = try self.resolveInst(new_body, old_inst.condition),
+ .then_body = .{ .instructions = then_body.toOwnedSlice() },
+ .else_body = .{ .instructions = else_body.toOwnedSlice() },
},
.kw_args = .{},
};
test/stage2/compare_output.zig
@@ -267,5 +267,42 @@ pub fn addCases(ctx: *TestContext) !void {
,
"",
);
+
+ // Requires a second move. The register allocator should figure out to re-use rax.
+ case.addCompareOutput(
+ \\export fn _start() noreturn {
+ \\ add(3, 4);
+ \\
+ \\ exit();
+ \\}
+ \\
+ \\fn add(a: u32, b: u32) void {
+ \\ const c = a + b; // 7
+ \\ const d = a + c; // 10
+ \\ const e = d + b; // 14
+ \\ const f = d + e; // 24
+ \\ const g = e + f; // 38
+ \\ const h = f + g; // 62
+ \\ const i = g + h; // 100
+ \\ const j = i + d; // 110
+ \\ assert(j == 110);
+ \\}
+ \\
+ \\pub fn assert(ok: bool) void {
+ \\ if (!ok) unreachable; // assertion failure
+ \\}
+ \\
+ \\fn exit() noreturn {
+ \\ asm volatile ("syscall"
+ \\ :
+ \\ : [number] "{rax}" (231),
+ \\ [arg1] "{rdi}" (0)
+ \\ : "rcx", "r11", "memory"
+ \\ );
+ \\ unreachable;
+ \\}
+ ,
+ "",
+ );
}
}