mirror of
https://github.com/ziglang/zig.git
synced 2026-02-12 20:37:54 +00:00
Merge pull request #7565 from joachimschmidt557/stage2-arm
stage2 ARM: add conditional branches
This commit is contained in:
commit
dfacac916f
@ -42,6 +42,11 @@ pub const Reloc = union(enum) {
|
||||
/// To perform the reloc, write 32-bit signed little-endian integer
|
||||
/// which is a relative jump, based on the address following the reloc.
|
||||
rel32: usize,
|
||||
/// A branch in the ARM instruction set
|
||||
arm_branch: struct {
|
||||
pos: usize,
|
||||
cond: @import("codegen/arm.zig").Condition,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Result = union(enum) {
|
||||
@ -1273,11 +1278,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
|
||||
switch (op) {
|
||||
.add => {
|
||||
// TODO runtime safety checks (overflow)
|
||||
writeInt(u32, try self.code.addManyAsArray(4), Instruction.add(.al, dst_reg, dst_reg, operand).toU32());
|
||||
},
|
||||
.sub => {
|
||||
// TODO runtime safety checks (underflow)
|
||||
if (lhs_is_dest) {
|
||||
writeInt(u32, try self.code.addManyAsArray(4), Instruction.sub(.al, dst_reg, dst_reg, operand).toU32());
|
||||
} else {
|
||||
@ -1293,6 +1296,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
.not, .xor => {
|
||||
writeInt(u32, try self.code.addManyAsArray(4), Instruction.eor(.al, dst_reg, dst_reg, operand).toU32());
|
||||
},
|
||||
.cmp_eq => {
|
||||
writeInt(u32, try self.code.addManyAsArray(4), Instruction.cmp(.al, dst_reg, operand).toU32());
|
||||
},
|
||||
else => unreachable, // not a binary instruction
|
||||
}
|
||||
}
|
||||
@ -1953,6 +1959,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
.unsigned => MCValue{ .compare_flags_unsigned = op },
|
||||
};
|
||||
},
|
||||
.arm, .armeb => {
|
||||
const lhs = try self.resolveInst(inst.lhs);
|
||||
const rhs = try self.resolveInst(inst.rhs);
|
||||
|
||||
const src_mcv = rhs;
|
||||
const dst_mcv = if (lhs != .register) try self.copyToNewRegister(&inst.base, lhs) else lhs;
|
||||
|
||||
try self.genArmBinOpCode(inst.base.src, dst_mcv.register, src_mcv, true, .cmp_eq);
|
||||
const info = inst.lhs.ty.intInfo(self.target.*);
|
||||
return switch (info.signedness) {
|
||||
.signed => MCValue{ .compare_flags_signed = op },
|
||||
.unsigned => MCValue{ .compare_flags_unsigned = op },
|
||||
};
|
||||
},
|
||||
else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}),
|
||||
}
|
||||
}
|
||||
@ -2016,6 +2036,37 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
self.code.items.len += 4;
|
||||
break :reloc reloc;
|
||||
},
|
||||
.arm, .armeb => reloc: {
|
||||
const condition: Condition = switch (cond) {
|
||||
.compare_flags_signed => |cmp_op| blk: {
|
||||
// Here we map to the opposite condition because the jump is to the false branch.
|
||||
const condition = Condition.fromCompareOperatorSigned(cmp_op);
|
||||
break :blk condition.negate();
|
||||
},
|
||||
.compare_flags_unsigned => |cmp_op| blk: {
|
||||
// Here we map to the opposite condition because the jump is to the false branch.
|
||||
const condition = Condition.fromCompareOperatorUnsigned(cmp_op);
|
||||
break :blk condition.negate();
|
||||
},
|
||||
.register => |reg| blk: {
|
||||
// cmp reg, 1
|
||||
// bne ...
|
||||
const op = Instruction.Operand.imm(1, 0);
|
||||
writeInt(u32, try self.code.addManyAsArray(4), Instruction.cmp(.al, reg, op).toU32());
|
||||
break :blk .ne;
|
||||
},
|
||||
else => return self.fail(inst.base.src, "TODO implement condbr {} when condition is {}", .{ self.target.cpu.arch, @tagName(cond) }),
|
||||
};
|
||||
|
||||
const reloc = Reloc{
|
||||
.arm_branch = .{
|
||||
.pos = self.code.items.len,
|
||||
.cond = condition,
|
||||
},
|
||||
};
|
||||
try self.code.resize(self.code.items.len + 4);
|
||||
break :reloc reloc;
|
||||
},
|
||||
else => return self.fail(inst.base.src, "TODO implement condbr {}", .{self.target.cpu.arch}),
|
||||
};
|
||||
|
||||
@ -2175,7 +2226,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
}
|
||||
},
|
||||
.arm, .armeb => {
|
||||
if (math.cast(i26, @intCast(i32, index) - @intCast(i32, self.code.items.len))) |delta| {
|
||||
if (math.cast(i26, @intCast(i32, index) - @intCast(i32, self.code.items.len + 8))) |delta| {
|
||||
writeInt(u32, try self.code.addManyAsArray(4), Instruction.b(.al, delta).toU32());
|
||||
} else |err| {
|
||||
return self.fail(src, "TODO: enable larger branch offset", .{});
|
||||
@ -2225,6 +2276,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
return self.fail(src, "unable to perform relocation: jump too far", .{});
|
||||
mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt);
|
||||
},
|
||||
.arm_branch => |info| {
|
||||
switch (arch) {
|
||||
.arm, .armeb => {
|
||||
const amt = @intCast(i32, self.code.items.len) - @intCast(i32, info.pos + 8);
|
||||
if (math.cast(i26, amt)) |delta| {
|
||||
writeInt(u32, self.code.items[info.pos..][0..4], Instruction.b(info.cond, delta).toU32());
|
||||
} else |_| {
|
||||
return self.fail(src, "TODO: enable larger branch offset", .{});
|
||||
}
|
||||
},
|
||||
else => unreachable, // attempting to perfrom an ARM relocation on a non-ARM target arch
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -2278,6 +2342,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
// Leave the jump offset undefined
|
||||
block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 });
|
||||
},
|
||||
.arm, .armeb => {
|
||||
try self.code.resize(self.code.items.len + 4);
|
||||
block.codegen.relocs.appendAssumeCapacity(.{
|
||||
.arm_branch = .{
|
||||
.pos = self.code.items.len - 4,
|
||||
.cond = .al,
|
||||
},
|
||||
});
|
||||
},
|
||||
else => return self.fail(src, "TODO implement brvoid for {}", .{self.target.cpu.arch}),
|
||||
}
|
||||
return .none;
|
||||
@ -2650,6 +2723,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
// Write the debug undefined value.
|
||||
return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa });
|
||||
},
|
||||
.compare_flags_unsigned,
|
||||
.compare_flags_signed,
|
||||
=> |op| {
|
||||
const condition = switch (mcv) {
|
||||
.compare_flags_unsigned => Condition.fromCompareOperatorUnsigned(op),
|
||||
.compare_flags_signed => Condition.fromCompareOperatorSigned(op),
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
// mov reg, 0
|
||||
// moveq reg, 1
|
||||
const zero = Instruction.Operand.imm(0, 0);
|
||||
const one = Instruction.Operand.imm(1, 0);
|
||||
writeInt(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, zero).toU32());
|
||||
writeInt(u32, try self.code.addManyAsArray(4), Instruction.mov(condition, reg, one).toU32());
|
||||
},
|
||||
.immediate => |x| {
|
||||
if (x > math.maxInt(u32)) return self.fail(src, "ARM registers are 32-bit wide", .{});
|
||||
|
||||
|
||||
@ -35,8 +35,74 @@ pub const Condition = enum(u4) {
|
||||
le,
|
||||
/// always
|
||||
al,
|
||||
|
||||
/// Converts a std.math.CompareOperator into a condition flag,
|
||||
/// i.e. returns the condition that is true iff the result of the
|
||||
/// comparison is true. Assumes signed comparison
|
||||
pub fn fromCompareOperatorSigned(op: std.math.CompareOperator) Condition {
|
||||
return switch (op) {
|
||||
.gte => .ge,
|
||||
.gt => .gt,
|
||||
.neq => .ne,
|
||||
.lt => .lt,
|
||||
.lte => .le,
|
||||
.eq => .eq,
|
||||
};
|
||||
}
|
||||
|
||||
/// Converts a std.math.CompareOperator into a condition flag,
|
||||
/// i.e. returns the condition that is true iff the result of the
|
||||
/// comparison is true. Assumes unsigned comparison
|
||||
pub fn fromCompareOperatorUnsigned(op: std.math.CompareOperator) Condition {
|
||||
return switch (op) {
|
||||
.gte => .cs,
|
||||
.gt => .hi,
|
||||
.neq => .ne,
|
||||
.lt => .cc,
|
||||
.lte => .ls,
|
||||
.eq => .eq,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the condition which is true iff the given condition is
|
||||
/// false (if such a condition exists)
|
||||
pub fn negate(cond: Condition) Condition {
|
||||
return switch (cond) {
|
||||
.eq => .ne,
|
||||
.ne => .eq,
|
||||
.cs => .cc,
|
||||
.cc => .cs,
|
||||
.mi => .pl,
|
||||
.pl => .mi,
|
||||
.vs => .vc,
|
||||
.vc => .vs,
|
||||
.hi => .ls,
|
||||
.ls => .hi,
|
||||
.ge => .lt,
|
||||
.lt => .ge,
|
||||
.gt => .le,
|
||||
.le => .gt,
|
||||
.al => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test "condition from CompareOperator" {
|
||||
testing.expectEqual(@as(Condition, .eq), Condition.fromCompareOperatorSigned(.eq));
|
||||
testing.expectEqual(@as(Condition, .eq), Condition.fromCompareOperatorUnsigned(.eq));
|
||||
|
||||
testing.expectEqual(@as(Condition, .gt), Condition.fromCompareOperatorSigned(.gt));
|
||||
testing.expectEqual(@as(Condition, .hi), Condition.fromCompareOperatorUnsigned(.gt));
|
||||
|
||||
testing.expectEqual(@as(Condition, .le), Condition.fromCompareOperatorSigned(.lte));
|
||||
testing.expectEqual(@as(Condition, .ls), Condition.fromCompareOperatorUnsigned(.lte));
|
||||
}
|
||||
|
||||
test "negate condition" {
|
||||
testing.expectEqual(@as(Condition, .eq), Condition.ne.negate());
|
||||
testing.expectEqual(@as(Condition, .ne), Condition.eq.negate());
|
||||
}
|
||||
|
||||
/// Represents a register in the ARM instruction set architecture
|
||||
pub const Register = enum(u5) {
|
||||
r0,
|
||||
|
||||
@ -282,4 +282,66 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
"123456",
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
var case = ctx.exe("if statements", linux_arm);
|
||||
// Simple if statement in assert
|
||||
case.addCompareOutput(
|
||||
\\export fn _start() noreturn {
|
||||
\\ var x: u32 = 123;
|
||||
\\ var y: u32 = 42;
|
||||
\\ assert(x > y);
|
||||
\\ exit();
|
||||
\\}
|
||||
\\
|
||||
\\fn assert(ok: bool) void {
|
||||
\\ if (!ok) unreachable;
|
||||
\\}
|
||||
\\
|
||||
\\fn exit() noreturn {
|
||||
\\ asm volatile ("svc #0"
|
||||
\\ :
|
||||
\\ : [number] "{r7}" (1),
|
||||
\\ [arg1] "{r0}" (0)
|
||||
\\ : "memory"
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
var case = ctx.exe("while loops", linux_arm);
|
||||
// Simple while loop with assert
|
||||
case.addCompareOutput(
|
||||
\\export fn _start() noreturn {
|
||||
\\ var x: u32 = 2020;
|
||||
\\ var i: u32 = 0;
|
||||
\\ while (x > 0) {
|
||||
\\ x -= 2;
|
||||
\\ i += 1;
|
||||
\\ }
|
||||
\\ assert(i == 1010);
|
||||
\\ exit();
|
||||
\\}
|
||||
\\
|
||||
\\fn assert(ok: bool) void {
|
||||
\\ if (!ok) unreachable;
|
||||
\\}
|
||||
\\
|
||||
\\fn exit() noreturn {
|
||||
\\ asm volatile ("svc #0"
|
||||
\\ :
|
||||
\\ : [number] "{r7}" (1),
|
||||
\\ [arg1] "{r0}" (0)
|
||||
\\ : "memory"
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
,
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user