Rematerialize constants in backend codegen.
This commit is contained in:
parent
2d5d32750d
commit
ce333b3070
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
Loading…
Reference in a new issue