stage2 ARM: Implement calling with stack parameters

This commit is contained in:
joachimschmidt557 2021-11-29 14:11:56 +01:00 committed by Andrew Kelley
parent 725267f7c2
commit 2f18c5955a
4 changed files with 241 additions and 40 deletions

View File

@ -83,6 +83,8 @@ max_end_stack: u32 = 0,
/// to place a new stack allocation, it goes here, and then bumps `max_end_stack`.
next_stack_offset: u32 = 0,
saved_regs_stack_space: u32 = 0,
/// Debug field, used to find bugs in the compiler.
air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init,
@ -123,10 +125,12 @@ const MCValue = union(enum) {
/// The value is in the compare flags assuming a signed operation,
/// with this operator applied on top of it.
compare_flags_signed: math.CompareOperator,
/// The value is a function argument passed via the stack.
stack_argument_offset: u32,
fn isMemory(mcv: MCValue) bool {
return switch (mcv) {
.embedded_in_code, .memory, .stack_offset => true,
.embedded_in_code, .memory, .stack_offset, .stack_argument_offset => true,
else => false,
};
}
@ -152,6 +156,7 @@ const MCValue = union(enum) {
.ptr_stack_offset,
.ptr_embedded_in_code,
.undef,
.stack_argument_offset,
=> false,
.register,
@ -302,6 +307,7 @@ pub fn generate(
.prev_di_pc = 0,
.prev_di_line = module_fn.lbrace_line,
.prev_di_column = module_fn.lbrace_column,
.prologue_stack_space = call_info.stack_byte_count + function.saved_regs_stack_space,
};
defer emit.deinit();
@ -387,11 +393,14 @@ fn gen(self: *Self) !void {
.r11 = true, // fp
.r14 = true, // lr
};
self.saved_regs_stack_space = 8;
inline for (callee_preserved_regs) |reg| {
if (self.register_manager.isRegAllocated(reg)) {
@field(saved_regs, @tagName(reg)) = true;
self.saved_regs_stack_space += 4;
}
}
self.mir_instructions.set(push_reloc, .{
.tag = .push,
.cond = .al,
@ -399,9 +408,10 @@ fn gen(self: *Self) !void {
});
// Backpatch stack offset
const stack_end = self.max_end_stack;
const aligned_stack_end = mem.alignForward(stack_end, self.stack_align);
if (Instruction.Operand.fromU32(@intCast(u32, aligned_stack_end))) |op| {
const total_stack_size = self.max_end_stack + self.saved_regs_stack_space;
const aligned_total_stack_end = mem.alignForwardGeneric(u32, total_stack_size, self.stack_align);
const stack_size = aligned_total_stack_end - self.saved_regs_stack_space;
if (Instruction.Operand.fromU32(stack_size)) |op| {
self.mir_instructions.set(sub_reloc, .{
.tag = .sub,
.cond = .al,
@ -1256,6 +1266,9 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo
.stack_offset => {
return self.fail("TODO implement loading from MCValue.stack_offset", .{});
},
.stack_argument_offset => {
return self.fail("TODO implement loading from MCValue.stack_argument_offset", .{});
},
}
}
@ -1297,6 +1310,7 @@ fn airStore(self: *Self, inst: Air.Inst.Index) !void {
.dead => unreachable,
.compare_flags_unsigned => unreachable,
.compare_flags_signed => unreachable,
.stack_argument_offset => unreachable,
.immediate => |imm| {
try self.setRegOrMem(elem_ty, .{ .memory = imm }, value);
},
@ -1367,6 +1381,7 @@ fn armOperandShouldBeRegister(self: *Self, mcv: MCValue) !bool {
},
.register => true,
.stack_offset,
.stack_argument_offset,
.embedded_in_code,
.memory,
=> true,
@ -1533,6 +1548,7 @@ fn genArmBinOpCode(
.immediate => |imm| Instruction.Operand.fromU32(@intCast(u32, imm)).?,
.register => |reg| Instruction.Operand.reg(reg, Instruction.Operand.Shift.none),
.stack_offset,
.stack_argument_offset,
.embedded_in_code,
.memory,
=> unreachable,
@ -1749,6 +1765,7 @@ fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue) !void {
.none => {},
}
},
.stack_argument_offset => return self.fail("TODO genArgDbgInfo for stack_argument_offset", .{}),
else => {},
}
}
@ -1814,6 +1831,9 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void {
var info = try self.resolveCallingConventionValues(fn_ty);
defer info.deinit(self);
// Make space for the arguments passed via the stack
self.max_end_stack += info.stack_byte_count;
// Due to incremental compilation, how function calls are generated depends
// on linking.
if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) {
@ -1836,9 +1856,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void {
try self.register_manager.getReg(reg, null);
try self.genSetReg(arg_ty, reg, arg_mcv);
},
.stack_offset => {
return self.fail("TODO implement calling with parameters in memory", .{});
},
.stack_offset => unreachable,
.stack_argument_offset => |offset| try self.genSetStackArgument(
arg_ty,
info.stack_byte_count - offset,
arg_mcv,
),
.ptr_stack_offset => {
return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
},
@ -2616,16 +2639,26 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
else => return self.fail("TODO implement storing other types abi_size={}", .{abi_size}),
}
},
.memory => |vaddr| {
_ = vaddr;
return self.fail("TODO implement set stack variable from memory vaddr", .{});
.memory,
.stack_argument_offset,
=> {
if (ty.abiSize(self.target.*) <= 4) {
const reg = try self.copyToTmpRegister(ty, mcv);
return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
} else {
return self.fail("TODO implement memcpy", .{});
}
},
.stack_offset => |off| {
if (stack_offset == off)
return; // Copy stack variable to itself; nothing to do.
const reg = try self.copyToTmpRegister(ty, mcv);
return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
if (ty.abiSize(self.target.*) <= 4) {
const reg = try self.copyToTmpRegister(ty, mcv);
return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
} else {
return self.fail("TODO implement memcpy", .{});
}
},
}
}
@ -2878,10 +2911,115 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
else => return self.fail("TODO a type of size {} is not allowed in a register", .{abi_size}),
}
},
.stack_argument_offset => |unadjusted_off| {
// TODO: maybe addressing from sp instead of fp
const abi_size = ty.abiSize(self.target.*);
const adj_off = unadjusted_off + abi_size;
const tag: Mir.Inst.Tag = switch (abi_size) {
1 => .ldrb_stack_argument,
2 => .ldrh_stack_argument,
4 => .ldr_stack_argument,
else => unreachable,
};
_ = try self.addInst(.{
.tag = tag,
.cond = .al,
.data = .{ .r_stack_offset = .{
.rt = reg,
.stack_offset = @intCast(u32, adj_off),
} },
});
},
else => return self.fail("TODO implement getSetReg for arm {}", .{mcv}),
}
}
fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
switch (mcv) {
.dead => unreachable,
.none, .unreach => return,
.undef => {
if (!self.wantSafety())
return; // The already existing value will do just fine.
// TODO Upgrade this to a memset call when we have that available.
switch (ty.abiSize(self.target.*)) {
1 => return self.genSetStackArgument(ty, stack_offset, .{ .immediate = 0xaa }),
2 => return self.genSetStackArgument(ty, stack_offset, .{ .immediate = 0xaaaa }),
4 => return self.genSetStackArgument(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
8 => return self.genSetStackArgument(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
else => return self.fail("TODO implement memset", .{}),
}
},
.register => |reg| {
const abi_size = ty.abiSize(self.target.*);
const adj_off = stack_offset - abi_size;
switch (abi_size) {
1, 4 => {
const offset = if (math.cast(u12, adj_off)) |imm| blk: {
break :blk Instruction.Offset.imm(imm);
} else |_| Instruction.Offset.reg(try self.copyToTmpRegister(Type.initTag(.u32), MCValue{ .immediate = adj_off }), 0);
const tag: Mir.Inst.Tag = switch (abi_size) {
1 => .strb,
4 => .str,
else => unreachable,
};
_ = try self.addInst(.{
.tag = tag,
.cond = .al,
.data = .{ .rr_offset = .{
.rt = reg,
.rn = .sp,
.offset = .{ .offset = offset },
} },
});
},
2 => {
const offset = if (adj_off <= math.maxInt(u8)) blk: {
break :blk Instruction.ExtraLoadStoreOffset.imm(@intCast(u8, adj_off));
} else Instruction.ExtraLoadStoreOffset.reg(try self.copyToTmpRegister(Type.initTag(.u32), MCValue{ .immediate = adj_off }));
_ = try self.addInst(.{
.tag = .strh,
.cond = .al,
.data = .{ .rr_extra_offset = .{
.rt = reg,
.rn = .sp,
.offset = .{ .offset = offset },
} },
});
},
else => return self.fail("TODO implement storing other types abi_size={}", .{abi_size}),
}
},
.immediate,
.compare_flags_signed,
.compare_flags_unsigned,
.stack_offset,
.memory,
.stack_argument_offset,
.embedded_in_code,
=> {
if (ty.abiSize(self.target.*) <= 4) {
const reg = try self.copyToTmpRegister(ty, mcv);
return self.genSetStackArgument(ty, stack_offset, MCValue{ .register = reg });
} else {
return self.fail("TODO implement memcpy", .{});
}
},
.ptr_stack_offset => {
return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
},
.ptr_embedded_in_code => {
return self.fail("TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
},
}
}
fn airPtrToInt(self: *Self, inst: Air.Inst.Index) !void {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const result = try self.resolveInst(un_op);
@ -3002,27 +3140,6 @@ fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue {
}
}
/// If the MCValue is an immediate, and it does not fit within this type,
/// we put it in a register.
/// A potential opportunity for future optimization here would be keeping track
/// of the fact that the instruction is available both as an immediate
/// and as a register.
fn limitImmediateType(self: *Self, operand: Air.Inst.Ref, comptime T: type) !MCValue {
const mcv = try self.resolveInst(operand);
const ti = @typeInfo(T).Int;
switch (mcv) {
.immediate => |imm| {
// This immediate is unsigned.
const U = std.meta.Int(.unsigned, ti.bits - @boolToInt(ti.signedness == .signed));
if (imm >= math.maxInt(U)) {
return MCValue{ .register = try self.copyToTmpRegister(Type.initTag(.usize), mcv) };
}
},
else => {},
}
return mcv;
}
fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {
if (typed_value.val.isUndef())
return MCValue{ .undef = {} };
@ -3214,7 +3331,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues {
if (ty.abiAlignment(self.target.*) == 8)
nsaa = std.mem.alignForwardGeneric(u32, nsaa, 8);
result.args[i] = .{ .stack_offset = nsaa };
result.args[i] = .{ .stack_argument_offset = nsaa };
nsaa += param_size;
}
}

View File

@ -30,6 +30,10 @@ prev_di_column: u32,
/// Relative to the beginning of `code`.
prev_di_pc: usize,
/// The amount of stack space consumed by all stack arguments as well
/// as the saved callee-saved registers
prologue_stack_space: u32,
/// The branch type of every branch
branch_types: std.AutoHashMapUnmanaged(Mir.Inst.Index, BranchType) = .{},
/// For every forward branch, maps the target instruction to a list of
@ -102,6 +106,10 @@ pub fn emitMir(
.str => try emit.mirLoadStore(inst),
.strb => try emit.mirLoadStore(inst),
.ldr_stack_argument => try emit.mirLoadStack(inst),
.ldrb_stack_argument => try emit.mirLoadStack(inst),
.ldrh_stack_argument => try emit.mirLoadStack(inst),
.ldrh => try emit.mirLoadStoreExtra(inst),
.strh => try emit.mirLoadStoreExtra(inst),
@ -468,6 +476,53 @@ fn mirLoadStore(emit: *Emit, inst: Mir.Inst.Index) !void {
}
}
fn mirLoadStack(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const cond = emit.mir.instructions.items(.cond)[inst];
const r_stack_offset = emit.mir.instructions.items(.data)[inst].r_stack_offset;
const raw_offset = emit.prologue_stack_space - r_stack_offset.stack_offset;
switch (tag) {
.ldr_stack_argument => {
const offset = if (raw_offset <= math.maxInt(u12)) blk: {
break :blk Instruction.Offset.imm(@intCast(u12, raw_offset));
} else return emit.fail("TODO mirLoadStack larger offsets", .{});
try emit.writeInstruction(Instruction.ldr(
cond,
r_stack_offset.rt,
.fp,
.{ .offset = offset },
));
},
.ldrb_stack_argument => {
const offset = if (raw_offset <= math.maxInt(u12)) blk: {
break :blk Instruction.Offset.imm(@intCast(u12, raw_offset));
} else return emit.fail("TODO mirLoadStack larger offsets", .{});
try emit.writeInstruction(Instruction.ldrb(
cond,
r_stack_offset.rt,
.fp,
.{ .offset = offset },
));
},
.ldrh_stack_argument => {
const offset = if (raw_offset <= math.maxInt(u8)) blk: {
break :blk Instruction.ExtraLoadStoreOffset.imm(@intCast(u8, raw_offset));
} else return emit.fail("TODO mirLoadStack larger offsets", .{});
try emit.writeInstruction(Instruction.ldrh(
cond,
r_stack_offset.rt,
.fp,
.{ .offset = offset },
));
},
else => unreachable,
}
}
fn mirLoadStoreExtra(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const cond = emit.mir.instructions.items(.cond)[inst];

View File

@ -51,10 +51,16 @@ pub const Inst = struct {
eor,
/// Load Register
ldr,
/// Load Register
ldr_stack_argument,
/// Load Register Byte
ldrb,
/// Load Register Byte
ldrb_stack_argument,
/// Load Register Halfword
ldrh,
/// Load Register Halfword
ldrh_stack_argument,
/// Logical Shift Left
lsl,
/// Logical Shift Right
@ -124,6 +130,13 @@ pub const Inst = struct {
///
/// Used by e.g. blx
reg: Register,
/// A register and a stack offset
///
/// Used by e.g. ldr_stack_argument
r_stack_offset: struct {
rt: Register,
stack_offset: u32,
},
/// A register and a 16-bit unsigned immediate
///
/// Used by e.g. movw

View File

@ -72,6 +72,22 @@ pub fn addCases(ctx: *TestContext) !void {
,
"Hello, World!\n",
);
case.addCompareOutput(
\\pub fn main() void {
\\ assert(add(1, 2, 3, 4, 5, 6) == 21);
\\}
\\
\\fn add(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) u32 {
\\ return a + b + c + d + e + f;
\\}
\\
\\pub fn assert(ok: bool) void {
\\ if (!ok) unreachable; // assertion failure
\\}
,
"",
);
}
{
@ -465,12 +481,12 @@ pub fn addCases(ctx: *TestContext) !void {
\\ const j = i + d; // 110
\\ const k = i + j; // 210
\\ const l = k + c; // 217
\\ const m = l * d; // 2170
\\ const n = m + e; // 2184
\\ const o = n * f; // 52416
\\ const p = o + g; // 52454
\\ const q = p * h; // 3252148
\\ const r = q + i; // 3252248
\\ const m = l * d; // 2170
\\ const n = m + e; // 2184
\\ const o = n * f; // 52416
\\ const p = o + g; // 52454
\\ const q = p * h; // 3252148
\\ const r = q + i; // 3252248
\\ const s = r * j; // 357747280
\\ const t = s + k; // 357747490
\\ break :blk t;