stage2: implement loading-storing via pointer (in register)

* load address (pointer) to a stack variable in a register via
  `lea` instruction
* store value on the stack via a pointer stored in a register via
  `mov [reg], imm` instruction
* the lowerings naturally are handled automatically by Mir -> Isel
  layer
* add initial (without safety) implementation of `.optional_payload`
* add matching stage2 test cases
This commit is contained in:
Jakub Konka 2021-12-31 17:57:59 +01:00
parent bc12d50170
commit c7f774803a
2 changed files with 154 additions and 32 deletions

View File

@ -1143,10 +1143,24 @@ fn airShr(self: *Self, inst: Air.Inst.Index) !void {
fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) !void { fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op; const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst)) const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
.dead const operand = try self.resolveInst(ty_op.operand);
else if (self.wantSafety()) {
return self.fail("TODO implement .optional_payload for {}", .{self.target.cpu.arch}); // TODO check for null
return self.fail("TODO implement check for null in .optional_payload", .{});
}
const dst_mcv: MCValue = blk: {
if (self.reuseOperand(inst, ty_op.operand, 0, operand)) {
break :blk operand;
} else {
break :blk try self.allocRegOrMem(inst, true);
}
};
const ty = self.air.typeOf(ty_op.operand);
var buf: Type.Payload.ElemType = undefined;
try self.load(dst_mcv, operand, ty.optionalChild(&buf));
break :result dst_mcv;
};
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
} }
@ -1408,16 +1422,16 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo
.compare_flags_unsigned => unreachable, .compare_flags_unsigned => unreachable,
.compare_flags_signed => unreachable, .compare_flags_signed => unreachable,
.immediate => |imm| try self.setRegOrMem(elem_ty, dst_mcv, .{ .memory = imm }), .immediate => |imm| try self.setRegOrMem(elem_ty, dst_mcv, .{ .memory = imm }),
.ptr_stack_offset => |off| try self.setRegOrMem(elem_ty, dst_mcv, .{ .stack_offset = off }), .ptr_stack_offset => |off| {
try self.setRegOrMem(elem_ty, dst_mcv, .{ .stack_offset = off });
},
.ptr_embedded_in_code => |off| { .ptr_embedded_in_code => |off| {
try self.setRegOrMem(elem_ty, dst_mcv, .{ .embedded_in_code = off }); try self.setRegOrMem(elem_ty, dst_mcv, .{ .embedded_in_code = off });
}, },
.embedded_in_code => { .embedded_in_code => {
return self.fail("TODO implement loading from MCValue.embedded_in_code", .{}); return self.fail("TODO implement loading from MCValue.embedded_in_code", .{});
}, },
.register => { .register => |reg| try self.setRegOrMem(elem_ty, dst_mcv, .{ .register = reg }),
return self.fail("TODO implement loading from MCValue.register for {}", .{self.target.cpu.arch});
},
.memory => |addr| { .memory => |addr| {
const reg = try self.register_manager.allocReg(null, &.{}); const reg = try self.register_manager.allocReg(null, &.{});
try self.genSetReg(ptr_ty, reg, .{ .memory = addr }); try self.genSetReg(ptr_ty, reg, .{ .memory = addr });
@ -1479,8 +1493,8 @@ fn airStore(self: *Self, inst: Air.Inst.Index) !void {
.embedded_in_code => { .embedded_in_code => {
return self.fail("TODO implement storing to MCValue.embedded_in_code", .{}); return self.fail("TODO implement storing to MCValue.embedded_in_code", .{});
}, },
.register => { .register => |reg| {
return self.fail("TODO implement storing to MCValue.register", .{}); try self.genSetPtrReg(elem_ty, reg, value);
}, },
.memory => { .memory => {
return self.fail("TODO implement storing to MCValue.memory", .{}); return self.fail("TODO implement storing to MCValue.memory", .{});
@ -2906,11 +2920,66 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
} }
} }
/// Set pointee via pointer stored in a register.
/// mov [reg], value
fn genSetPtrReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void {
switch (mcv) {
.dead => unreachable,
.unreach, .none => return, // Nothing to do.
.immediate => |imm| {
const abi_size = ty.abiSize(self.target.*);
switch (abi_size) {
1, 2, 4 => {
// TODO this is wasteful!
// introduce new MIR tag specifically for mov [reg + 0], imm
const payload = try self.addExtra(Mir.ImmPair{
.dest_off = 0,
.operand = @bitCast(i32, @intCast(u32, imm)),
});
_ = try self.addInst(.{
.tag = .mov_mem_imm,
.ops = (Mir.Ops{
.reg1 = reg.to64(),
.flags = switch (abi_size) {
1 => 0b00,
2 => 0b01,
4 => 0b10,
else => unreachable,
},
}).encode(),
.data = .{ .payload = payload },
});
},
else => {
return self.fail("TODO implement set pointee with immediate of ABI size {d}", .{abi_size});
},
}
},
else => |other| {
return self.fail("TODO implement set pointee with {}", .{other});
},
}
}
fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void {
switch (mcv) { switch (mcv) {
.dead => unreachable, .dead => unreachable,
.ptr_stack_offset => |off| { .ptr_stack_offset => |unadjusted_off| {
return self.genSetReg(ty.elemType(), reg, .{ .stack_offset = off }); const ptr_abi_size = ty.abiSize(self.target.*);
const elem_ty = ty.childType();
const elem_abi_size = elem_ty.abiSize(self.target.*);
const off = unadjusted_off + elem_abi_size;
if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) {
return self.fail("stack offset too large", .{});
}
_ = try self.addInst(.{
.tag = .lea,
.ops = (Mir.Ops{
.reg1 = registerAlias(reg, @intCast(u32, ptr_abi_size)),
.reg2 = .rbp,
}).encode(),
.data = .{ .imm = -@intCast(i32, off) },
});
}, },
.ptr_embedded_in_code => unreachable, .ptr_embedded_in_code => unreachable,
.unreach, .none => return, // Nothing to do. .unreach, .none => return, // Nothing to do.

View File

@ -1662,27 +1662,80 @@ pub fn addCases(ctx: *TestContext) !void {
"", "",
); );
} }
} {
var case = ctx.exe("issue 7187: miscompilation with bool return type", target);
case.addCompareOutput(
\\pub fn main() void {
\\ var x: usize = 1;
\\ var y: bool = getFalse();
\\ _ = y;
\\
\\ assert(x == 1);
\\}
\\
\\fn getFalse() bool {
\\ return false;
\\}
\\
\\fn assert(ok: bool) void {
\\ if (!ok) unreachable;
\\}
, "");
}
{ {
var case = ctx.exe("issue 7187: miscompilation with bool return type", linux_x64); var case = ctx.exe("load-store via pointer deref", target);
case.addCompareOutput( case.addCompareOutput(
\\pub fn main() void { \\pub fn main() void {
\\ var x: usize = 1; \\ var x: u32 = undefined;
\\ var y: bool = getFalse(); \\ set(&x);
\\ _ = y; \\ assert(x == 123);
\\ \\}
\\ assert(x == 1); \\
\\} \\fn set(x: *u32) void {
\\ \\ x.* = 123;
\\fn getFalse() bool { \\}
\\ return false; \\
\\} \\fn assert(ok: bool) void {
\\ \\ if (!ok) unreachable;
\\fn assert(ok: bool) void { \\}
\\ if (!ok) unreachable; , "");
\\} }
, "");
{
var case = ctx.exe("optional payload", target);
case.addCompareOutput(
\\pub fn main() void {
\\ var x: u32 = undefined;
\\ const maybe_x = byPtr(&x);
\\ assert(maybe_x != null);
\\}
\\
\\fn byPtr(x: *u32) ?*u32 {
\\ return x;
\\}
\\
\\fn assert(ok: bool) void {
\\ if (!ok) unreachable;
\\}
, "");
case.addCompareOutput(
\\pub fn main() void {
\\ var x: u32 = undefined;
\\ const maybe_x = byPtr(&x);
\\ assert(maybe_x == null);
\\}
\\
\\fn byPtr(x: *u32) ?*u32 {
\\ _ = x;
\\ return null;
\\}
\\
\\fn assert(ok: bool) void {
\\ if (!ok) unreachable;
\\}
, "");
}
} }
} }