use super::*;
pub(crate) fn expr_for_reg_use(
lowering: &ProtoLowering<'_>,
block: BlockRef,
instr_ref: InstrRef,
reg: Reg,
) -> HirExpr {
if let Some(local) = loop_local_for_reg(lowering, block, reg) {
return HirExpr::LocalRef(local);
}
let Some(values) = lowering.dataflow.use_values_at(instr_ref).get(reg) else {
return expr_for_entry_reg(lowering, reg);
};
if values.is_empty() {
return expr_for_entry_reg(lowering, reg);
}
if values.len() == 1 {
let value = values
.iter()
.next()
.expect("len checked above, exactly one SSA-like value exists");
return match value {
SsaValue::Def(def) => lowering
.bindings
.expr_for_temp(lowering.bindings.fixed_temps[def.index()]),
SsaValue::Phi(phi) => lowering
.bindings
.expr_for_temp(lowering.bindings.phi_temps[phi.index()]),
};
}
unresolved_expr(format!(
"multi-value use r{} @{}",
reg.index(),
instr_ref.index()
))
}
pub(crate) fn expr_for_closure_capture(
lowering: &ProtoLowering<'_>,
block: BlockRef,
instr_ref: InstrRef,
dst: Reg,
source: crate::transformer::CaptureSource,
) -> HirExpr {
match source {
crate::transformer::CaptureSource::Reg(reg) if reg == dst => {
let self_temp = lowering.bindings.instr_fixed_defs[instr_ref.index()]
.first()
.copied()
.expect("closure writes exactly one fixed target");
lowering.bindings.expr_for_temp(self_temp)
}
crate::transformer::CaptureSource::Reg(reg) => {
if let Some(target) = lowering.bindings.closure_capture_target(instr_ref, reg) {
return target.expr();
}
let expr = expr_for_reg_use(lowering, block, instr_ref, reg);
let entry_empty = reg_use_is_entry_empty(lowering, instr_ref, reg);
let should_forward = match &expr {
_ if entry_empty => true,
HirExpr::TempRef(_) => is_loadnil_def(lowering, instr_ref, reg),
_ => false,
};
if should_forward
&& let Some(forward_expr) = forward_def_in_block(lowering, block, instr_ref, reg)
{
return forward_expr;
}
expr
}
crate::transformer::CaptureSource::Upvalue(upvalue) => {
HirExpr::UpvalueRef(UpvalueId(upvalue.index()))
}
}
}
fn reg_use_is_entry_empty(lowering: &ProtoLowering<'_>, instr_ref: InstrRef, reg: Reg) -> bool {
lowering
.dataflow
.use_values_at(instr_ref)
.get(reg)
.is_none_or(|values| values.is_empty())
&& reg.index() >= lowering.bindings.params.len()
&& !lowering.bindings.entry_local_regs.contains_key(®)
}
fn is_loadnil_def(lowering: &ProtoLowering<'_>, instr_ref: InstrRef, reg: Reg) -> bool {
let Some(values) = lowering.dataflow.use_values_at(instr_ref).get(reg) else {
return false;
};
if values.len() != 1 {
return false;
}
let value = values.iter().next().unwrap();
let SsaValue::Def(def) = value else {
return false;
};
let def_instr = lowering.dataflow.def_instr(def);
matches!(
&lowering.proto.instrs[def_instr.index()],
crate::transformer::LowInstr::LoadNil(_)
)
}
fn forward_def_in_block(
lowering: &ProtoLowering<'_>,
block: BlockRef,
instr_ref: InstrRef,
reg: Reg,
) -> Option<HirExpr> {
let block_range = lowering.cfg.blocks[block.index()].instrs;
let mut last_def_temp = None;
for idx in (instr_ref.index() + 1)..block_range.end() {
for def in &lowering.dataflow.instr_defs[idx] {
if lowering.dataflow.def_reg(*def) == reg {
last_def_temp = Some(lowering.bindings.fixed_temps[def.index()]);
}
}
}
last_def_temp.map(|temp| lowering.bindings.expr_for_temp(temp))
}
pub(crate) fn expr_for_reg_at_block_entry(
lowering: &ProtoLowering<'_>,
block: BlockRef,
reg: Reg,
) -> HirExpr {
if let Some(local) = loop_local_for_reg(lowering, block, reg) {
return HirExpr::LocalRef(local);
}
let range = lowering.cfg.blocks[block.index()].instrs;
if range.is_empty() {
return expr_for_entry_reg(lowering, reg);
}
let Some(values) = lowering.dataflow.reaching_values_at(range.start).get(reg) else {
return expr_for_entry_reg(lowering, reg);
};
if values.is_empty() {
return expr_for_entry_reg(lowering, reg);
}
if values.len() == 1 {
let value = values
.iter()
.next()
.expect("len checked above, exactly one SSA-like value exists");
return match value {
SsaValue::Def(def) => lowering
.bindings
.expr_for_temp(lowering.bindings.fixed_temps[def.index()]),
SsaValue::Phi(phi) => lowering
.bindings
.expr_for_temp(lowering.bindings.phi_temps[phi.index()]),
};
}
unresolved_expr(format!(
"multi-value entry r{} block#{}",
reg.index(),
block.index()
))
}
pub(crate) fn expr_for_reg_at_block_exit(
lowering: &ProtoLowering<'_>,
block: BlockRef,
reg: Reg,
) -> HirExpr {
if let Some(local) = loop_local_for_reg(lowering, block, reg) {
return HirExpr::LocalRef(local);
}
let range = lowering.cfg.blocks[block.index()].instrs;
let Some(last_instr_ref) = range.last() else {
return expr_for_entry_reg(lowering, reg);
};
let effect = &lowering.dataflow.instr_effects[last_instr_ref.index()];
if effect.fixed_must_defs.contains(®) {
let Some(def) = fixed_def_for_reg(lowering, last_instr_ref, reg) else {
return unresolved_expr(format!(
"missing block-exit def r{} block#{}",
reg.index(),
block.index()
));
};
return lowering
.bindings
.expr_for_temp(lowering.bindings.fixed_temps[def.index()]);
}
let mut values = lowering
.dataflow
.reaching_values_at(last_instr_ref)
.get(reg)
.map(|values| values.to_compact_set())
.unwrap_or_default();
if effect.fixed_may_defs.contains(®) {
let Some(def) = fixed_def_for_reg(lowering, last_instr_ref, reg) else {
return unresolved_expr(format!(
"missing block-exit may-def r{} block#{}",
reg.index(),
block.index()
));
};
values.insert(SsaValue::Def(def));
}
if values.is_empty() {
return expr_for_entry_reg(lowering, reg);
}
if values.len() == 1 {
let value = values
.iter()
.next()
.expect("len checked above, exactly one SSA-like value exists");
return match value {
SsaValue::Def(def) => lowering
.bindings
.expr_for_temp(lowering.bindings.fixed_temps[def.index()]),
SsaValue::Phi(phi) => lowering
.bindings
.expr_for_temp(lowering.bindings.phi_temps[phi.index()]),
};
}
unresolved_expr(format!(
"multi-value exit r{} block#{}",
reg.index(),
block.index()
))
}
pub(crate) fn expr_for_reg_use_inline(
lowering: &ProtoLowering<'_>,
block: BlockRef,
instr_ref: InstrRef,
reg: Reg,
) -> HirExpr {
if let Some(local) = loop_local_for_reg(lowering, block, reg) {
return HirExpr::LocalRef(local);
}
let Some(values) = lowering.dataflow.use_values_at(instr_ref).get(reg) else {
return expr_for_entry_reg(lowering, reg);
};
if values.is_empty() {
return expr_for_entry_reg(lowering, reg);
}
if values.len() == 1 {
let value = values
.iter()
.next()
.expect("len checked above, exactly one SSA-like value exists");
return match value {
SsaValue::Def(def) => expr_for_dup_safe_fixed_def(lowering, def).unwrap_or_else(|| {
lowering
.bindings
.expr_for_temp(lowering.bindings.fixed_temps[def.index()])
}),
SsaValue::Phi(phi) => lowering
.bindings
.expr_for_temp(lowering.bindings.phi_temps[phi.index()]),
};
}
unresolved_expr(format!(
"multi-value use r{} @{}",
reg.index(),
instr_ref.index()
))
}
pub(crate) fn expr_for_reg_use_single_eval_with_call_policy(
lowering: &ProtoLowering<'_>,
block: BlockRef,
instr_ref: InstrRef,
reg: Reg,
allow_call_consumed_by_pure_wrapper: bool,
) -> HirExpr {
if let Some(local) = loop_local_for_reg(lowering, block, reg) {
return HirExpr::LocalRef(local);
}
let Some(values) = lowering.dataflow.use_values_at(instr_ref).get(reg) else {
return expr_for_entry_reg(lowering, reg);
};
if values.is_empty() {
return expr_for_entry_reg(lowering, reg);
}
if values.len() == 1 {
let value = values
.iter()
.next()
.expect("len checked above, exactly one SSA-like value exists");
return match value {
SsaValue::Def(def) => {
if lowering.dataflow.def_block(def) != block {
return lowering
.bindings
.expr_for_temp(lowering.bindings.fixed_temps[def.index()]);
}
if def_has_intervening_use(lowering, def, instr_ref) {
return lowering
.bindings
.expr_for_temp(lowering.bindings.fixed_temps[def.index()]);
}
if def_is_call_consumed_by_non_branch(lowering, def, instr_ref)
&& (!allow_call_consumed_by_pure_wrapper
|| def_has_later_use_after_pure_wrapper(lowering, def, instr_ref))
{
return lowering
.bindings
.expr_for_temp(lowering.bindings.fixed_temps[def.index()]);
}
expr_for_fixed_def_single_eval(lowering, def).unwrap_or_else(|| {
lowering
.bindings
.expr_for_temp(lowering.bindings.fixed_temps[def.index()])
})
}
SsaValue::Phi(phi) => lowering
.bindings
.expr_for_temp(lowering.bindings.phi_temps[phi.index()]),
};
}
unresolved_expr(format!(
"multi-value use r{} @{}",
reg.index(),
instr_ref.index()
))
}
fn def_is_call_consumed_by_non_branch(
lowering: &ProtoLowering<'_>,
def: DefId,
consumer_instr: InstrRef,
) -> bool {
let def_instr = lowering.dataflow.def_instr(def);
matches!(lowering.proto.instrs[def_instr.index()], LowInstr::Call(_))
&& !matches!(
lowering.proto.instrs[consumer_instr.index()],
LowInstr::Branch(_)
)
}
fn def_has_later_use_after_pure_wrapper(
lowering: &ProtoLowering<'_>,
def: DefId,
wrapper_instr: InstrRef,
) -> bool {
let def_reg = lowering.dataflow.def_reg(def);
let def_block = lowering.dataflow.def_block(def);
if lowering.cfg.instr_to_block.get(wrapper_instr.index()) != Some(&def_block) {
return true;
}
let range = lowering.cfg.blocks[def_block.index()].instrs;
for instr_index in (wrapper_instr.index() + 1)..range.end() {
let effect = &lowering.dataflow.instr_effects[instr_index];
if effect.fixed_uses.contains(&def_reg)
&& !matches!(lowering.proto.instrs[instr_index], LowInstr::Branch(_))
{
return true;
}
if effect.fixed_must_defs.contains(&def_reg) {
return false;
}
}
false
}
fn def_has_intervening_use(
lowering: &ProtoLowering<'_>,
def: DefId,
consumer_instr: InstrRef,
) -> bool {
let def_instr = lowering.dataflow.def_instr(def);
if def_instr.index() >= consumer_instr.index() {
return false;
}
let effect = &lowering.dataflow.instr_effects[def_instr.index()];
effect.fixed_must_defs.iter().any(|reg| {
((def_instr.index() + 1)..consumer_instr.index()).any(|instr_index| {
lowering.dataflow.instr_effects[instr_index]
.fixed_uses
.contains(reg)
})
})
}