mirror of
https://github.com/ziglang/zig.git
synced 2025-12-16 11:13:08 +00:00
Previously, the registers included r0, r1, r2, r3 which are not included in the callee saved registers according to the Procedure Call Standard for the ARM Architecture.
1271 lines
38 KiB
Zig
1271 lines
38 KiB
Zig
const std = @import("std");
|
|
const DW = std.dwarf;
|
|
const testing = std.testing;
|
|
|
|
/// The condition field specifies the flags neccessary for an
|
|
/// Instruction to be executed
|
|
pub const Condition = enum(u4) {
|
|
/// equal
|
|
eq,
|
|
/// not equal
|
|
ne,
|
|
/// unsigned higher or same
|
|
cs,
|
|
/// unsigned lower
|
|
cc,
|
|
/// negative
|
|
mi,
|
|
/// positive or zero
|
|
pl,
|
|
/// overflow
|
|
vs,
|
|
/// no overflow
|
|
vc,
|
|
/// unsigned higer
|
|
hi,
|
|
/// unsigned lower or same
|
|
ls,
|
|
/// greater or equal
|
|
ge,
|
|
/// less than
|
|
lt,
|
|
/// greater than
|
|
gt,
|
|
/// less than or equal
|
|
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,
|
|
r1,
|
|
r2,
|
|
r3,
|
|
r4,
|
|
r5,
|
|
r6,
|
|
r7,
|
|
r8,
|
|
r9,
|
|
r10,
|
|
r11,
|
|
r12,
|
|
r13,
|
|
r14,
|
|
r15,
|
|
|
|
/// Argument / result / scratch register 1
|
|
a1,
|
|
/// Argument / result / scratch register 2
|
|
a2,
|
|
/// Argument / scratch register 3
|
|
a3,
|
|
/// Argument / scratch register 4
|
|
a4,
|
|
/// Variable-register 1
|
|
v1,
|
|
/// Variable-register 2
|
|
v2,
|
|
/// Variable-register 3
|
|
v3,
|
|
/// Variable-register 4
|
|
v4,
|
|
/// Variable-register 5
|
|
v5,
|
|
/// Platform register
|
|
v6,
|
|
/// Variable-register 7
|
|
v7,
|
|
/// Frame pointer or Variable-register 8
|
|
fp,
|
|
/// Intra-Procedure-call scratch register
|
|
ip,
|
|
/// Stack pointer
|
|
sp,
|
|
/// Link register
|
|
lr,
|
|
/// Program counter
|
|
pc,
|
|
|
|
/// Returns the unique 4-bit ID of this register which is used in
|
|
/// the machine code
|
|
pub fn id(self: Register) u4 {
|
|
return @truncate(u4, @enumToInt(self));
|
|
}
|
|
|
|
/// Returns the index into `callee_preserved_regs`.
|
|
pub fn allocIndex(self: Register) ?u4 {
|
|
inline for (callee_preserved_regs) |cpreg, i| {
|
|
if (self.id() == cpreg.id()) return i;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn dwarfLocOp(self: Register) u8 {
|
|
return @as(u8, self.id()) + DW.OP_reg0;
|
|
}
|
|
};
|
|
|
|
test "Register.id" {
|
|
testing.expectEqual(@as(u4, 15), Register.r15.id());
|
|
testing.expectEqual(@as(u4, 15), Register.pc.id());
|
|
}
|
|
|
|
/// Program status registers containing flags, mode bits and other
|
|
/// vital information
|
|
pub const Psr = enum {
|
|
cpsr,
|
|
spsr,
|
|
};
|
|
|
|
pub const callee_preserved_regs = [_]Register{ .r4, .r5, .r6, .r7, .r8, .r10 };
|
|
pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 };
|
|
pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 };
|
|
|
|
/// Represents an instruction in the ARM instruction set architecture
|
|
pub const Instruction = union(enum) {
|
|
DataProcessing: packed struct {
|
|
// Note to self: The order of the fields top-to-bottom is
|
|
// right-to-left in the actual 32-bit int representation
|
|
op2: u12,
|
|
rd: u4,
|
|
rn: u4,
|
|
s: u1,
|
|
opcode: u4,
|
|
i: u1,
|
|
fixed: u2 = 0b00,
|
|
cond: u4,
|
|
},
|
|
Multiply: packed struct {
|
|
rn: u4,
|
|
fixed_1: u4 = 0b1001,
|
|
rm: u4,
|
|
ra: u4,
|
|
rd: u4,
|
|
set_cond: u1,
|
|
accumulate: u1,
|
|
fixed_2: u6 = 0b000000,
|
|
cond: u4,
|
|
},
|
|
MultiplyLong: packed struct {
|
|
rn: u4,
|
|
fixed_1: u4 = 0b1001,
|
|
rm: u4,
|
|
rdlo: u4,
|
|
rdhi: u4,
|
|
set_cond: u1,
|
|
accumulate: u1,
|
|
unsigned: u1,
|
|
fixed_2: u5 = 0b00001,
|
|
cond: u4,
|
|
},
|
|
SingleDataTransfer: packed struct {
|
|
offset: u12,
|
|
rd: u4,
|
|
rn: u4,
|
|
load_store: u1,
|
|
write_back: u1,
|
|
byte_word: u1,
|
|
up_down: u1,
|
|
pre_post: u1,
|
|
imm: u1,
|
|
fixed: u2 = 0b01,
|
|
cond: u4,
|
|
},
|
|
ExtraLoadStore: packed struct {
|
|
imm4l: u4,
|
|
fixed_1: u1 = 0b1,
|
|
op2: u2,
|
|
fixed_2: u1 = 0b1,
|
|
imm4h: u4,
|
|
rt: u4,
|
|
rn: u4,
|
|
o1: u1,
|
|
write_back: u1,
|
|
imm: u1,
|
|
up_down: u1,
|
|
pre_index: u1,
|
|
fixed_3: u3 = 0b000,
|
|
cond: u4,
|
|
},
|
|
BlockDataTransfer: packed struct {
|
|
register_list: u16,
|
|
rn: u4,
|
|
load_store: u1,
|
|
write_back: u1,
|
|
psr_or_user: u1,
|
|
up_down: u1,
|
|
pre_post: u1,
|
|
fixed: u3 = 0b100,
|
|
cond: u4,
|
|
},
|
|
Branch: packed struct {
|
|
offset: u24,
|
|
link: u1,
|
|
fixed: u3 = 0b101,
|
|
cond: u4,
|
|
},
|
|
BranchExchange: packed struct {
|
|
rn: u4,
|
|
fixed_1: u1 = 0b1,
|
|
link: u1,
|
|
fixed_2: u22 = 0b0001_0010_1111_1111_1111_00,
|
|
cond: u4,
|
|
},
|
|
SupervisorCall: packed struct {
|
|
comment: u24,
|
|
fixed: u4 = 0b1111,
|
|
cond: u4,
|
|
},
|
|
Breakpoint: packed struct {
|
|
imm4: u4,
|
|
fixed_1: u4 = 0b0111,
|
|
imm12: u12,
|
|
fixed_2_and_cond: u12 = 0b1110_0001_0010,
|
|
},
|
|
|
|
/// Represents the possible operations which can be performed by a
|
|
/// DataProcessing instruction
|
|
const Opcode = enum(u4) {
|
|
// Rd := Op1 AND Op2
|
|
@"and",
|
|
// Rd := Op1 EOR Op2
|
|
eor,
|
|
// Rd := Op1 - Op2
|
|
sub,
|
|
// Rd := Op2 - Op1
|
|
rsb,
|
|
// Rd := Op1 + Op2
|
|
add,
|
|
// Rd := Op1 + Op2 + C
|
|
adc,
|
|
// Rd := Op1 - Op2 + C - 1
|
|
sbc,
|
|
// Rd := Op2 - Op1 + C - 1
|
|
rsc,
|
|
// set condition codes on Op1 AND Op2
|
|
tst,
|
|
// set condition codes on Op1 EOR Op2
|
|
teq,
|
|
// set condition codes on Op1 - Op2
|
|
cmp,
|
|
// set condition codes on Op1 + Op2
|
|
cmn,
|
|
// Rd := Op1 OR Op2
|
|
orr,
|
|
// Rd := Op2
|
|
mov,
|
|
// Rd := Op1 AND NOT Op2
|
|
bic,
|
|
// Rd := NOT Op2
|
|
mvn,
|
|
};
|
|
|
|
/// Represents the second operand to a data processing instruction
|
|
/// which can either be content from a register or an immediate
|
|
/// value
|
|
pub const Operand = union(enum) {
|
|
Register: packed struct {
|
|
rm: u4,
|
|
shift: u8,
|
|
},
|
|
Immediate: packed struct {
|
|
imm: u8,
|
|
rotate: u4,
|
|
},
|
|
|
|
/// Represents multiple ways a register can be shifted. A
|
|
/// register can be shifted by a specific immediate value or
|
|
/// by the contents of another register
|
|
pub const Shift = union(enum) {
|
|
Immediate: packed struct {
|
|
fixed: u1 = 0b0,
|
|
typ: u2,
|
|
amount: u5,
|
|
},
|
|
Register: packed struct {
|
|
fixed_1: u1 = 0b1,
|
|
typ: u2,
|
|
fixed_2: u1 = 0b0,
|
|
rs: u4,
|
|
},
|
|
|
|
pub const Type = enum(u2) {
|
|
logical_left,
|
|
logical_right,
|
|
arithmetic_right,
|
|
rotate_right,
|
|
};
|
|
|
|
pub const none = Shift{
|
|
.Immediate = .{
|
|
.amount = 0,
|
|
.typ = 0,
|
|
},
|
|
};
|
|
|
|
pub fn toU8(self: Shift) u8 {
|
|
return switch (self) {
|
|
.Register => |v| @bitCast(u8, v),
|
|
.Immediate => |v| @bitCast(u8, v),
|
|
};
|
|
}
|
|
|
|
pub fn reg(rs: Register, typ: Type) Shift {
|
|
return Shift{
|
|
.Register = .{
|
|
.rs = rs.id(),
|
|
.typ = @enumToInt(typ),
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn imm(amount: u5, typ: Type) Shift {
|
|
return Shift{
|
|
.Immediate = .{
|
|
.amount = amount,
|
|
.typ = @enumToInt(typ),
|
|
},
|
|
};
|
|
}
|
|
};
|
|
|
|
pub fn toU12(self: Operand) u12 {
|
|
return switch (self) {
|
|
.Register => |v| @bitCast(u12, v),
|
|
.Immediate => |v| @bitCast(u12, v),
|
|
};
|
|
}
|
|
|
|
pub fn reg(rm: Register, shift: Shift) Operand {
|
|
return Operand{
|
|
.Register = .{
|
|
.rm = rm.id(),
|
|
.shift = shift.toU8(),
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn imm(immediate: u8, rotate: u4) Operand {
|
|
return Operand{
|
|
.Immediate = .{
|
|
.imm = immediate,
|
|
.rotate = rotate,
|
|
},
|
|
};
|
|
}
|
|
|
|
/// Tries to convert an unsigned 32 bit integer into an
|
|
/// immediate operand using rotation. Returns null when there
|
|
/// is no conversion
|
|
pub fn fromU32(x: u32) ?Operand {
|
|
const masks = comptime blk: {
|
|
const base_mask: u32 = std.math.maxInt(u8);
|
|
var result = [_]u32{0} ** 16;
|
|
for (result) |*mask, i| mask.* = std.math.rotr(u32, base_mask, 2 * i);
|
|
break :blk result;
|
|
};
|
|
|
|
return for (masks) |mask, i| {
|
|
if (x & mask == x) {
|
|
break Operand{
|
|
.Immediate = .{
|
|
.imm = @intCast(u8, std.math.rotl(u32, x, 2 * i)),
|
|
.rotate = @intCast(u4, i),
|
|
},
|
|
};
|
|
}
|
|
} else null;
|
|
}
|
|
};
|
|
|
|
/// Represents the offset operand of a load or store
|
|
/// instruction. Data can be loaded from memory with either an
|
|
/// immediate offset or an offset that is stored in some register.
|
|
pub const Offset = union(enum) {
|
|
Immediate: u12,
|
|
Register: packed struct {
|
|
rm: u4,
|
|
shift: u8,
|
|
},
|
|
|
|
pub const none = Offset{
|
|
.Immediate = 0,
|
|
};
|
|
|
|
pub fn toU12(self: Offset) u12 {
|
|
return switch (self) {
|
|
.Register => |v| @bitCast(u12, v),
|
|
.Immediate => |v| v,
|
|
};
|
|
}
|
|
|
|
pub fn reg(rm: Register, shift: u8) Offset {
|
|
return Offset{
|
|
.Register = .{
|
|
.rm = rm.id(),
|
|
.shift = shift,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn imm(immediate: u12) Offset {
|
|
return Offset{
|
|
.Immediate = immediate,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// Represents the offset operand of an extra load or store
|
|
/// instruction.
|
|
pub const ExtraLoadStoreOffset = union(enum) {
|
|
immediate: u8,
|
|
register: u4,
|
|
|
|
pub const none = ExtraLoadStoreOffset{
|
|
.immediate = 0,
|
|
};
|
|
|
|
pub fn reg(register: Register) ExtraLoadStoreOffset {
|
|
return ExtraLoadStoreOffset{
|
|
.register = register.id(),
|
|
};
|
|
}
|
|
|
|
pub fn imm(immediate: u8) ExtraLoadStoreOffset {
|
|
return ExtraLoadStoreOffset{
|
|
.immediate = immediate,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// Represents the register list operand to a block data transfer
|
|
/// instruction
|
|
pub const RegisterList = packed struct {
|
|
r0: bool = false,
|
|
r1: bool = false,
|
|
r2: bool = false,
|
|
r3: bool = false,
|
|
r4: bool = false,
|
|
r5: bool = false,
|
|
r6: bool = false,
|
|
r7: bool = false,
|
|
r8: bool = false,
|
|
r9: bool = false,
|
|
r10: bool = false,
|
|
r11: bool = false,
|
|
r12: bool = false,
|
|
r13: bool = false,
|
|
r14: bool = false,
|
|
r15: bool = false,
|
|
};
|
|
|
|
pub fn toU32(self: Instruction) u32 {
|
|
return switch (self) {
|
|
.DataProcessing => |v| @bitCast(u32, v),
|
|
.Multiply => |v| @bitCast(u32, v),
|
|
.MultiplyLong => |v| @bitCast(u32, v),
|
|
.SingleDataTransfer => |v| @bitCast(u32, v),
|
|
.ExtraLoadStore => |v| @bitCast(u32, v),
|
|
.BlockDataTransfer => |v| @bitCast(u32, v),
|
|
.Branch => |v| @bitCast(u32, v),
|
|
.BranchExchange => |v| @bitCast(u32, v),
|
|
.SupervisorCall => |v| @bitCast(u32, v),
|
|
.Breakpoint => |v| @intCast(u32, v.imm4) | (@intCast(u32, v.fixed_1) << 4) | (@intCast(u32, v.imm12) << 8) | (@intCast(u32, v.fixed_2_and_cond) << 20),
|
|
};
|
|
}
|
|
|
|
// Helper functions for the "real" functions below
|
|
|
|
fn dataProcessing(
|
|
cond: Condition,
|
|
opcode: Opcode,
|
|
s: u1,
|
|
rd: Register,
|
|
rn: Register,
|
|
op2: Operand,
|
|
) Instruction {
|
|
return Instruction{
|
|
.DataProcessing = .{
|
|
.cond = @enumToInt(cond),
|
|
.i = @boolToInt(op2 == .Immediate),
|
|
.opcode = @enumToInt(opcode),
|
|
.s = s,
|
|
.rn = rn.id(),
|
|
.rd = rd.id(),
|
|
.op2 = op2.toU12(),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn specialMov(
|
|
cond: Condition,
|
|
rd: Register,
|
|
imm: u16,
|
|
top: bool,
|
|
) Instruction {
|
|
return Instruction{
|
|
.DataProcessing = .{
|
|
.cond = @enumToInt(cond),
|
|
.i = 1,
|
|
.opcode = if (top) 0b1010 else 0b1000,
|
|
.s = 0,
|
|
.rn = @truncate(u4, imm >> 12),
|
|
.rd = rd.id(),
|
|
.op2 = @truncate(u12, imm),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn multiply(
|
|
cond: Condition,
|
|
set_cond: u1,
|
|
rd: Register,
|
|
rn: Register,
|
|
rm: Register,
|
|
ra: ?Register,
|
|
) Instruction {
|
|
return Instruction{
|
|
.Multiply = .{
|
|
.cond = @enumToInt(cond),
|
|
.accumulate = @boolToInt(ra != null),
|
|
.set_cond = set_cond,
|
|
.rd = rd.id(),
|
|
.rn = rn.id(),
|
|
.ra = if (ra) |reg| reg.id() else 0b0000,
|
|
.rm = rm.id(),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn multiplyLong(
|
|
cond: Condition,
|
|
signed: u1,
|
|
accumulate: u1,
|
|
set_cond: u1,
|
|
rdhi: Register,
|
|
rdlo: Register,
|
|
rm: Register,
|
|
rn: Register,
|
|
) Instruction {
|
|
return Instruction{
|
|
.MultiplyLong = .{
|
|
.cond = @enumToInt(cond),
|
|
.unsigned = signed,
|
|
.accumulate = accumulate,
|
|
.set_cond = set_cond,
|
|
.rdlo = rdlo.id(),
|
|
.rdhi = rdhi.id(),
|
|
.rn = rn.id(),
|
|
.rm = rm.id(),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn singleDataTransfer(
|
|
cond: Condition,
|
|
rd: Register,
|
|
rn: Register,
|
|
offset: Offset,
|
|
pre_index: bool,
|
|
positive: bool,
|
|
byte_word: u1,
|
|
write_back: bool,
|
|
load_store: u1,
|
|
) Instruction {
|
|
return Instruction{
|
|
.SingleDataTransfer = .{
|
|
.cond = @enumToInt(cond),
|
|
.rn = rn.id(),
|
|
.rd = rd.id(),
|
|
.offset = offset.toU12(),
|
|
.load_store = load_store,
|
|
.write_back = @boolToInt(write_back),
|
|
.byte_word = byte_word,
|
|
.up_down = @boolToInt(positive),
|
|
.pre_post = @boolToInt(pre_index),
|
|
.imm = @boolToInt(offset != .Immediate),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn extraLoadStore(
|
|
cond: Condition,
|
|
pre_index: bool,
|
|
positive: bool,
|
|
write_back: bool,
|
|
o1: u1,
|
|
op2: u2,
|
|
rn: Register,
|
|
rt: Register,
|
|
offset: ExtraLoadStoreOffset,
|
|
) Instruction {
|
|
const imm4l: u4 = switch (offset) {
|
|
.immediate => |imm| @truncate(u4, imm),
|
|
.register => |reg| reg,
|
|
};
|
|
const imm4h: u4 = switch (offset) {
|
|
.immediate => |imm| @truncate(u4, imm >> 4),
|
|
.register => |reg| 0b0000,
|
|
};
|
|
|
|
return Instruction{
|
|
.ExtraLoadStore = .{
|
|
.imm4l = imm4l,
|
|
.op2 = op2,
|
|
.imm4h = imm4h,
|
|
.rt = rt.id(),
|
|
.rn = rn.id(),
|
|
.o1 = o1,
|
|
.write_back = @boolToInt(write_back),
|
|
.imm = @boolToInt(offset == .immediate),
|
|
.up_down = @boolToInt(positive),
|
|
.pre_index = @boolToInt(pre_index),
|
|
.cond = @enumToInt(cond),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn blockDataTransfer(
|
|
cond: Condition,
|
|
rn: Register,
|
|
reg_list: RegisterList,
|
|
pre_post: u1,
|
|
up_down: u1,
|
|
psr_or_user: u1,
|
|
write_back: bool,
|
|
load_store: u1,
|
|
) Instruction {
|
|
return Instruction{
|
|
.BlockDataTransfer = .{
|
|
.register_list = @bitCast(u16, reg_list),
|
|
.rn = rn.id(),
|
|
.load_store = load_store,
|
|
.write_back = @boolToInt(write_back),
|
|
.psr_or_user = psr_or_user,
|
|
.up_down = up_down,
|
|
.pre_post = pre_post,
|
|
.cond = @enumToInt(cond),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn branch(cond: Condition, offset: i26, link: u1) Instruction {
|
|
return Instruction{
|
|
.Branch = .{
|
|
.cond = @enumToInt(cond),
|
|
.link = link,
|
|
.offset = @bitCast(u24, @intCast(i24, offset >> 2)),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn branchExchange(cond: Condition, rn: Register, link: u1) Instruction {
|
|
return Instruction{
|
|
.BranchExchange = .{
|
|
.cond = @enumToInt(cond),
|
|
.link = link,
|
|
.rn = rn.id(),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn supervisorCall(cond: Condition, comment: u24) Instruction {
|
|
return Instruction{
|
|
.SupervisorCall = .{
|
|
.cond = @enumToInt(cond),
|
|
.comment = comment,
|
|
},
|
|
};
|
|
}
|
|
|
|
fn breakpoint(imm: u16) Instruction {
|
|
return Instruction{
|
|
.Breakpoint = .{
|
|
.imm12 = @truncate(u12, imm >> 4),
|
|
.imm4 = @truncate(u4, imm),
|
|
},
|
|
};
|
|
}
|
|
|
|
// Public functions replicating assembler syntax as closely as
|
|
// possible
|
|
|
|
// Data processing
|
|
|
|
pub fn @"and"(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .@"and", 0, rd, rn, op2);
|
|
}
|
|
|
|
pub fn ands(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .@"and", 1, rd, rn, op2);
|
|
}
|
|
|
|
pub fn eor(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .eor, 0, rd, rn, op2);
|
|
}
|
|
|
|
pub fn eors(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .eor, 1, rd, rn, op2);
|
|
}
|
|
|
|
pub fn sub(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .sub, 0, rd, rn, op2);
|
|
}
|
|
|
|
pub fn subs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .sub, 1, rd, rn, op2);
|
|
}
|
|
|
|
pub fn rsb(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .rsb, 0, rd, rn, op2);
|
|
}
|
|
|
|
pub fn rsbs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .rsb, 1, rd, rn, op2);
|
|
}
|
|
|
|
pub fn add(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .add, 0, rd, rn, op2);
|
|
}
|
|
|
|
pub fn adds(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .add, 1, rd, rn, op2);
|
|
}
|
|
|
|
pub fn adc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .adc, 0, rd, rn, op2);
|
|
}
|
|
|
|
pub fn adcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .adc, 1, rd, rn, op2);
|
|
}
|
|
|
|
pub fn sbc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .sbc, 0, rd, rn, op2);
|
|
}
|
|
|
|
pub fn sbcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .sbc, 1, rd, rn, op2);
|
|
}
|
|
|
|
pub fn rsc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .rsc, 0, rd, rn, op2);
|
|
}
|
|
|
|
pub fn rscs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .rsc, 1, rd, rn, op2);
|
|
}
|
|
|
|
pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .tst, 1, .r0, rn, op2);
|
|
}
|
|
|
|
pub fn teq(cond: Condition, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .teq, 1, .r0, rn, op2);
|
|
}
|
|
|
|
pub fn cmp(cond: Condition, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .cmp, 1, .r0, rn, op2);
|
|
}
|
|
|
|
pub fn cmn(cond: Condition, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .cmn, 1, .r0, rn, op2);
|
|
}
|
|
|
|
pub fn orr(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .orr, 0, rd, rn, op2);
|
|
}
|
|
|
|
pub fn orrs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .orr, 1, rd, rn, op2);
|
|
}
|
|
|
|
pub fn mov(cond: Condition, rd: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .mov, 0, rd, .r0, op2);
|
|
}
|
|
|
|
pub fn movs(cond: Condition, rd: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .mov, 1, rd, .r0, op2);
|
|
}
|
|
|
|
pub fn bic(cond: Condition, rd: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .bic, 0, rd, rn, op2);
|
|
}
|
|
|
|
pub fn bics(cond: Condition, rd: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .bic, 1, rd, rn, op2);
|
|
}
|
|
|
|
pub fn mvn(cond: Condition, rd: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .mvn, 0, rd, .r0, op2);
|
|
}
|
|
|
|
pub fn mvns(cond: Condition, rd: Register, op2: Operand) Instruction {
|
|
return dataProcessing(cond, .mvn, 1, rd, .r0, op2);
|
|
}
|
|
|
|
// movw and movt
|
|
|
|
pub fn movw(cond: Condition, rd: Register, imm: u16) Instruction {
|
|
return specialMov(cond, rd, imm, false);
|
|
}
|
|
|
|
pub fn movt(cond: Condition, rd: Register, imm: u16) Instruction {
|
|
return specialMov(cond, rd, imm, true);
|
|
}
|
|
|
|
// PSR transfer
|
|
|
|
pub fn mrs(cond: Condition, rd: Register, psr: Psr) Instruction {
|
|
return Instruction{
|
|
.DataProcessing = .{
|
|
.cond = @enumToInt(cond),
|
|
.i = 0,
|
|
.opcode = if (psr == .spsr) 0b1010 else 0b1000,
|
|
.s = 0,
|
|
.rn = 0b1111,
|
|
.rd = rd.id(),
|
|
.op2 = 0b0000_0000_0000,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn msr(cond: Condition, psr: Psr, op: Operand) Instruction {
|
|
return Instruction{
|
|
.DataProcessing = .{
|
|
.cond = @enumToInt(cond),
|
|
.i = 0,
|
|
.opcode = if (psr == .spsr) 0b1011 else 0b1001,
|
|
.s = 0,
|
|
.rn = 0b1111,
|
|
.rd = 0b1111,
|
|
.op2 = op.toU12(),
|
|
},
|
|
};
|
|
}
|
|
|
|
// Multiply
|
|
|
|
pub fn mul(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
|
|
return multiply(cond, 0, rd, rn, rm, null);
|
|
}
|
|
|
|
pub fn muls(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
|
|
return multiply(cond, 1, rd, rn, rm, null);
|
|
}
|
|
|
|
pub fn mla(cond: Condition, rd: Register, rn: Register, rm: Register, ra: Register) Instruction {
|
|
return multiply(cond, 0, rd, rn, rm, ra);
|
|
}
|
|
|
|
pub fn mlas(cond: Condition, rd: Register, rn: Register, rm: Register, ra: Register) Instruction {
|
|
return multiply(cond, 1, rd, rn, rm, ra);
|
|
}
|
|
|
|
// Multiply long
|
|
|
|
pub fn umull(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
|
|
return multiplyLong(cond, 0, 0, 0, rdhi, rdlo, rm, rn);
|
|
}
|
|
|
|
pub fn umulls(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
|
|
return multiplyLong(cond, 0, 0, 1, rdhi, rdlo, rm, rn);
|
|
}
|
|
|
|
pub fn umlal(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
|
|
return multiplyLong(cond, 0, 1, 0, rdhi, rdlo, rm, rn);
|
|
}
|
|
|
|
pub fn umlals(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
|
|
return multiplyLong(cond, 0, 1, 1, rdhi, rdlo, rm, rn);
|
|
}
|
|
|
|
pub fn smull(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
|
|
return multiplyLong(cond, 1, 0, 0, rdhi, rdlo, rm, rn);
|
|
}
|
|
|
|
pub fn smulls(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
|
|
return multiplyLong(cond, 1, 0, 1, rdhi, rdlo, rm, rn);
|
|
}
|
|
|
|
pub fn smlal(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
|
|
return multiplyLong(cond, 1, 1, 0, rdhi, rdlo, rm, rn);
|
|
}
|
|
|
|
pub fn smlals(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
|
|
return multiplyLong(cond, 1, 1, 1, rdhi, rdlo, rm, rn);
|
|
}
|
|
|
|
// Single data transfer
|
|
|
|
pub const OffsetArgs = struct {
|
|
pre_index: bool = true,
|
|
positive: bool = true,
|
|
offset: Offset,
|
|
write_back: bool = false,
|
|
};
|
|
|
|
pub fn ldr(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction {
|
|
return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 0, args.write_back, 1);
|
|
}
|
|
|
|
pub fn ldrb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction {
|
|
return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 1);
|
|
}
|
|
|
|
pub fn str(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction {
|
|
return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 0, args.write_back, 0);
|
|
}
|
|
|
|
pub fn strb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction {
|
|
return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 0);
|
|
}
|
|
|
|
// Extra load/store
|
|
|
|
pub const ExtraLoadStoreOffsetArgs = struct {
|
|
pre_index: bool = true,
|
|
positive: bool = true,
|
|
offset: ExtraLoadStoreOffset,
|
|
write_back: bool = false,
|
|
};
|
|
|
|
pub fn strh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction {
|
|
return extraLoadStore(cond, args.pre_index, args.positive, args.write_back, 0, 0b01, rn, rt, args.offset);
|
|
}
|
|
|
|
pub fn ldrh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction {
|
|
return extraLoadStore(cond, args.pre_index, args.positive, args.write_back, 1, 0b01, rn, rt, args.offset);
|
|
}
|
|
|
|
// Block data transfer
|
|
|
|
pub fn ldmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
|
|
return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 1);
|
|
}
|
|
|
|
pub fn ldmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
|
|
return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 1);
|
|
}
|
|
|
|
pub fn ldmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
|
|
return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 1);
|
|
}
|
|
|
|
pub fn ldmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
|
|
return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 1);
|
|
}
|
|
|
|
pub const ldmfa = ldmda;
|
|
pub const ldmea = ldmdb;
|
|
pub const ldmed = ldmib;
|
|
pub const ldmfd = ldmia;
|
|
pub const ldm = ldmia;
|
|
|
|
pub fn stmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
|
|
return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 0);
|
|
}
|
|
|
|
pub fn stmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
|
|
return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 0);
|
|
}
|
|
|
|
pub fn stmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
|
|
return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 0);
|
|
}
|
|
|
|
pub fn stmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
|
|
return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 0);
|
|
}
|
|
|
|
pub const stmed = stmda;
|
|
pub const stmfd = stmdb;
|
|
pub const stmfa = stmib;
|
|
pub const stmea = stmia;
|
|
pub const stm = stmia;
|
|
|
|
// Branch
|
|
|
|
pub fn b(cond: Condition, offset: i26) Instruction {
|
|
return branch(cond, offset, 0);
|
|
}
|
|
|
|
pub fn bl(cond: Condition, offset: i26) Instruction {
|
|
return branch(cond, offset, 1);
|
|
}
|
|
|
|
// Branch and exchange
|
|
|
|
pub fn bx(cond: Condition, rn: Register) Instruction {
|
|
return branchExchange(cond, rn, 0);
|
|
}
|
|
|
|
pub fn blx(cond: Condition, rn: Register) Instruction {
|
|
return branchExchange(cond, rn, 1);
|
|
}
|
|
|
|
// Supervisor Call
|
|
|
|
pub const swi = svc;
|
|
|
|
pub fn svc(cond: Condition, comment: u24) Instruction {
|
|
return supervisorCall(cond, comment);
|
|
}
|
|
|
|
// Breakpoint
|
|
|
|
pub fn bkpt(imm: u16) Instruction {
|
|
return breakpoint(imm);
|
|
}
|
|
|
|
// Aliases
|
|
|
|
pub fn nop() Instruction {
|
|
return mov(.al, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none));
|
|
}
|
|
|
|
pub fn pop(cond: Condition, args: anytype) Instruction {
|
|
if (@typeInfo(@TypeOf(args)) != .Struct) {
|
|
@compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args)));
|
|
}
|
|
|
|
if (args.len < 1) {
|
|
@compileError("Expected at least one register");
|
|
} else if (args.len == 1) {
|
|
const reg = args[0];
|
|
return ldr(cond, reg, .sp, .{
|
|
.pre_index = false,
|
|
.positive = true,
|
|
.offset = Offset.imm(4),
|
|
.write_back = false,
|
|
});
|
|
} else {
|
|
var register_list: u16 = 0;
|
|
inline for (args) |arg| {
|
|
const reg = @as(Register, arg);
|
|
register_list |= @as(u16, 1) << reg.id();
|
|
}
|
|
return ldm(cond, .sp, true, @bitCast(RegisterList, register_list));
|
|
}
|
|
}
|
|
|
|
pub fn push(cond: Condition, args: anytype) Instruction {
|
|
if (@typeInfo(@TypeOf(args)) != .Struct) {
|
|
@compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args)));
|
|
}
|
|
|
|
if (args.len < 1) {
|
|
@compileError("Expected at least one register");
|
|
} else if (args.len == 1) {
|
|
const reg = args[0];
|
|
return str(cond, reg, .sp, .{
|
|
.pre_index = true,
|
|
.positive = false,
|
|
.offset = Offset.imm(4),
|
|
.write_back = true,
|
|
});
|
|
} else {
|
|
var register_list: u16 = 0;
|
|
inline for (args) |arg| {
|
|
const reg = @as(Register, arg);
|
|
register_list |= @as(u16, 1) << reg.id();
|
|
}
|
|
return stmdb(cond, .sp, true, @bitCast(RegisterList, register_list));
|
|
}
|
|
}
|
|
};
|
|
|
|
test "serialize instructions" {
|
|
const Testcase = struct {
|
|
inst: Instruction,
|
|
expected: u32,
|
|
};
|
|
|
|
const testcases = [_]Testcase{
|
|
.{ // add r0, r0, r0
|
|
.inst = Instruction.add(.al, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)),
|
|
.expected = 0b1110_00_0_0100_0_0000_0000_00000000_0000,
|
|
},
|
|
.{ // mov r4, r2
|
|
.inst = Instruction.mov(.al, .r4, Instruction.Operand.reg(.r2, Instruction.Operand.Shift.none)),
|
|
.expected = 0b1110_00_0_1101_0_0000_0100_00000000_0010,
|
|
},
|
|
.{ // mov r0, #42
|
|
.inst = Instruction.mov(.al, .r0, Instruction.Operand.imm(42, 0)),
|
|
.expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010,
|
|
},
|
|
.{ // mrs r5, cpsr
|
|
.inst = Instruction.mrs(.al, .r5, .cpsr),
|
|
.expected = 0b1110_00010_0_001111_0101_000000000000,
|
|
},
|
|
.{ // mul r0, r1, r2
|
|
.inst = Instruction.mul(.al, .r0, .r1, .r2),
|
|
.expected = 0b1110_000000_0_0_0000_0000_0010_1001_0001,
|
|
},
|
|
.{ // umlal r0, r1, r5, r6
|
|
.inst = Instruction.umlal(.al, .r0, .r1, .r5, .r6),
|
|
.expected = 0b1110_00001_0_1_0_0001_0000_0110_1001_0101,
|
|
},
|
|
.{ // ldr r0, [r2, #42]
|
|
.inst = Instruction.ldr(.al, .r0, .r2, .{
|
|
.offset = Instruction.Offset.imm(42),
|
|
}),
|
|
.expected = 0b1110_01_0_1_1_0_0_1_0010_0000_000000101010,
|
|
},
|
|
.{ // str r0, [r3]
|
|
.inst = Instruction.str(.al, .r0, .r3, .{
|
|
.offset = Instruction.Offset.none,
|
|
}),
|
|
.expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000,
|
|
},
|
|
.{ // strh r1, [r5]
|
|
.inst = Instruction.strh(.al, .r1, .r5, .{
|
|
.offset = Instruction.ExtraLoadStoreOffset.none,
|
|
}),
|
|
.expected = 0b1110_000_1_1_1_0_0_0101_0001_0000_1011_0000,
|
|
},
|
|
.{ // b #12
|
|
.inst = Instruction.b(.al, 12),
|
|
.expected = 0b1110_101_0_0000_0000_0000_0000_0000_0011,
|
|
},
|
|
.{ // bl #-4
|
|
.inst = Instruction.bl(.al, -4),
|
|
.expected = 0b1110_101_1_1111_1111_1111_1111_1111_1111,
|
|
},
|
|
.{ // bx lr
|
|
.inst = Instruction.bx(.al, .lr),
|
|
.expected = 0b1110_0001_0010_1111_1111_1111_0001_1110,
|
|
},
|
|
.{ // svc #0
|
|
.inst = Instruction.svc(.al, 0),
|
|
.expected = 0b1110_1111_0000_0000_0000_0000_0000_0000,
|
|
},
|
|
.{ // bkpt #42
|
|
.inst = Instruction.bkpt(42),
|
|
.expected = 0b1110_0001_0010_000000000010_0111_1010,
|
|
},
|
|
.{ // stmdb r9, {r0}
|
|
.inst = Instruction.stmdb(.al, .r9, false, .{ .r0 = true }),
|
|
.expected = 0b1110_100_1_0_0_0_0_1001_0000000000000001,
|
|
},
|
|
.{ // ldmea r4!, {r2, r5}
|
|
.inst = Instruction.ldmea(.al, .r4, true, .{ .r2 = true, .r5 = true }),
|
|
.expected = 0b1110_100_1_0_0_1_1_0100_0000000000100100,
|
|
},
|
|
};
|
|
|
|
for (testcases) |case| {
|
|
const actual = case.inst.toU32();
|
|
testing.expectEqual(case.expected, actual);
|
|
}
|
|
}
|
|
|
|
test "aliases" {
|
|
const Testcase = struct {
|
|
expected: Instruction,
|
|
actual: Instruction,
|
|
};
|
|
|
|
const testcases = [_]Testcase{
|
|
.{ // pop { r6 }
|
|
.actual = Instruction.pop(.al, .{.r6}),
|
|
.expected = Instruction.ldr(.al, .r6, .sp, .{
|
|
.pre_index = false,
|
|
.positive = true,
|
|
.offset = Instruction.Offset.imm(4),
|
|
.write_back = false,
|
|
}),
|
|
},
|
|
.{ // pop { r1, r5 }
|
|
.actual = Instruction.pop(.al, .{ .r1, .r5 }),
|
|
.expected = Instruction.ldm(.al, .sp, true, .{ .r1 = true, .r5 = true }),
|
|
},
|
|
.{ // push { r3 }
|
|
.actual = Instruction.push(.al, .{.r3}),
|
|
.expected = Instruction.str(.al, .r3, .sp, .{
|
|
.pre_index = true,
|
|
.positive = false,
|
|
.offset = Instruction.Offset.imm(4),
|
|
.write_back = true,
|
|
}),
|
|
},
|
|
.{ // push { r0, r2 }
|
|
.actual = Instruction.push(.al, .{ .r0, .r2 }),
|
|
.expected = Instruction.stmdb(.al, .sp, true, .{ .r0 = true, .r2 = true }),
|
|
},
|
|
};
|
|
|
|
for (testcases) |case| {
|
|
testing.expectEqual(case.expected.toU32(), case.actual.toU32());
|
|
}
|
|
}
|