Более приятный способ сопоставления с образцом окна инструкций по сборке глазка с ржавчиной?
Итак, я пытаюсь реализовать оптимизацию глазка, где я иду отVec<LLStackInstruction> -> Vec<LLStackInstruction>
, где возвращаемый список оптимизирован. (LL
для низкого уровня) Наш компилятор смоделирован как абстрактная стековая машина, и для инициализации/назначения мы помещаем RHS в стек, помещаем адрес LHS в стек, а затем извлекаем и STR, но пытаемся свести это к отправке RHS и затем сохраняем по адресу прямо через спецификатор адреса, но я получаю очень уродливое совпадение, например: (и с изменчивостью)
pub fn peephole_optimiser(instrs: Vec<LLStackInstruction>) -> Vec<LLStackInstruction> {
let mut optimized_instructions: Vec<LLStackInstruction> = Vec::new();
let mut iter = instrs.iter().peekable();
while let Some(instruction) = iter.next() {
let window_iter = iter.clone();
let window: Vec<_> = [vec![instruction], window_iter.take(10).collect()].concat();
if let Some(LLStackInstruction::Push(Register::FP)) = window.get(0) {
if let Some(LLStackInstruction::Mov(Condition::None, r1, ArgType::Imm(n))) = window.get(1) {
if let Some(LLStackInstruction::Push(r2)) = window.get(2) {
if let Some(LLStackInstruction::Pop(_)) = window.get(3) {
if let Some(LLStackInstruction::Pop(_)) = window.get(4) {
if let Some(LLStackInstruction::Sub(..)) = window.get(5) {
if let Some(LLStackInstruction::Branch(..)) = window.get(6) {
if let Some(LLStackInstruction::Push(_)) = window.get(7) {
if let Some(LLStackInstruction::Pop(_)) = window.get(8) {
if let Some(LLStackInstruction::Pop(_)) = window.get(9) {
if let Some(LLStackInstruction::Str(..)) = window.get(10) {
if r1 == r2 {
optimized_instructions.push(LLStackInstruction::Pop(Register::R4));
optimized_instructions.push(LLStackInstruction::Str(Register::R4, AddressSpecifier::ImmediateOffset(Register::FP, -n)));
iter.nth(9);
continue;
}
}
}
}
}
}
}
}
}
}
}
}
optimized_instructions.push(instruction.clone());
}
optimized_instructions
}
Как я могу сделать это лучше?
Пример того, что делает эта оптимизация
// Compact init/assign statements
// push {fp}
// mov r1, #88
// push {r1}
// pop {r0}
// pop {r1}
// subs r0, r1, r0
// bvs _overflow_err
// push {r0}
// pop {r3}
// pop {r4}
// str r4, [r3]
// INTO
// pop {r4}
// str r4, [fp, #-88]
Единственное, о чем я мог подумать, это то, что я мог бы выразить это в несколько шагов меньших окон и выполнить несколько проходов оптимизации (или после оптимизации окна перезапустить результат с его начала до тех пор, пока он не изменится).
3 ответа
Вы можете сопоставить свое окно напрямую, а не вложенноеif let
заявления. Rust поддерживает деструктуризацию срезов по шаблону:
match window[..11] {
[
LLStackInstruction::Push(Register::FP),
LLStackInstruction::Push(r2),
LLStackInstruction::Pop(_),
LLStackInstruction::Pop(_),
LLStackInstruction::Sub(..),
LLStackInstruction::Branch(..),
LLStackInstruction::Push(_),
LLStackInstruction::Pop(_),
LLStackInstruction::Pop(_),
LLStackInstruction::Str(..)
] if r1 == r2 => {
optimized_instructions.push(LLStackInstruction::Pop(Register::R4));
optimized_instructions.push(LLStackInstruction::Str(
Register::R4,
AddressSpecifier::ImmediateOffset(Register::FP, -n),
));
iter.nth(9);
continue;
}
_ => {}
}
Как упоминал Джонас, вы можете использовать сопоставление шаблонов срезов для большей эргономики, а ночью вы можете комбинировать его с цепочками let, чтобы избежать вложенных if.
Тогда трудность заключается в том, как совместить работу с окнами с пропуском произвольного количества инструкций в случае успеха.
Простое решение — использовать индексацию вручную:
pub fn peephole_optimiser(instrs: &[LLStackInstruction]) -> Vec<LLStackInstruction> {
const WINDOW_SIZE: usize = 11;
type Instr = LLStackInstruction;
if instrs.len() < WINDOW_SIZE{
optimized.extend_from_slice(instrs);
return optimized;
}
let mut optimized = Vec::new();
let mut index = 0;
while index < instrs.len() - WINDOW_SIZE {
if let [
Instr::Push(Register::FP),
Instr::Mov(Condition::None, r1, ArgType::Imm(_)),
Instr::Push(r2),
Instr::Pop(_),
Instr::Pop(_),
Instr::Sub(..),
Instr::Branch(..),
Instr::Push(..),
Instr::Pop(..),
Instr::Pop(..),
Instr::Str(..)
] = &instrs[index..][..WINDOW_SIZE]
{
if r1 == r2 {
optimized.push(LLStackInstruction::Pop(Register::R4));
optimized.push(LLStackInstruction::Str(<>));
index += WINDOW_SIZE;
continue;
}
}
optimized.push(window[0].clone());
index += 1;
}
optimized
}
но это немного сложнее понять цель, и вы рискуете забыть увеличить индекс перед повторным запуском цикла... что приведет к бесконечному циклу.
slice
имеетwindows
функции для перебора окон, вfor
цикл, хотя для этого требуется пропуск вручную:
pub fn peephole_optimiser(instrs: &[LLStackInstruction]) -> Vec<LLStackInstruction> {
const WINDOW_SIZE: usize = 11;
type Instr = LLStackInstruction;
let mut optimized = Vec::new();
if instrs.len() < WINDOW_SIZE {
optimized.extend_from_slice(instrs);
return optimized;
}
let mut skip = 0;
for window in instrs.windows(WINDOW_SIZE) {
if skip > 0 {
skip -= 1;
continue;
}
if let [
Instr::Push(Register::FP),
Instr::Mov(Condition::None, r1, ArgType::Imm(_)),
Instr::Push(r2),
Instr::Pop(_),
Instr::Pop(_),
Instr::Sub(..),
Instr::Branch(..),
Instr::Push(..),
Instr::Pop(..),
Instr::Pop(..),
Instr::Str(..)
] = window
{
if r1 == r2 {
optimized.push(Instr::Pop(Register::R4));
optimized.push(Instr::Str(<>));
skip = 9;
continue;
}
}
optimized.push(window[0].clone());
}
optimized
}
Однако дополнительная переменная не очень хороша.
В конце концов, возможно, комбинацияslice::windows
с несколько более ручным режимом итерации — лучшее, чего мы можем достичь:
- Одна итерационная переменная, а не две разные.
- Без возможности случайно попасть в бесконечный цикл.
Я думаю, что это довольно читабельно, особенно если выделить совпадение в отдельную функцию:
pub fn peephole_optimiser(instrs: &[LLStackInstruction]) -> Vec<LLStackInstruction> {
const WINDOW_SIZE: usize = 11;
type Instr = LLStackInstruction;
fn matches(window: &[Instr]) -> bool {
assert_eq!(WINDOW_SIZE, window.len());
if let [
Instr::Push(Register::FP),
Instr::Mov(Condition::None, r1, ArgType::Imm(_)),
Instr::Push(r2),
Instr::Pop(_),
Instr::Pop(_),
Instr::Sub(..),
Instr::Branch(..),
Instr::Push(..),
Instr::Pop(..),
Instr::Str(..)
] = window
{
r1 == r2
} else {
false
}
}
let mut optimized = Vec::new();
let mut windows = instrs.windows(WINDOW_SIZE);
while let Some(window) = windows.next() {
if matches(window) {
optimized.push(Instr::Pop(Register::R4));
optimized.push(Instr::Str(<>);
windows.nth(WINDOW_SIZE - 2);
continue;
}
optimized.push(window[0].clone());
}
optimized
}
В ночное время есть функция цепочек if-let :
#![feature(let_chains)]
if let a = b && let c = d && e == f { ... }
В стабильной версии вы можете использоватьif_chain
ящик:
if_chain::if_chain! {
if let a = b;
if let c = d;
if e == f;
then { ... }
}