fix(armv5te): implement obscure behaviour on invalid LDM writeback
All I have to do is implement ARMv5TE specific instructions, and then we're finished with ARMWRESTLER!
This commit is contained in:
parent
591352a65b
commit
e6863e7a9b
11
src/arm.zig
11
src/arm.zig
|
@ -27,11 +27,12 @@ const condition_lut = [_]u16{
|
||||||
0x0000, // NV - never
|
0x0000, // NV - never
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn Arm32(comptime arch: Architecture) type {
|
pub fn Arm32(comptime isa: Architecture) type {
|
||||||
const is_v5te = arch == .v5te;
|
const is_v5te = isa == .v5te;
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
pub const arch = isa;
|
||||||
|
|
||||||
r: [16]u32 = [_]u32{0x00} ** 16,
|
r: [16]u32 = [_]u32{0x00} ** 16,
|
||||||
pipe: Pipeline = Pipeline.init(),
|
pipe: Pipeline = Pipeline.init(),
|
||||||
|
@ -46,12 +47,12 @@ pub fn Arm32(comptime arch: Architecture) type {
|
||||||
itcm: if (is_v5te) Itcm else void,
|
itcm: if (is_v5te) Itcm else void,
|
||||||
dtcm: if (is_v5te) Dtcm else void,
|
dtcm: if (is_v5te) Dtcm else void,
|
||||||
|
|
||||||
const arm = switch (arch) {
|
const arm = switch (isa) {
|
||||||
.v4t => @import("arm/v4t.zig").arm,
|
.v4t => @import("arm/v4t.zig").arm,
|
||||||
.v5te => @import("arm/v5te.zig").arm,
|
.v5te => @import("arm/v5te.zig").arm,
|
||||||
};
|
};
|
||||||
|
|
||||||
const thumb = switch (arch) {
|
const thumb = switch (isa) {
|
||||||
.v4t => @import("arm/v4t.zig").thumb,
|
.v4t => @import("arm/v4t.zig").thumb,
|
||||||
.v5te => @import("arm/v5te.zig").thumb,
|
.v5te => @import("arm/v5te.zig").thumb,
|
||||||
};
|
};
|
||||||
|
@ -388,7 +389,7 @@ pub fn Arm32(comptime arch: Architecture) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interface(self: *Self) Interpreter {
|
pub fn interface(self: *Self) Interpreter {
|
||||||
return switch (arch) {
|
return switch (isa) {
|
||||||
.v4t => .{ .v4t = self },
|
.v4t => .{ .v4t = self },
|
||||||
.v5te => .{ .v5te = self },
|
.v5te => .{ .v5te = self },
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,78 +4,101 @@ pub fn blockDataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: b
|
||||||
return struct {
|
return struct {
|
||||||
fn inner(cpu: *Arm32, opcode: u32) void {
|
fn inner(cpu: *Arm32, opcode: u32) void {
|
||||||
const rn: u4 = @truncate(opcode >> 16 & 0xF);
|
const rn: u4 = @truncate(opcode >> 16 & 0xF);
|
||||||
const rlist = opcode & 0xFFFF;
|
const rlist: u16 = @intCast(opcode & 0xFFFF);
|
||||||
const r15 = rlist >> 15 & 1 == 1;
|
|
||||||
|
|
||||||
var count: u32 = 0;
|
const reg_count: u32 = @popCount(rlist);
|
||||||
var i: u5 = 0;
|
const first_in_list: u4 = @truncate(@ctz(rlist)); // note that @ctz(0x0000) and @ctz(0x0001) collide
|
||||||
var first: u4 = 0;
|
|
||||||
var write_to_base = true;
|
|
||||||
|
|
||||||
while (i < 16) : (i += 1) {
|
// U determines whether the LDM/STM transfer is made upwards (U == 1)
|
||||||
const r: u4 = @truncate(15 - i);
|
// or downwards (U == 0).
|
||||||
if (rlist >> r & 1 == 1) {
|
|
||||||
first = r;
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var start = cpu.r[rn];
|
const start_addr: u32 = if (U) blk: {
|
||||||
if (U) {
|
break :blk cpu.r[rn] + if (P) 4 else 0;
|
||||||
start += if (P) 4 else 0;
|
} else blk: {
|
||||||
} else {
|
break :blk cpu.r[rn] - (4 * reg_count) + if (!P) 4 else 0;
|
||||||
start = start - (4 * count) + if (!P) 4 else 0;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
var end = cpu.r[rn];
|
// FIXME : why 4 * reg_count?
|
||||||
if (U) {
|
|
||||||
end = end + (4 * count) - if (!P) 4 else 0;
|
|
||||||
} else {
|
|
||||||
end -= if (P) 4 else 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var new_base = cpu.r[rn];
|
const new_base_addr: u32 = if (U) blk: {
|
||||||
if (U) {
|
break :blk cpu.r[rn] + 4 * reg_count;
|
||||||
new_base += 4 * count;
|
} else blk: {
|
||||||
} else {
|
break :blk cpu.r[rn] - 4 * reg_count;
|
||||||
new_base -= 4 * count;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
var address = start;
|
var address = start_addr;
|
||||||
|
|
||||||
|
// On Empty List:
|
||||||
|
//
|
||||||
|
// ARMv4 Only: R15 is Loaded/Stored
|
||||||
|
// ARMv4/Armv5: Rn = Rn +/- 0x40
|
||||||
|
|
||||||
if (rlist == 0) {
|
if (rlist == 0) {
|
||||||
var und_addr = cpu.r[rn];
|
if (Arm32.arch == .v4t) {
|
||||||
if (U) {
|
const undefined_addr: u32 = if (U) blk: {
|
||||||
und_addr += if (P) 4 else 0;
|
break :blk cpu.r[rn] + if (P) 4 else 0;
|
||||||
} else {
|
} else blk: {
|
||||||
und_addr -= 0x40 - if (!P) 4 else 0;
|
break :blk cpu.r[rn] - 0x40 - if (!P) 4 else 0;
|
||||||
}
|
};
|
||||||
|
|
||||||
if (L) {
|
if (L) {
|
||||||
cpu.r[15] = cpu.read(u32, und_addr);
|
cpu.r[15] = cpu.read(u32, undefined_addr);
|
||||||
cpu.pipe.reload(cpu);
|
cpu.pipe.reload(cpu);
|
||||||
} else {
|
} else {
|
||||||
cpu.write(u32, und_addr, cpu.r[15] + 4);
|
cpu.write(u32, undefined_addr, cpu.r[15] + 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu.r[rn] = if (U) cpu.r[rn] + 0x40 else cpu.r[rn] - 0x40;
|
cpu.r[rn] = if (U) cpu.r[rn] + 0x40 else cpu.r[rn] - 0x40;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
i = first;
|
// What happens when W is set and Rn is in the rlist? (STM)
|
||||||
while (i < 16) : (i += 1) {
|
//
|
||||||
|
// Armv4: Store OLD Base if Rb is FIRST entry in Rlist, otherwise store NEW base
|
||||||
|
// Armv5: Always store OLD Base
|
||||||
|
|
||||||
|
// FIXME: This absolutely needs revisiting :skull:
|
||||||
|
|
||||||
|
const r15_present = rlist >> 15 & 1 == 1;
|
||||||
|
var write_to_base = true;
|
||||||
|
|
||||||
|
for (first_in_list..16) |idx| {
|
||||||
|
const i: u4 = @intCast(idx);
|
||||||
|
|
||||||
if (rlist >> i & 1 == 1) {
|
if (rlist >> i & 1 == 1) {
|
||||||
transfer(cpu, r15, i, address);
|
transfer(cpu, r15_present, i, address);
|
||||||
address += 4;
|
address += 4;
|
||||||
|
|
||||||
if (W and !L and write_to_base) {
|
if (W and !L and write_to_base) {
|
||||||
cpu.r[rn] = new_base;
|
cpu.r[rn] = new_base_addr;
|
||||||
write_to_base = false;
|
write_to_base = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (W and L and rlist >> rn & 1 == 0) cpu.r[rn] = new_base;
|
// What happens when W is set and Rn is in the rlist? (LDM)
|
||||||
|
//
|
||||||
|
// ARMv4: No writeback
|
||||||
|
// ARMv5: writeback if Rn is "the ONLY register" or NOT the LAST register
|
||||||
|
|
||||||
|
if (W and L) {
|
||||||
|
if (rlist >> rn & 1 == 0) {
|
||||||
|
cpu.r[rn] = new_base_addr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Arm32.arch) {
|
||||||
|
.v4t => {}, // No Writeback
|
||||||
|
.v5te => {
|
||||||
|
const rn_is_last = (15 - @clz(rlist)) <= rn;
|
||||||
|
|
||||||
|
if (reg_count == 1 or !rn_is_last) {
|
||||||
|
cpu.r[rn] = new_base_addr;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer(cpu: *Arm32, r15_present: bool, i: u5, address: u32) void {
|
fn transfer(cpu: *Arm32, r15_present: bool, i: u5, address: u32) void {
|
||||||
|
|
Loading…
Reference in New Issue