Skip to content

Commit

Permalink
Concatenate (#82)
Browse files Browse the repository at this point in the history
* concatenate sequences instead of "striding"

* remove so much vestigial nonsense

* impl Iterable for SdccCall1 and Subroutine

* no such thing as stride, remove it

* doc-comments

* renamed:    src/z80/calling_conventions.rs -> src/z80/sdcccall1.rs

* nicer way to contruct return instructions

* mos6502 subroutine

* fmt

* basic aapcs32 module

---------

Co-authored-by: Sam M W <you@example.com>
  • Loading branch information
omarandlorraine and Sam M W authored Nov 4, 2024
1 parent 88fe4a4 commit 60a841e
Show file tree
Hide file tree
Showing 21 changed files with 425 additions and 244 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ emu6809 = { version = "0.1.2", optional = true }
iz80 = { version = "0.4.1", optional = true }
mos6502 = {version = "0.6.0", optional = true }
dez80 = { version = "4.0.1", optional = true }
bitmatch = "0.1.1"
2 changes: 2 additions & 0 deletions examples/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ use strop::Disassemble;
use strop::Iterable;
use strop::StropError;

/*
fn zero(_hex: u8) -> Result<u8, StropError> {
Ok(b'0')
}
*/

fn target_function(hex: u8) -> Result<u8, StropError> {
match hex {
Expand Down
6 changes: 4 additions & 2 deletions examples/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use strop::z80::SdccCall1;
use strop::BruteForce;
use strop::Callable;
use strop::Disassemble;
use strop::Iterable;

fn target_function() -> SdccCall1 {
// Construct some machine code.
Expand All @@ -18,14 +19,15 @@ fn target_function() -> SdccCall1 {
// This is not a terribly efficient way to encode this program; you can save a byte and some
// time with `LD HL, 7F40H` instead (a single 16-bit bit immediate load is more efficient than two
// individual 8-bit loads) -- let's see if strop figures this out!
//
// When building a SdccCall1 callable we leave off the terminating RET instruction since when
// SdccCall1 builds, it adds one

use strop::Goto;
use strop::IterableSequence;

let mc = [
Insn::new(&[0x26, 0x40]), // LD H,40H
Insn::new(&[0x2e, 0x7f]), // LD L,7FH
Insn::new(&[0xc9]), // RET
];

// This machine code is callable using the sdcccall(1) calling convention.
Expand Down
93 changes: 93 additions & 0 deletions src/armv4t/aapcs32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//! Implements searches for functions complying with the AAPCS32 calling convention, as used by
//! modern (EABI) linux systems and others.
use crate::armv4t::isa::decode::Register;
use crate::armv4t::Insn;
use crate::Sequence;

fn callee_saved(r: &Register) -> bool {
match r {
Register::R0 => false,
Register::R1 => false,
Register::R2 => false,
Register::R3 => false,
Register::R4 => true,
Register::R5 => true,
Register::R6 => true,
Register::R7 => true,
Register::R8 => true,
Register::R9 => true,
Register::R10 => true,
Register::R11 => true,
Register::R12 => false,
Register::Sp => false,
Register::Lr => false,
Register::Pc => false,
}
}

fn prologue(r: &[Register]) -> Vec<Insn> {
if r.is_empty() {
vec![]
} else {
vec![Insn::push(r)]
}
}

fn epilogue(r: &[Register]) -> Vec<Insn> {
if r.is_empty() {
vec![Insn::bx_lr()]
} else {
let mut r = r.to_owned();
r.push(Register::Pc);
vec![Insn::pop(&r)]
}
}

/// The AAPCS32-compliant function
#[derive(Debug)]
pub struct Function(Sequence<Insn>);

impl Function {
/// Builds the function by concatenating the prologue, the body of the subroutine, and the
/// epilogue. The prologue and epilogue are made to save and restore the callee-saved
/// registers.
pub fn build(&self) -> Sequence<Insn> {
let mut unique_elements = std::collections::HashSet::new();

let callee_saved_registers: Vec<_> = self
.0
.iter()
.map(|i| i.uses())
.flat_map(|v| v.into_iter())
.filter(|item| unique_elements.insert(*item))
.filter(callee_saved)
.collect();

let prologue = prologue(&callee_saved_registers);
let epilogue = epilogue(&callee_saved_registers);
vec![&prologue, &self.0, &epilogue].into()
}
}

impl crate::Disassemble for Function {
fn dasm(&self) {
self.0.dasm()
}
}

impl crate::Goto<Insn> for Function {
fn goto(&mut self, t: &[Insn]) {
self.0.goto(t);
}
}

impl crate::Iterable for Function {
fn first() -> Self {
Self(crate::Iterable::first())
}

fn step(&mut self) -> bool {
self.0.step()
}
}
3 changes: 2 additions & 1 deletion src/armv4t/diss.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
impl std::fmt::Display for crate::armv4t::Insn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{}", self.decode().display(Default::default()))
let i = unarm::arm::Ins::new(self.0, &Default::default()).parse(&Default::default());
write!(f, "{}", i.display(Default::default()))
}
}

Expand Down
173 changes: 81 additions & 92 deletions src/armv4t/isa.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,82 @@
//! Module for representing ARMv4T machine code instructions.
pub mod decode;

/// Represents an ARMv4T machine code instruction.
#[derive(Clone, Copy, Default, PartialOrd, PartialEq)]
pub struct Insn(pub(crate) u32);

impl Insn {
/// Return the instruction, `bx lr`.
pub fn bx_lr() -> Self {
Self(0xe12fff1e)
}

/// Returns the instruction for popping the registers off the stack
pub fn pop(r: &[crate::armv4t::isa::decode::Register]) -> Self {
use crate::armv4t::isa::decode::Register;
let mut i = 0xe8bd0000u32;
for reg in [
Register::R0,
Register::R1,
Register::R2,
Register::R3,
Register::R4,
Register::R5,
Register::R6,
Register::R7,
Register::R8,
Register::R9,
Register::R10,
Register::R11,
Register::R12,
Register::Lr,
Register::Sp,
Register::Pc,
]
.iter()
.enumerate()
{
if r.contains(reg.1) {
i |= 1 << (reg.0 as u32);
}
}
Self(i)
}

/// Returns the instruction for pushing the registers onto the stack
pub fn push(r: &[crate::armv4t::isa::decode::Register]) -> Self {
use crate::armv4t::isa::decode::Register;
let mut i = 0xe92d0000u32;
for reg in [
Register::R0,
Register::R1,
Register::R2,
Register::R3,
Register::R4,
Register::R5,
Register::R6,
Register::R7,
Register::R8,
Register::R9,
Register::R10,
Register::R11,
Register::R12,
Register::Lr,
Register::Sp,
Register::Pc,
]
.iter()
.enumerate()
{
if r.contains(reg.1) {
i |= 1 << (reg.0 as u32);
}
}
Self(i)
}
}

impl crate::Iterable for Insn {
fn first() -> Self {
Insn(0)
Expand All @@ -14,17 +87,6 @@ impl crate::Iterable for Insn {
false
} else {
self.0 += 1;
self.fixup();
true
}
}

fn stride(&mut self) -> bool {
if self.0 > 0xfff70000 {
false
} else {
self.0 += 0x80000;
self.fixup();
true
}
}
Expand All @@ -42,71 +104,9 @@ impl crate::Encode<u32> for Insn {
}
}

impl Insn {
/// Decodes the instruction and returns an `unarm::ParsedIns`
pub fn decode(&self) -> unarm::ParsedIns {
unarm::arm::Ins::new(self.0, &Default::default()).parse(&Default::default())
}

/// No matter the `Insn`'s value, if it does not encode a valid ARMv4T machine code
/// instruction, this method mutates it so that it does.
pub fn fixup(&mut self) {
fn exclude(i: &mut Insn, rng: std::ops::RangeInclusive<u32>) {
if rng.contains(&i.0) {
i.0 = *rng.end() + 1;
assert!(i.is_valid(), "{:?}", i);
}
}

if self.0 & 0x0e000000 == 0x00000000 {
let rotate: u8 = ((self.0 >> 4) & 0x000000ff).try_into().unwrap();

if rotate & 0x01 == 0 {
// shifting by a five-bit unsigned integer, nothing to check
} else {
// shifting by an amount specified in a register; we need to check that bit 7 of
// the instruction is 0, otherwise it is an undefined instruction.
if self.0 & 0x00000080 != 0 {
self.0 |= 0xff;
self.0 += 1;
}
}
}
exclude(self, 0x01000000..=0x010eff8f);

/*
if self.0 & 0x0d900000 == 0x01800000 {
// the instruction is one of: tst, teq, cmp, cmn, but the the S bit is not set!
// So for this instruction to be valid we need to set the S bit
self.0 |= 0x00100000
}
*/
if self.0 & 0x0fffffff == 0x012fff1f {
// It's a bx{cond} pc instruction, which is undefined
self.0 += 1;
}
}

/// Returns `true` iff the `Insn` represents a valid ARMv4T machine code instruction.
pub fn is_valid(&self) -> bool {
if self.0 & 0x0c000000 == 0x0c000000 {
// coprocessor instructions are not implemented in the emulator
return false;
} else if self.0 & 0x0e000010 == 0x06000010 {
// this range of instructions is undefined
return false;
}

let d = self.decode();

if d.mnemonic.starts_with("bx") {
// A Branch and Exchange instruction with PC as its operand is undefined behaviour
if let unarm::args::Argument::Reg(reg) = d.args[0] {
return reg.reg != unarm::args::Register::Pc;
}
}

true
impl crate::Disassemble for Insn {
fn dasm(&self) {
println!("{:?}", self);
}
}

Expand All @@ -123,6 +123,11 @@ mod test {
cpu.step(&mut mem)
}

#[test]
fn bx_lr() {
assert_eq!("bx lr", &format!("{}", super::Insn::bx_lr()));
}

#[test]
#[ignore]
fn all_instructions() {
Expand All @@ -132,29 +137,13 @@ mod test {
// let mut i = super::Insn::first();

while i.step() {
i.decode();

// check that the instruction can be disassembled
format!("{}", i);
assert_eq!(format!("{:?}", i).len(), 95, "{:?}", i);

// println!("{:?}", i);

assert!(!format!("{:?}", i).contains("illegal"), "{:?}", i);

// check that the increment method does not visit invalid instructions; this will in
// turn validate the fixup method.
if !i.is_valid() {
let beginning = i;
let mut end = i;
while !end.is_valid() {
end.step();
}
panic!("found a range of illegal instructions visited by the .increment method: 0x{:08x}..=0x{:08x}",
beginning.0, end.0
);
}

// check that the emulator can execute the instruction
if !emulator_knows_it(i) {
let beginning = i;
Expand Down
Loading

0 comments on commit 60a841e

Please sign in to comment.