Rematerialize constants in backend codegen.

This commit is contained in:
Chris Fallin 2023-03-01 17:23:09 -08:00
parent 2d5d32750d
commit ce333b3070
3 changed files with 44 additions and 15 deletions

View file

@ -74,7 +74,7 @@ impl<'a, V: Visitor> BlockVisitor<'a, V> {
self.visitor.pre_term(); self.visitor.pre_term();
for &inst in self.body.blocks[block].insts.iter().rev() { for &inst in self.body.blocks[block].insts.iter().rev() {
if self.trees.owner.contains_key(&inst) { if self.trees.owner.contains_key(&inst) || self.trees.remat.contains(&inst) {
continue; continue;
} }
self.visitor.post_inst(inst); self.visitor.post_inst(inst);

View file

@ -142,7 +142,7 @@ impl<'a> WasmFuncBackend<'a> {
for &inst in &self.body.blocks[*block].insts { for &inst in &self.body.blocks[*block].insts {
// If this value is "owned", do nothing: it will be lowered in // If this value is "owned", do nothing: it will be lowered in
// the one place it's used. // the one place it's used.
if self.trees.owner.contains_key(&inst) { if self.trees.owner.contains_key(&inst) || self.trees.remat.contains(&inst) {
continue; continue;
} }
if let &ValueDef::Operator(..) = &self.body.values[inst] { if let &ValueDef::Operator(..) = &self.body.values[inst] {
@ -180,6 +180,9 @@ impl<'a> WasmFuncBackend<'a> {
fn lower_value(&self, value: Value, func: &mut wasm_encoder::Function) { fn lower_value(&self, value: Value, func: &mut wasm_encoder::Function) {
log::trace!("lower_value: value {}", value); log::trace!("lower_value: value {}", value);
let value = self.body.resolve_alias(value); let value = self.body.resolve_alias(value);
if self.trees.remat.contains(&value) {
self.lower_inst(value, /* root = */ false, func);
} else {
let local = match &self.body.values[value] { let local = match &self.body.values[value] {
&ValueDef::BlockParam(..) | &ValueDef::Operator(..) => self.locals.values[value][0], &ValueDef::BlockParam(..) | &ValueDef::Operator(..) => self.locals.values[value][0],
&ValueDef::PickOutput(orig_value, idx, _) => self.locals.values[orig_value][idx], &ValueDef::PickOutput(orig_value, idx, _) => self.locals.values[orig_value][idx],
@ -187,6 +190,7 @@ impl<'a> WasmFuncBackend<'a> {
}; };
func.instruction(&wasm_encoder::Instruction::LocalGet(local.index() as u32)); func.instruction(&wasm_encoder::Instruction::LocalGet(local.index() as u32));
} }
}
fn lower_set_value(&self, value: Value, func: &mut wasm_encoder::Function) { fn lower_set_value(&self, value: Value, func: &mut wasm_encoder::Function) {
debug_assert_eq!( debug_assert_eq!(
@ -205,7 +209,7 @@ impl<'a> WasmFuncBackend<'a> {
&ValueDef::Operator(ref op, ref args, ref tys) => { &ValueDef::Operator(ref op, ref args, ref tys) => {
for &arg in &args[..] { for &arg in &args[..] {
let arg = self.body.resolve_alias(arg); let arg = self.body.resolve_alias(arg);
if self.trees.owner.contains_key(&arg) { if self.trees.owner.contains_key(&arg) || self.trees.remat.contains(&arg) {
log::trace!(" -> arg {} is owned", arg); log::trace!(" -> arg {} is owned", arg);
self.lower_inst(arg, /* root = */ false, func); self.lower_inst(arg, /* root = */ false, func);
} else { } else {

View file

@ -4,7 +4,7 @@
use crate::entity::EntityRef; use crate::entity::EntityRef;
use crate::ir::{FunctionBody, Value, ValueDef}; use crate::ir::{FunctionBody, Value, ValueDef};
use crate::Operator; use crate::Operator;
use std::collections::{HashMap, HashSet}; use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};
use std::convert::TryFrom; use std::convert::TryFrom;
/// One "argument slot" of an operator defining a value. /// One "argument slot" of an operator defining a value.
@ -19,22 +19,43 @@ pub struct Trees {
/// For a given value that is defined by an operator, which /// For a given value that is defined by an operator, which
/// Values, if any, live at each slot? /// Values, if any, live at each slot?
pub owned: HashMap<ValueArg, Value>, pub owned: HashMap<ValueArg, Value>,
/// Values that are regenerated every time they are used.
pub remat: HashSet<Value>,
}
fn is_remat(op: &Operator) -> bool {
// Only ops with no args can be always-rematerialized.
match op {
Operator::I32Const { .. }
| Operator::I64Const { .. }
| Operator::F32Const { .. }
| Operator::F64Const { .. } => true,
_ => false,
}
} }
impl Trees { impl Trees {
pub fn compute(body: &FunctionBody) -> Trees { pub fn compute(body: &FunctionBody) -> Trees {
let mut owner = HashMap::new(); let mut owner = HashMap::default();
let mut owned = HashMap::new(); let mut owned = HashMap::default();
let mut multi_use = HashSet::new(); let mut remat = HashSet::default();
let mut multi_use = HashSet::default();
for (value, def) in body.values.entries() { for (value, def) in body.values.entries() {
match def { match def {
&ValueDef::Operator(_, ref args, _) => { &ValueDef::Operator(op, ref args, _) => {
// Ignore operators with invalid args: these must // Ignore operators with invalid args: these must
// always be unreachable. // always be unreachable.
if args.iter().any(|arg| arg.is_invalid()) { if args.iter().any(|arg| arg.is_invalid()) {
continue; continue;
} }
// If this is an always-rematerialized operator,
// mark it as such and continue.
if is_remat(&op) {
remat.insert(value);
continue;
}
// For each of the args, if the value is produced // For each of the args, if the value is produced
// by a single-output op and is movable, and is // by a single-output op and is movable, and is
// not already recorded in `multi_use`, place it // not already recorded in `multi_use`, place it
@ -75,7 +96,11 @@ impl Trees {
}); });
} }
Trees { owner, owned } Trees {
owner,
owned,
remat,
}
} }
fn is_single_output_op(body: &FunctionBody, value: Value) -> Option<Operator> { fn is_single_output_op(body: &FunctionBody, value: Value) -> Option<Operator> {