-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an optimization pass which removes SetAttrs.
If a Lua program doesn't use _ENV directly to manipulate a global variable, then we can simply treat the variable just like any other local variable.
- Loading branch information
1 parent
2fe812c
commit 8261df0
Showing
8 changed files
with
172 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,146 @@ | ||
use bytecode::instructions::HLInstr; | ||
use bytecode::instructions::{HLInstr, Opcode}; | ||
use irgen::{constants_map::ConstantsMap, register_map::Lifetime}; | ||
|
||
/// Represents an IR in which all instructions are in SSA form. | ||
pub struct LuaIR { | ||
pub instrs: Vec<HLInstr>, | ||
pub const_map: ConstantsMap, | ||
pub lifetimes: Vec<Lifetime>, | ||
pub lifetimes: Vec<Option<Lifetime>>, | ||
pub non_env_lookups: Vec<usize>, | ||
} | ||
|
||
impl LuaIR { | ||
pub fn new( | ||
instrs: Vec<HLInstr>, | ||
const_map: ConstantsMap, | ||
mut lifetimes: Vec<Lifetime>, | ||
non_env_lookups: Vec<usize>, | ||
) -> LuaIR { | ||
lifetimes.sort_by(|x, y| x.start_point().cmp(&y.start_point())); | ||
LuaIR { | ||
instrs, | ||
const_map, | ||
lifetimes, | ||
lifetimes: lifetimes.into_iter().map(|l| Some(l)).collect(), | ||
non_env_lookups, | ||
} | ||
} | ||
|
||
/// Optimizes the IR and removes unnecessary `SetAttr` instructions. | ||
/// For example consider the following Lua program: `x = 2` | ||
/// The compiler generates: | ||
/// ------------- | ||
/// LDI 1 0 0 -- load IntTable[0] into register 1, R(1) = 2 | ||
/// LDS 2 0 0 -- load StringTable[0] into register 2, R(2) = "x" | ||
/// SetAttr 0 2 1 -- _ENV["x"] = 2 | ||
/// ------------- | ||
/// If the user does not query _ENV["x"] directly, that means we can optimize | ||
/// away the `LDS` and `SetAttr` instructions, and we can treat "x" like a local | ||
/// variable instead. | ||
pub fn optimize_env_lookups(&mut self) { | ||
let mut new_instrs = vec![]; | ||
let mut new_lifetimes: Vec<Option<Lifetime>> = Vec::with_capacity(self.lifetimes.len()); | ||
// push _ENV | ||
new_lifetimes.push(Some(Lifetime::new(0))); | ||
for _ in 1..self.lifetimes.len() { | ||
new_lifetimes.push(None); | ||
} | ||
let len = self.instrs.len(); | ||
let mut i = 0; | ||
while i < len { | ||
let instr = self.instrs[i]; | ||
if i < len - 2 && self.non_env_lookups.binary_search(&instr.1).is_ok() { | ||
// skip the attribute load | ||
i += 1; | ||
let attr = self.instrs[i]; | ||
self.lifetimes[attr.1] = None; | ||
// skip the SetAttr | ||
i += 1; | ||
} | ||
new_instrs.push(instr); | ||
let regs_to_update = Self::get_register_operands(instr); | ||
for r in regs_to_update { | ||
Self::update_end_point_to(&mut new_lifetimes[r], new_instrs.len()); | ||
} | ||
i += 1; | ||
} | ||
self.instrs = new_instrs; | ||
self.lifetimes = new_lifetimes; | ||
} | ||
|
||
/// Gets all the register numbers that are used by the given instruction. | ||
fn get_register_operands(instr: HLInstr) -> Vec<usize> { | ||
match instr.0 { | ||
Opcode::LDI | Opcode::LDS | Opcode::LDF => vec![instr.1], | ||
Opcode::MOV => vec![instr.1, instr.2], | ||
_ => vec![instr.1, instr.2, instr.3], | ||
} | ||
} | ||
|
||
/// Update the endpoint of a lifetime. If it doesn't exist, then create it. | ||
fn update_end_point_to(lifetime: &mut Option<Lifetime>, ep: usize) { | ||
match lifetime { | ||
Some(ref mut l) => l.set_end_point(ep), | ||
None => *lifetime = Some(Lifetime::new(ep - 1)), | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use bytecode::instructions::Opcode; | ||
|
||
#[test] | ||
fn optimize_env_lookups_generates_correct_code() { | ||
let instrs = vec![ | ||
HLInstr(Opcode::LDI, 1, 0, 0), | ||
HLInstr(Opcode::LDS, 2, 0, 0), | ||
HLInstr(Opcode::SetAttr, 0, 2, 1), | ||
HLInstr(Opcode::LDI, 3, 0, 0), | ||
]; | ||
let lifetimes = vec![ | ||
Lifetime::with_end_point(0, 3), | ||
Lifetime::with_end_point(0, 3), | ||
Lifetime::with_end_point(1, 3), | ||
Lifetime::with_end_point(3, 4), | ||
]; | ||
let non_env_lookups = vec![1]; | ||
let mut ir = LuaIR::new(instrs, ConstantsMap::new(), lifetimes, non_env_lookups); | ||
ir.optimize_env_lookups(); | ||
let expected_instrs = vec![HLInstr(Opcode::LDI, 1, 0, 0), HLInstr(Opcode::LDI, 3, 0, 0)]; | ||
assert_eq!(ir.instrs, expected_instrs); | ||
let expected_lifetimes = vec![ | ||
Some(Lifetime::with_end_point(0, 1)), | ||
Some(Lifetime::with_end_point(0, 1)), | ||
None, | ||
Some(Lifetime::with_end_point(1, 2)), | ||
]; | ||
assert_eq!(ir.lifetimes, expected_lifetimes); | ||
} | ||
|
||
#[test] | ||
fn optimize_env_lookups_empty_non_env_lookups() { | ||
let instrs = vec![ | ||
HLInstr(Opcode::LDI, 1, 0, 0), | ||
HLInstr(Opcode::LDS, 2, 0, 0), | ||
HLInstr(Opcode::SetAttr, 0, 2, 1), | ||
HLInstr(Opcode::LDI, 3, 0, 0), | ||
]; | ||
let lifetimes = vec![ | ||
Lifetime::with_end_point(0, 3), | ||
Lifetime::with_end_point(0, 3), | ||
Lifetime::with_end_point(1, 3), | ||
Lifetime::with_end_point(3, 4), | ||
]; | ||
let mut ir = LuaIR::new(instrs.clone(), ConstantsMap::new(), lifetimes, vec![]); | ||
ir.optimize_env_lookups(); | ||
assert_eq!(ir.instrs, instrs); | ||
let expected_lifetimes = vec![ | ||
Some(Lifetime::with_end_point(0, 3)), | ||
Some(Lifetime::with_end_point(0, 3)), | ||
Some(Lifetime::with_end_point(1, 3)), | ||
Some(Lifetime::with_end_point(3, 4)), | ||
]; | ||
assert_eq!(ir.lifetimes, expected_lifetimes); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters