mod expression_values;
mod reaching_values;
mod tests;
mod value;
use super::cfg::{ControlFlowGraph, Instr};
use crate::codegen::Expression;
use crate::sema::ast::{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::{is_single_constant, set_max_signed, set_max_unsigned, 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.into_iter() {
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,
encoded_args_len,
value,
gas,
salt,
..
} => {
*encoded_args = expression_reduce(encoded_args, &vars, ns);
*encoded_args_len = expression_reduce(encoded_args_len, &vars, ns);
if let Some(value) = value {
*value = expression_reduce(value, &vars, ns);
}
if let Some(salt) = salt {
*salt = expression_reduce(salt, &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::AbiDecode { data, .. } => {
*data = expression_reduce(data, &vars, ns);
}
Instr::EmitEvent { topics, data, .. } => {
*topics = topics
.iter()
.map(|e| expression_reduce(e, &vars, ns))
.collect();
*data = data
.iter()
.map(|e| expression_reduce(e, &vars, ns))
.collect();
}
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, unchecked, 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!(
"{} multiply optimized to shift left {}",
ty.to_string(ns),
shift
),
);
return Expression::ShiftLeft(
*loc,
ty.clone(),
left.clone(),
Box::new(Expression::NumberLiteral(*loc, ty.clone(), shift)),
);
}
cmp *= 2;
shift += 1;
}
}
if ty.is_signed_int() {
if let (Some(left_max), Some(right_max)) =
(set_max_signed(&left_values), set_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,
ty.clone(),
Box::new(Expression::Multiply(
*loc,
Type::Int(64),
*unchecked,
Box::new(left.as_ref().clone().cast(&Type::Int(64), ns)),
Box::new(right.as_ref().clone().cast(&Type::Int(64), ns)),
)),
);
}
}
} else {
let left_max = set_max_unsigned(&left_values);
let right_max = set_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,
ty.clone(),
Box::new(Expression::Multiply(
*loc,
Type::Uint(64),
*unchecked,
Box::new(left.as_ref().clone().cast(&Type::Uint(64), ns)),
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,
ty.clone(),
left.clone(),
Box::new(Expression::NumberLiteral(*loc, ty.clone(), shift)),
ty.is_signed_int(),
);
}
cmp *= 2;
shift += 1;
}
}
if ty.is_signed_int() {
if let (Some(left_max), Some(right_max)) =
(set_max_signed(&left_values), set_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,
ty.clone(),
Box::new(Expression::UnsignedDivide(
*loc,
Type::Int(64),
Box::new(left.as_ref().clone().cast(&Type::Int(64), ns)),
Box::new(right.as_ref().clone().cast(&Type::Int(64), ns)),
)),
);
}
}
} else {
let left_max = set_max_unsigned(&left_values);
let right_max = set_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,
ty.clone(),
Box::new(Expression::UnsignedDivide(
*loc,
Type::Uint(64),
Box::new(left.as_ref().clone().cast(&Type::Uint(64), ns)),
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,
ty.clone(),
left.clone(),
Box::new(Expression::NumberLiteral(*loc, ty.clone(), cmp - 1)),
);
}
cmp *= 2;
}
}
if ty.is_signed_int() {
if let (Some(left_max), Some(right_max)) =
(set_max_signed(&left_values), set_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,
ty.clone(),
Box::new(Expression::SignedModulo(
*loc,
Type::Int(64),
Box::new(left.as_ref().clone().cast(&Type::Int(64), ns)),
Box::new(right.as_ref().clone().cast(&Type::Int(64), ns)),
)),
);
}
}
} else {
let left_max = set_max_unsigned(&left_values);
let right_max = set_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,
ty.clone(),
Box::new(Expression::UnsignedModulo(
*loc,
Type::Uint(64),
Box::new(left.as_ref().clone().cast(&Type::Uint(64), ns)),
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 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
}