support additional instructions for waitloop skipping

Adds support for MultipleLoadStore, LoadStoreImmediateOffset, and
MoveShiftedRegister. This allows waitloop skipping to work in Spiderman
the Movie, bringing in-game fps from 280 to 375 and title screen fps
from 450 to 700.

This change also disallows instructions that branch from participating
in waitloop skipping. I don't expect this to affect anything, but I
figure it's better to be overly cautious.
This commit is contained in:
Matthew Berry 2022-12-04 18:26:09 -08:00
parent f6d88f82fa
commit 51a9de443a

View file

@ -10,10 +10,10 @@ module GBA
@branch_dest = 0_u32
# Collection of branch destinations identified as waitloops.
@identified_waitloops = Array(UInt32).new
@identified_waitloops = Array(UInt32).new(10)
# Collection of branch destinations identified as non-waitloops.
@identified_non_waitloops = Array(UInt32).new
@identified_non_waitloops = Array(UInt32).new(10)
# Flags when a waitloop is detected. Used by the CPU to fast-forward.
@entered_waitloop = false
@ -21,6 +21,9 @@ module GBA
# Table to quickly look up an instruction's class.
getter waitloop_instr_lut : Slice(Instruction.class) { build_lut }
# Instructions that waitloop detection fails to parse. Used temporarily for debugging until all instructions are implemented.
@waitloop_parse_failures = Set(Instruction.class).new
# Attempt to detect a waitloop. Assumes thumb instructions.
def analyze_loop(start_addr : UInt32, end_addr : UInt32) : Nil
return unless @attempt_waitloop_detection
@ -37,9 +40,14 @@ module GBA
written_bits = never_write = 0_u16
(start_addr...end_addr).step(2) do |addr|
instr = @gba.bus.read_half_internal(addr)
parsed_instr = waitloop_instr_lut[instr >> 8].parse?(instr)
instr_struct = waitloop_instr_lut[instr >> 8]
parsed_instr = instr_struct.parse?(instr)
unless parsed_instr && parsed_instr.read_only?
if parsed_instr == nil && !@waitloop_parse_failures.includes?(instr_struct)
puts "Failed to parse a #{instr_struct} while checking for a waitloop"
@waitloop_parse_failures.add(instr_struct)
end
@identified_non_waitloops.push(start_addr) if @cache_waitloop_results
return
end
@ -50,6 +58,11 @@ module GBA
return
end
if parsed_instr.write_bits & 1_u16 << 15 > 0 # instruction might branch, which could indicate an impure loop.
@identified_non_waitloops.push(start_addr) if @cache_waitloop_results
return
end
written_bits |= parsed_instr.write_bits
end
@ -125,11 +138,11 @@ module GBA
end
def read_bits : UInt16
0_u16
1_u16 << 15
end
def write_bits : UInt16
0_u16
1_u16 << 15
end
def self.parse?(instr : UInt16) : ConditionalBranch
@ -140,6 +153,43 @@ module GBA
end
struct MultipleLoadStore < Instruction
def initialize(@load : Bool, @rb : UInt16, @list : UInt16)
end
def read_only? : Bool
@load
end
def read_bits : UInt16
res = 1_u16 << @rb # always read
unless @load
if @list == 0
res |= 1_u16 << 15
else
res |= @list
end
end
res
end
def write_bits : UInt16
res = 1_u16 << @rb # always written to
if @load
if @list == 0
res |= 1_u16 << 15
else
res |= @list
end
end
res
end
def self.parse?(instr : UInt16) : MultipleLoadStore
load = bit?(instr, 11)
rb = bits(instr, 8..10)
list = bits(instr, 0..7)
new(load, rb, list)
end
end
struct PushPopRegisters < Instruction
@ -186,6 +236,35 @@ module GBA
end
struct LoadStoreImmediateOffset < Instruction
def initialize(@byte_quantity : Bool, @load : Bool, @offset : UInt16, @rb : UInt16, @rd : UInt16)
end
def read_only? : Bool
@load
end
def read_bits : UInt16
res = 1_u16 << @rb
res |= 1_u16 << @rd unless @load
res
end
def write_bits : UInt16
if @load
1_u16 << @rd
else
0_u16
end
end
def self.parse?(instr : UInt16) : LoadStoreImmediateOffset
byte_quantity = bit?(instr, 12)
load = bit?(instr, 11)
offset = bits(instr, 6..10)
rb = bits(instr, 3..5)
rd = bits(instr, 0..2)
new(byte_quantity, load, offset, rb, rd)
end
end
struct LoadStoreSignExtended < Instruction
@ -280,6 +359,28 @@ module GBA
end
struct MoveShiftedRegister < Instruction
def initialize(op : UInt16, @offset : UInt16, @rs : UInt16, @rd : UInt16)
end
def read_only? : Bool
true
end
def read_bits : UInt16
1_u16 << @rs
end
def write_bits : UInt16
1_u16 << @rd
end
def self.parse?(instr : UInt16) : MoveShiftedRegister
op = bits(instr, 11..12)
offset = bits(instr, 6..10)
rs = bits(instr, 3..5)
rd = bits(instr, 0..2)
new(op, offset, rs, rd)
end
end
struct Unimplemented < Instruction