mod expression_values;
mod reaching_values;
mod tests;
mod value;
use super::cfg::{ControlFlowGraph, Instr};
use crate::codegen::Expression;
use crate::sema::ast::{ExternalCallAccounts, Namespace, Type};
use bitvec::prelude::*;
use expression_values::expression_values;
use num_bigint::{BigInt, Sign};
use num_traits::{One, ToPrimitive};
use reaching_values::{reaching_values, transfer};
use std::collections::{HashMap, HashSet};
use std::convert::TryInto;
use value::{get_max_signed, get_max_unsigned, is_single_constant, Value};
const MAX_VALUES: usize = 100;
pub fn strength_reduce(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
let mut block_vars = HashMap::new();
let mut vars = HashMap::new();
reaching_values(0, cfg, &mut vars, &mut block_vars, ns);
for (block_no, vars) in block_vars {
block_reduce(block_no, cfg, vars, ns);
}
}
fn block_reduce(
block_no: usize,
cfg: &mut ControlFlowGraph,
mut vars: Variables,
ns: &mut Namespace,
) {
for instr in &mut cfg.blocks[block_no].instr {
match instr {
Instr::Set { expr, .. } => {
*expr = expression_reduce(expr, &vars, ns);
}
Instr::Call { args, .. } => {
*args = args
.iter()
.map(|e| expression_reduce(e, &vars, ns))
.collect();
}
Instr::Return { value } => {
*value = value
.iter()
.map(|e| expression_reduce(e, &vars, ns))
.collect();
}
Instr::Store { dest, data } => {
*dest = expression_reduce(dest, &vars, ns);
*data = expression_reduce(data, &vars, ns);
}
Instr::AssertFailure {
encoded_args: Some(expr),
} => {
*expr = expression_reduce(expr, &vars, ns);
}
Instr::Print { expr } => {
*expr = expression_reduce(expr, &vars, ns);
}
Instr::ClearStorage { storage, .. } => {
*storage = expression_reduce(storage, &vars, ns);
}
Instr::SetStorage { storage, value, .. } => {
*value = expression_reduce(value, &vars, ns);
*storage = expression_reduce(storage, &vars, ns);
}
Instr::SetStorageBytes {
storage,
value,
offset,
..
} => {
*value = expression_reduce(value, &vars, ns);
*storage = expression_reduce(storage, &vars, ns);
*offset = expression_reduce(offset, &vars, ns);
}
Instr::PushStorage { storage, value, .. } => {
if let Some(value) = value {
*value = expression_reduce(value, &vars, ns);
}
*storage = expression_reduce(storage, &vars, ns);
}
Instr::PopStorage { storage, .. } => {
*storage = expression_reduce(storage, &vars, ns);
}
Instr::PushMemory { value, .. } => {
*value = Box::new(expression_reduce(value, &vars, ns));
}
Instr::Constructor {
encoded_args,
value,
gas,
salt,
accounts,
..
} => {
*encoded_args = expression_reduce(encoded_args, &vars, ns);
if let Some(value) = value {
*value = expression_reduce(value, &vars, ns);
}
if let Some(salt) = salt {
*salt = expression_reduce(salt, &vars, ns);
}
if let ExternalCallAccounts::Present(accounts) = accounts {
*accounts = expression_reduce(accounts, &vars, ns);
}
*gas = expression_reduce(gas, &vars, ns);
}
Instr::ExternalCall {
address,
payload,
value,
gas,
..
} => {
*value = expression_reduce(value, &vars, ns);
if let Some(address) = address {
*address = expression_reduce(address, &vars, ns);
}
*payload = expression_reduce(payload, &vars, ns);
*gas = expression_reduce(gas, &vars, ns);
}
Instr::ValueTransfer { address, value, .. } => {
*address = expression_reduce(address, &vars, ns);
*value = expression_reduce(value, &vars, ns);
}
Instr::EmitEvent { topics, data, .. } => {
*topics = topics
.iter()
.map(|e| expression_reduce(e, &vars, ns))
.collect();
*data = expression_reduce(data, &vars, ns);
}
Instr::WriteBuffer { offset, .. } => {
*offset = expression_reduce(offset, &vars, ns);
}
_ => (),
}
transfer(instr, &mut vars, ns);
}
}
fn expression_reduce(expr: &Expression, vars: &Variables, ns: &mut Namespace) -> Expression {
let filter = |expr: &Expression, ns: &mut Namespace| -> Expression {
match expr {
Expression::Multiply {
loc,
ty,
overflowing,
left,
right,
} => {
let bits = ty.bits(ns) as usize;
if bits >= 128 {
let left_values = expression_values(left, vars, ns);
let right_values = expression_values(right, vars, ns);
match is_single_constant(&right_values) {
Some(right) if *overflowing => {
let mut shift = BigInt::one();
let mut cmp = BigInt::from(2);
for _ in 1..bits {
if cmp == right {
ns.hover_overrides.insert(
*loc,
format!(
"{} multiply optimized to shift left {}",
ty.to_string(ns),
shift
),
);
return Expression::ShiftLeft {
loc: *loc,
ty: ty.clone(),
left: left.clone(),
right: Box::new(Expression::NumberLiteral {
loc: *loc,
ty: ty.clone(),
value: shift,
}),
};
}
cmp *= 2;
shift += 1;
}
}
_ => (), }
if ty.is_signed_int(ns) {
if let (Some(left_max), Some(right_max)) =
(get_max_signed(&left_values), get_max_signed(&right_values))
{
if (left_max * right_max).to_i64().is_some() {
ns.hover_overrides.insert(
*loc,
format!(
"{} multiply optimized to int64 multiply",
ty.to_string(ns),
),
);
return Expression::SignExt {
loc: *loc,
ty: ty.clone(),
expr: Box::new(Expression::Multiply {
loc: *loc,
ty: Type::Int(64),
overflowing: *overflowing,
left: Box::new(
left.as_ref().clone().cast(&Type::Int(64), ns),
),
right: Box::new(
right.as_ref().clone().cast(&Type::Int(64), ns),
),
}),
};
}
}
} else {
let left_max = get_max_unsigned(&left_values);
let right_max = get_max_unsigned(&right_values);
if left_max * right_max <= BigInt::from(u64::MAX) {
ns.hover_overrides.insert(
*loc,
format!(
"{} multiply optimized to uint64 multiply",
ty.to_string(ns),
),
);
return Expression::ZeroExt {
loc: *loc,
ty: ty.clone(),
expr: Box::new(Expression::Multiply {
loc: *loc,
ty: Type::Uint(64),
overflowing: *overflowing,
left: Box::new(left.as_ref().clone().cast(&Type::Uint(64), ns)),
right: Box::new(
right.as_ref().clone().cast(&Type::Uint(64), ns),
),
}),
};
}
}
}
expr.clone()
}
Expression::UnsignedDivide {
loc,
ty,
left,
right,
}
| Expression::SignedDivide {
loc,
ty,
left,
right,
} => {
let bits = ty.bits(ns) as usize;
if bits >= 128 {
let left_values = expression_values(left, vars, ns);
let right_values = expression_values(right, vars, ns);
if let Some(right) = is_single_constant(&right_values) {
let mut shift = BigInt::one();
let mut cmp = BigInt::from(2);
for _ in 1..bits {
if cmp == right {
ns.hover_overrides.insert(
*loc,
format!(
"{} divide optimized to shift right {}",
ty.to_string(ns),
shift
),
);
return Expression::ShiftRight {
loc: *loc,
ty: ty.clone(),
left: left.clone(),
right: Box::new(Expression::NumberLiteral {
loc: *loc,
ty: ty.clone(),
value: shift,
}),
signed: ty.is_signed_int(ns),
};
}
cmp *= 2;
shift += 1;
}
}
if ty.is_signed_int(ns) {
if let (Some(left_max), Some(right_max)) =
(get_max_signed(&left_values), get_max_signed(&right_values))
{
if left_max.to_i64().is_some() && right_max.to_i64().is_some() {
ns.hover_overrides.insert(
*loc,
format!(
"{} divide optimized to int64 divide",
ty.to_string(ns),
),
);
return Expression::SignExt {
loc: *loc,
ty: ty.clone(),
expr: Box::new(Expression::UnsignedDivide {
loc: *loc,
ty: Type::Int(64),
left: Box::new(
left.as_ref().clone().cast(&Type::Int(64), ns),
),
right: Box::new(
right.as_ref().clone().cast(&Type::Int(64), ns),
),
}),
};
}
}
} else {
let left_max = get_max_unsigned(&left_values);
let right_max = get_max_unsigned(&right_values);
if left_max.to_u64().is_some() && right_max.to_u64().is_some() {
ns.hover_overrides.insert(
*loc,
format!("{} divide optimized to uint64 divide", ty.to_string(ns),),
);
return Expression::ZeroExt {
loc: *loc,
ty: ty.clone(),
expr: Box::new(Expression::UnsignedDivide {
loc: *loc,
ty: Type::Uint(64),
left: Box::new(left.as_ref().clone().cast(&Type::Uint(64), ns)),
right: Box::new(
right.as_ref().clone().cast(&Type::Uint(64), ns),
),
}),
};
}
}
}
expr.clone()
}
Expression::SignedModulo {
loc,
ty,
left,
right,
}
| Expression::UnsignedModulo {
loc,
ty,
left,
right,
} => {
let bits = ty.bits(ns) as usize;
if bits >= 128 {
let left_values = expression_values(left, vars, ns);
let right_values = expression_values(right, vars, ns);
if let Some(right) = is_single_constant(&right_values) {
let mut cmp = BigInt::one();
for _ in 1..bits {
if cmp == right {
ns.hover_overrides.insert(
*loc,
format!(
"{} modulo optimized to bitwise and {}",
ty.to_string(ns),
cmp.clone() - 1
),
);
return Expression::BitwiseAnd {
loc: *loc,
ty: ty.clone(),
left: left.clone(),
right: Box::new(Expression::NumberLiteral {
loc: *loc,
ty: ty.clone(),
value: cmp - 1,
}),
};
}
cmp *= 2;
}
}
if ty.is_signed_int(ns) {
if let (Some(left_max), Some(right_max)) =
(get_max_signed(&left_values), get_max_signed(&right_values))
{
if left_max.to_i64().is_some() && right_max.to_i64().is_some() {
ns.hover_overrides.insert(
*loc,
format!(
"{} modulo optimized to int64 modulo",
ty.to_string(ns),
),
);
return Expression::SignExt {
loc: *loc,
ty: ty.clone(),
expr: Box::new(Expression::SignedModulo {
loc: *loc,
ty: Type::Int(64),
left: Box::new(
left.as_ref().clone().cast(&Type::Int(64), ns),
),
right: Box::new(
right.as_ref().clone().cast(&Type::Int(64), ns),
),
}),
};
}
}
} else {
let left_max = get_max_unsigned(&left_values);
let right_max = get_max_unsigned(&right_values);
if left_max.to_u64().is_some() && right_max.to_u64().is_some() {
ns.hover_overrides.insert(
*loc,
format!("{} modulo optimized to uint64 modulo", ty.to_string(ns)),
);
return Expression::ZeroExt {
loc: *loc,
ty: ty.clone(),
expr: Box::new(Expression::UnsignedModulo {
loc: *loc,
ty: Type::Uint(64),
left: Box::new(left.as_ref().clone().cast(&Type::Uint(64), ns)),
right: Box::new(
right.as_ref().clone().cast(&Type::Uint(64), ns),
),
}),
};
}
}
}
expr.clone()
}
_ => expr.clone(),
}
};
expr.copy_filter(ns, filter)
}
fn track(ty: &Type) -> bool {
matches!(
ty,
Type::Uint(_) | Type::Int(_) | Type::Bool | Type::Value | Type::UserType(_)
)
}
type Variables = HashMap<usize, HashSet<Value>>;
type Bits = BitArray<[u8; 32], Lsb0>;
fn highest_set_bit(bs: &[u8]) -> usize {
for (i, b) in bs.iter().enumerate().rev() {
if *b != 0 {
return (i + 1) * 8 - bs[i].leading_zeros() as usize - 1;
}
}
0
}
fn bigint_to_bitarr(v: &BigInt, bits: usize) -> BitArray<[u8; 32], Lsb0> {
let mut bs = v.to_signed_bytes_le();
bs.resize(
32,
if v.sign() == Sign::Minus {
u8::MAX
} else {
u8::MIN
},
);
let mut ba = BitArray::new(bs.try_into().unwrap());
if bits < 256 {
ba[bits..256].fill(false);
}
ba
}