Skip to content

Commit 788ef6c

Browse files
committed
Implement some passes (Slight help from AI)
1 parent 4f1fb4c commit 788ef6c

2 files changed

Lines changed: 351 additions & 26 deletions

File tree

zetac/src/codegen/ir/optimization/pass.rs

Lines changed: 268 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
use enum_map::Enum;
2+
use std::collections::HashMap;
3+
use ir::Bytecode;
24

3-
pub trait Pass: Send + Sync {
4-
fn optimize(&mut self, stmts: &mut Vec<u8>) -> anyhow::Result<()>;
5+
use crate::codegen::ir::module::ZetaModule;
56

7+
use std::sync::{Arc, Mutex};
8+
9+
pub trait Pass: Send + Sync + 'static {
10+
fn optimize(&self, bytecode: &mut Vec<u8>, module: &ZetaModule) -> anyhow::Result<()>;
611
fn priority(&self) -> OptimizationPassPriority;
12+
13+
/// Create a new boxed instance of the pass (for thread-safe cloning)
14+
#[inline]
15+
fn boxed(self) -> Box<dyn Pass>
16+
where
17+
Self: Sized + 'static
18+
{
19+
Box::new(self)
20+
}
721
}
822

923
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Enum)]
@@ -48,39 +62,278 @@ impl Default for OptimizationPassPriority {
4862
pub struct ConstantFoldingPass;
4963

5064
impl Pass for ConstantFoldingPass {
51-
fn optimize(&mut self, bytecode: &mut Vec<u8>) -> anyhow::Result<()> {
52-
for stmt in bytecode.iter_mut() {
53-
// TODO: check the bytecode, and if it's a math operation with two constants then let's replace it with a constant
54-
55-
}
56-
todo!()
65+
fn optimize(&self, bytecode: &mut Vec<u8>, _module: &ZetaModule) -> anyhow::Result<()> {
66+
let mut i = 0;
67+
while i < bytecode.len() {
68+
// Check if we have a binary operation with two constants before it
69+
if i >= 2 && Self::is_binary_op(bytecode[i]) {
70+
let op = bytecode[i];
71+
// Check if the previous two instructions are constants
72+
if i >= 2 && Self::is_constant_load(&bytecode[i-2..i]) {
73+
// Get the two constant values
74+
let (val1, val1_size) = Self::get_constant_value(&bytecode[..i-1]);
75+
let (val2, val2_size) = Self::get_constant_value(&bytecode[i-1..i+1]);
76+
77+
if let Some(result) = Self::evaluate_binary_op(op, val1, val2) {
78+
let result_bytes = Self::get_constant_bytes(result);
79+
let replace_start = i - val1_size - val2_size;
80+
let replace_end = i + 1;
81+
82+
let len = result_bytes.len();
83+
bytecode.splice(replace_start..replace_end, result_bytes);
84+
i = replace_start + len;
85+
continue;
86+
}
87+
}
88+
}
89+
i += 1;
90+
}
91+
Ok(())
5792
}
5893

94+
5995
fn priority(&self) -> OptimizationPassPriority {
60-
OptimizationPassPriority::Min
96+
OptimizationPassPriority::Medium
97+
}
98+
}
99+
100+
impl ConstantFoldingPass {
101+
fn is_binary_op(op: u8) -> bool {
102+
matches!(Bytecode::from(op),
103+
Bytecode::Add | Bytecode::Sub |
104+
Bytecode::Mul | Bytecode::Div |
105+
Bytecode::Mod | Bytecode::BitOr |
106+
Bytecode::BitAnd | Bytecode::BitXor |
107+
Bytecode::Shl | Bytecode::Shr
108+
)
109+
}
110+
111+
fn is_constant_load(ops: &[u8]) -> bool {
112+
matches!(Bytecode::from(ops[0]),
113+
Bytecode::PushI32 | Bytecode::PushI64 |
114+
Bytecode::PushU8 | Bytecode::PushU16 |
115+
Bytecode::PushU32 | Bytecode::PushU64
116+
)
117+
}
118+
119+
fn get_constant_value(ops: &[u8]) -> (i64, usize) {
120+
match Bytecode::from(ops[0]) {
121+
Bytecode::PushI32 => (i32::from_le_bytes([ops[1], ops[2], ops[3], ops[4]]) as i64, 5),
122+
Bytecode::PushI64 =>
123+
(i64::from_le_bytes([ops[1], ops[2], ops[3], ops[4], ops[5], ops[6], ops[7], ops[8]]), 9),
124+
Bytecode::PushU8 => (ops[1] as i64, 2),
125+
Bytecode::PushU16 => (u16::from_le_bytes([ops[1], ops[2]]) as i64, 3),
126+
Bytecode::PushU32 => (u32::from_le_bytes([ops[1], ops[2], ops[3], ops[4]]) as i64, 5),
127+
Bytecode::PushU64 =>
128+
(u64::from_le_bytes([ops[1], ops[2], ops[3], ops[4], ops[5], ops[6], ops[7], ops[8]]) as i64, 9),
129+
_ => (0, 0),
130+
}
131+
}
132+
133+
fn evaluate_binary_op(op: u8, left: i64, right: i64) -> Option<i64> {
134+
match Bytecode::from(op) {
135+
Bytecode::Add => left.checked_add(right),
136+
Bytecode::Sub => left.checked_sub(right),
137+
Bytecode::Mul => left.checked_mul(right),
138+
Bytecode::Div => left.checked_div(right),
139+
Bytecode::Mod => left.checked_rem(right),
140+
Bytecode::BitOr => Some(left | right),
141+
Bytecode::BitAnd => Some(left & right),
142+
Bytecode::BitXor => Some(left ^ right),
143+
Bytecode::Shl => left.checked_shl(right as u32),
144+
Bytecode::Shr => left.checked_shr(right as u32),
145+
_ => None,
146+
}
147+
}
148+
149+
fn get_constant_bytes(value: i64) -> Vec<u8> {
150+
if value >= i8::MIN as i64 && value <= i8::MAX as i64 {
151+
vec![Bytecode::PushI8 as u8, value as u8]
152+
} else if value >= i16::MIN as i64 && value <= i16::MAX as i64 {
153+
let bytes = (value as i16).to_le_bytes();
154+
vec![Bytecode::PushI16 as u8, bytes[0], bytes[1]]
155+
} else if value >= i32::MIN as i64 && value <= i32::MAX as i64 {
156+
let bytes = (value as i32).to_le_bytes();
157+
vec![Bytecode::PushI32 as u8, bytes[0], bytes[1], bytes[2], bytes[3]]
158+
} else {
159+
let bytes = value.to_le_bytes();
160+
let mut result = vec![Bytecode::PushI64 as u8];
161+
result.extend_from_slice(&bytes);
162+
result
163+
}
61164
}
62165
}
63166

64167
pub struct DeadCodeEliminationPass;
65168

66169
impl Pass for DeadCodeEliminationPass {
67-
fn optimize(&mut self, bytecode: &mut Vec<u8>) -> anyhow::Result<()> {
68-
todo!()
170+
fn optimize(&self, bytecode: &mut Vec<u8>, _module: &ZetaModule) -> anyhow::Result<()> {
171+
let mut i = 0;
172+
while i < bytecode.len() {
173+
match Bytecode::from(bytecode[i]) {
174+
// Remove unreachable code after return/break/continue/throw
175+
Bytecode::Return | Bytecode::Halt => {
176+
// Remove all instructions until the next label or end of function
177+
let mut j = i + 1;
178+
while j < bytecode.len() {
179+
if Self::is_control_flow(&bytecode[j..]) {
180+
break;
181+
}
182+
j += 1;
183+
}
184+
if j > i + 1 {
185+
bytecode.drain(i+1..j);
186+
}
187+
}
188+
// Remove dead stores (store followed by another store to same location)
189+
Bytecode::StoreLocal | Bytecode::StoreGlobal | Bytecode::StoreVar => {
190+
if let Some(loc) = Self::get_store_location(&bytecode[i..]) {
191+
let mut j = i + loc.len();
192+
while j < bytecode.len() {
193+
if Self::is_control_flow(&bytecode[j..]) {
194+
break;
195+
}
196+
197+
// Check if this is a store to the same location
198+
if let Some(other_loc) = Self::get_store_location(&bytecode[j..]) {
199+
if other_loc == loc {
200+
// Found a store to the same location, remove the first one
201+
bytecode.drain(i..i+loc.len());
202+
i -= 1; // Adjust index since we removed items
203+
break;
204+
}
205+
j += other_loc.len();
206+
} else {
207+
j += 1;
208+
}
209+
}
210+
}
211+
}
212+
_ => {}
213+
}
214+
i += 1;
215+
}
216+
Ok(())
69217
}
70218

71219
fn priority(&self) -> OptimizationPassPriority {
72-
OptimizationPassPriority::Min
220+
OptimizationPassPriority::Medium
221+
}
222+
}
223+
224+
impl DeadCodeEliminationPass {
225+
fn is_control_flow(bytecode: &[u8]) -> bool {
226+
matches!(
227+
Bytecode::from(bytecode[0]),
228+
Bytecode::Jump
229+
| Bytecode::JumpIfTrue
230+
| Bytecode::JumpIfFalse
231+
| Bytecode::Return
232+
| Bytecode::Halt
233+
| Bytecode::Call
234+
| Bytecode::TailCall
235+
| Bytecode::CallNative
236+
)
237+
}
238+
239+
fn get_store_location(bytecode: &[u8]) -> Option<Vec<u8>> {
240+
match Bytecode::from(bytecode[0]) {
241+
Bytecode::StoreLocal | Bytecode::LoadLocal => {
242+
if bytecode.len() > 1 {
243+
Some(bytecode[0..2].to_vec())
244+
} else {
245+
None
246+
}
247+
}
248+
Bytecode::StoreGlobal | Bytecode::LoadGlobal => {
249+
if bytecode.len() > 1 {
250+
Some(bytecode[0..2].to_vec())
251+
} else {
252+
None
253+
}
254+
}
255+
Bytecode::StoreVar | Bytecode::LoadVar => {
256+
if bytecode.len() > 1 {
257+
// For StoreVar, the name length is the next byte
258+
let name_len = bytecode[1] as usize;
259+
if bytecode.len() > 1 + name_len {
260+
Some(bytecode[0..2 + name_len].to_vec())
261+
} else {
262+
None
263+
}
264+
} else {
265+
None
266+
}
267+
}
268+
_ => None,
269+
}
73270
}
74271
}
75272

76-
pub struct InliningPass;
273+
274+
pub struct InliningPass {
275+
// Maximum size of function to inline (in bytes)
276+
max_inline_size: usize,
277+
}
278+
279+
impl Default for InliningPass {
280+
fn default() -> Self {
281+
Self {
282+
max_inline_size: 120, // Reasonable default for small functions
283+
}
284+
}
285+
}
77286

78287
impl Pass for InliningPass {
79-
fn optimize(&mut self, bytecode: &mut Vec<u8>) -> anyhow::Result<()> {
80-
todo!()
288+
fn optimize(&self, bytecode: &mut Vec<u8>, module: &ZetaModule) -> anyhow::Result<()> {
289+
let mut i = 0;
290+
while i < bytecode.len() {
291+
if i + 1 < bytecode.len() && Bytecode::from(bytecode[i]) == Bytecode::Call {
292+
// Get the function name
293+
if let Some((name, name_len)) = self.get_function_name(&bytecode[i+1..]) {
294+
// Look up the function in the module
295+
if let Some((_, func)) = module.functions.iter().find(|(_, f)| f.name == name) {
296+
// Check if the function is small enough to inline
297+
if func.code.len() <= self.max_inline_size {
298+
// Replace the call with the function body
299+
let call_size = 1 + 1 + name_len; // CALL op + name length + name
300+
let mut new_code = func.code.clone();
301+
302+
// Replace return with jump to the end of inlined code
303+
if let Some(return_pos) = new_code.iter().position(|&b| Bytecode::from(b) == Bytecode::Return) {
304+
new_code.truncate(return_pos);
305+
}
306+
307+
let len = new_code.len();
308+
bytecode.splice(i..i+call_size, new_code);
309+
i += len;
310+
continue;
311+
}
312+
}
313+
}
314+
}
315+
i += 1;
316+
}
317+
Ok(())
81318
}
82319

83320
fn priority(&self) -> OptimizationPassPriority {
84321
OptimizationPassPriority::Max
85322
}
323+
}
324+
325+
impl InliningPass {
326+
fn get_function_name(&self, bytecode: &[u8]) -> Option<(String, usize)> {
327+
if bytecode.is_empty() {
328+
return None;
329+
}
330+
331+
let name_len = bytecode[0] as usize;
332+
if bytecode.len() > name_len {
333+
let name = String::from_utf8_lossy(&bytecode[1..=name_len]).into_owned();
334+
Some((name, name_len + 1)) // +1 for the length byte
335+
} else {
336+
None
337+
}
338+
}
86339
}

0 commit comments

Comments
 (0)