Skip to content

Commit ed52904

Browse files
authored
Polishing (#23)
Performance optimizations and bug fixes
1 parent 146c7eb commit ed52904

23 files changed

+2594
-471
lines changed

EqSat/Cargo.lock

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

EqSat/Cargo.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ edition = "2021"
77

88
[lib]
99
crate-type = ["cdylib"]
10-
path = "src/main.rs"
10+
path = "src/lib.rs"
1111

1212

1313
[dependencies]
@@ -20,9 +20,17 @@ mimalloc = { version = "*", default-features = false }
2020
# egraph = { path = "./egraph" }
2121
foldhash = "=0.1.0"
2222

23+
[dependencies.iced-x86]
24+
version = "1.21.0"
25+
features = ["code_asm"]
26+
2327
[profile.release]
2428
debug = true
2529
debuginfo-level = 2
26-
panic = "abort"
2730
lto = true
2831
codegen-units = 1
32+
panic = "abort"
33+
opt-level = 3
34+
35+
[build]
36+
rustflags = ["-C", "target-cpu=native"]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use iced_x86::{Instruction, Register};
2+
3+
pub trait IAmd64Assembler {
4+
fn push_reg(&mut self, reg: Register);
5+
6+
fn push_mem64(&mut self, base_reg: Register, offset: i32);
7+
8+
fn pop_reg(&mut self, reg: Register);
9+
10+
fn mov_reg_reg(&mut self, reg1: Register, reg2: Register);
11+
12+
fn mov_reg_mem64(&mut self, dst_reg: Register, base_reg: Register, offset: i32);
13+
14+
fn mov_mem64_reg(&mut self, base_reg: Register, offset: i32, src_reg: Register);
15+
16+
fn movabs_reg_imm64(&mut self, reg: Register, imm: u64);
17+
18+
fn add_reg_reg(&mut self, reg1: Register, reg2: Register);
19+
20+
fn add_reg_imm32(&mut self, reg: Register, imm32: u32);
21+
22+
fn sub_reg_imm32(&mut self, reg: Register, imm32: u32);
23+
24+
fn imul_reg_reg(&mut self, reg1: Register, reg2: Register);
25+
26+
fn and_reg_reg(&mut self, reg1: Register, reg2: Register);
27+
28+
fn and_reg_imm32(&mut self, reg: Register, imm: u32);
29+
30+
fn and_mem64_reg(&mut self, base_reg: Register, offset: i32, src_reg: Register);
31+
32+
fn or_reg_reg(&mut self, reg1: Register, reg2: Register);
33+
34+
fn xor_reg_reg(&mut self, reg1: Register, reg2: Register);
35+
36+
fn not_reg(&mut self, reg: Register);
37+
38+
fn shl_reg_cl(&mut self, reg: Register);
39+
40+
fn shr_reg_cl(&mut self, reg: Register);
41+
42+
fn shr_reg_imm8(&mut self, reg: Register, imm8: u8);
43+
44+
fn call_reg(&mut self, reg: Register);
45+
46+
fn ret(&mut self);
47+
48+
fn get_instructions(&mut self) -> Vec<Instruction>;
49+
50+
fn get_bytes(&mut self) -> Vec<u8>;
51+
52+
fn reset(&mut self);
53+
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
use iced_x86::{IcedError, Register};
2+
use rand::Rng;
3+
4+
use crate::assembler::{
5+
amd64_assembler::IAmd64Assembler, fast_amd64_assembler::FastAmd64Assembler,
6+
iced_amd64_assembler::IcedAmd64Assembler,
7+
};
8+
9+
/// Differential tester that compares FastAmd64Assembler against IcedAmd64Assembler
10+
pub struct Amd64AssemblerDifferentialTester {
11+
rand: rand::rngs::ThreadRng,
12+
registers: Vec<Register>,
13+
iced_assembler: IcedAmd64Assembler,
14+
fast_assembler: FastAmd64Assembler,
15+
}
16+
17+
impl Amd64AssemblerDifferentialTester {
18+
/// Creates a new differential tester with the given buffer
19+
pub unsafe fn new(buffer: *mut u8) -> Result<Self, IcedError> {
20+
let registers = vec![
21+
Register::RAX,
22+
Register::RCX,
23+
Register::RDX,
24+
Register::RBX,
25+
Register::RSI,
26+
Register::RDI,
27+
Register::RSP,
28+
Register::RBP,
29+
Register::R8,
30+
Register::R9,
31+
Register::R10,
32+
Register::R11,
33+
Register::R12,
34+
Register::R13,
35+
Register::R14,
36+
Register::R15,
37+
];
38+
39+
Ok(Self {
40+
rand: rand::thread_rng(),
41+
registers,
42+
iced_assembler: IcedAmd64Assembler::new()?,
43+
fast_assembler: FastAmd64Assembler {
44+
p: buffer,
45+
offset: 0,
46+
},
47+
})
48+
}
49+
50+
pub fn test() -> Result<(), Box<dyn std::error::Error>> {
51+
let mut buffer = vec![0u8; 64 * 4096];
52+
let ptr = buffer.as_mut_ptr();
53+
54+
unsafe {
55+
let mut tester = Self::new(ptr)?;
56+
tester.run()?;
57+
}
58+
59+
Ok(())
60+
}
61+
62+
pub fn run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
63+
for i in 0..self.registers.len() {
64+
let reg1 = self.registers[i];
65+
self.diff_reg_insts(reg1)?;
66+
67+
for j in (i + 1)..self.registers.len() {
68+
let reg2 = self.registers[j];
69+
self.diff_reg_reg_insts(reg1, reg2)?;
70+
}
71+
}
72+
73+
println!("All differential tests passed!");
74+
Ok(())
75+
}
76+
77+
fn diff_reg_insts(&mut self, reg: Register) -> Result<(), Box<dyn std::error::Error>> {
78+
self.diff("PushReg", |asm| asm.push_reg(reg))?;
79+
self.diff("PopReg", |asm| asm.pop_reg(reg))?;
80+
self.diff("NotReg", |asm| asm.not_reg(reg))?;
81+
self.diff("ShlRegCl", |asm| asm.shl_reg_cl(reg))?;
82+
self.diff("ShrRegCl", |asm| asm.shr_reg_cl(reg))?;
83+
self.diff("CallReg", |asm| asm.call_reg(reg))?;
84+
85+
// Test reg, constant instructions
86+
for _ in 0..100 {
87+
let c = self.rand.gen::<i64>() as u64;
88+
89+
self.diff("MovabsRegImm64", |asm| asm.movabs_reg_imm64(reg, c))?;
90+
self.diff("AddRegImm32", |asm| asm.add_reg_imm32(reg, c as u32))?;
91+
self.diff("SubRegImm32", |asm| asm.sub_reg_imm32(reg, c as u32))?;
92+
self.diff("AndRegImm32", |asm| asm.and_reg_imm32(reg, c as u32))?;
93+
self.diff("ShrRegImm8", |asm| asm.shr_reg_imm8(reg, c as u8))?;
94+
95+
if reg != Register::RSP {
96+
self.diff("PushMem64", |asm| asm.push_mem64(reg, c as i32))?;
97+
}
98+
}
99+
100+
Ok(())
101+
}
102+
103+
fn diff_reg_reg_insts(
104+
&mut self,
105+
reg1: Register,
106+
reg2: Register,
107+
) -> Result<(), Box<dyn std::error::Error>> {
108+
// Test reg, reg instructions
109+
self.diff("MovRegReg", |asm| asm.mov_reg_reg(reg1, reg2))?;
110+
self.diff("MovRegReg", |asm| asm.mov_reg_reg(reg2, reg1))?;
111+
self.diff("AddRegReg", |asm| asm.add_reg_reg(reg1, reg2))?;
112+
self.diff("AddRegReg", |asm| asm.add_reg_reg(reg2, reg1))?;
113+
self.diff("AndRegReg", |asm| asm.and_reg_reg(reg1, reg2))?;
114+
self.diff("AndRegReg", |asm| asm.and_reg_reg(reg2, reg1))?;
115+
self.diff("OrRegReg", |asm| asm.or_reg_reg(reg1, reg2))?;
116+
self.diff("OrRegReg", |asm| asm.or_reg_reg(reg2, reg1))?;
117+
self.diff("XorRegReg", |asm| asm.xor_reg_reg(reg1, reg2))?;
118+
self.diff("XorRegReg", |asm| asm.xor_reg_reg(reg2, reg1))?;
119+
self.diff("ImulRegReg", |asm| asm.imul_reg_reg(reg1, reg2))?;
120+
self.diff("ImulRegReg", |asm| asm.imul_reg_reg(reg2, reg1))?;
121+
122+
// Test reg, reg, constant instructions
123+
for _ in 0..100 {
124+
let c = self.rand.gen::<i32>();
125+
126+
self.diff("MovMem64Reg", |asm| asm.mov_mem64_reg(reg1, c, reg2))?;
127+
self.diff("MovMem64Reg", |asm| asm.mov_mem64_reg(reg2, c, reg1))?;
128+
self.diff("MovRegMem64", |asm| asm.mov_reg_mem64(reg1, reg2, c))?;
129+
self.diff("MovRegMem64", |asm| asm.mov_reg_mem64(reg2, reg1, c))?;
130+
self.diff("AndMem64Reg", |asm| asm.and_mem64_reg(reg1, c, reg2))?;
131+
self.diff("AndMem64Reg", |asm| asm.and_mem64_reg(reg2, c, reg1))?;
132+
}
133+
134+
Ok(())
135+
}
136+
137+
/// Executes a test function on both assemblers and compares results
138+
fn diff<F>(&mut self, method_name: &str, func: F) -> Result<(), Box<dyn std::error::Error>>
139+
where
140+
F: Fn(&mut dyn IAmd64Assembler),
141+
{
142+
// Assemble the instruction using both assemblers
143+
func(&mut self.iced_assembler);
144+
func(&mut self.fast_assembler);
145+
146+
// Compare the results
147+
self.compare(method_name)?;
148+
149+
// Reset both assemblers
150+
self.iced_assembler.reset();
151+
self.fast_assembler.reset();
152+
153+
Ok(())
154+
}
155+
156+
/// Compares the output of both assemblers
157+
fn compare(&mut self, method_name: &str) -> Result<(), Box<dyn std::error::Error>> {
158+
let iced_insts = self.iced_assembler.get_instructions();
159+
let iced_bytes = self.iced_assembler.get_bytes();
160+
let our_insts = self.fast_assembler.get_instructions();
161+
let our_bytes = self.fast_assembler.get_bytes();
162+
163+
if iced_insts.is_empty() || iced_bytes.is_empty() || iced_insts.len() != our_insts.len() {
164+
return Err(format!(
165+
"Method {} failed: instruction count mismatch (iced: {}, ours: {})",
166+
method_name,
167+
iced_insts.len(),
168+
our_insts.len()
169+
)
170+
.into());
171+
}
172+
173+
// Check if instructions are equivalent
174+
if iced_insts.len() == 1 && our_insts.len() == 1 {
175+
let iced_str = format!("{}", iced_insts[0]);
176+
let our_str = format!("{}", our_insts[0]);
177+
178+
if iced_str != our_str {
179+
return Err(format!(
180+
"Method {} failed: Instruction '{}' and '{}' not equivalent!\nIced bytes: {:?}\nOur bytes: {:?}",
181+
method_name,
182+
iced_str,
183+
our_str,
184+
iced_bytes,
185+
our_bytes
186+
).into());
187+
}
188+
} else {
189+
// Compare all instructions
190+
for (i, (iced_inst, our_inst)) in iced_insts.iter().zip(our_insts.iter()).enumerate() {
191+
let iced_str = format!("{}", iced_inst);
192+
let our_str = format!("{}", our_inst);
193+
194+
if iced_str != our_str {
195+
return Err(format!(
196+
"Method {} failed at instruction {}: '{}' != '{}'",
197+
method_name, i, iced_str, our_str
198+
)
199+
.into());
200+
}
201+
}
202+
}
203+
204+
Ok(())
205+
}
206+
}

0 commit comments

Comments
 (0)