use rustc_hash::FxHashMap;
use sway_types::ident::Ident;
use crate::{
asm::{AsmArg, AsmBlock, AsmInstruction},
block::Block,
constant::Constant,
context::Context,
function::Function,
irtype::Type,
local_var::LocalVar,
pretty::DebugWithContext,
value::{Value, ValueDatum},
};
#[derive(Debug, Clone, DebugWithContext)]
pub struct BranchToWithArgs {
pub block: Block,
pub args: Vec<Value>,
}
#[derive(Debug, Clone, DebugWithContext)]
pub enum Instruction {
AsmBlock(AsmBlock, Vec<AsmArg>),
BinaryOp {
op: BinaryOpKind,
arg1: Value,
arg2: Value,
},
BitCast(Value, Type),
Branch(BranchToWithArgs),
Call(Function, Vec<Value>),
CastPtr(Value, Type),
Cmp(Predicate, Value, Value),
ConditionalBranch {
cond_value: Value,
true_block: BranchToWithArgs,
false_block: BranchToWithArgs,
},
ContractCall {
return_type: Type,
name: String,
params: Value,
coins: Value,
asset_id: Value,
gas: Value,
},
FuelVm(FuelVmInstruction),
GetLocal(LocalVar),
GetElemPtr {
base: Value,
elem_ptr_ty: Type,
indices: Vec<Value>,
},
IntToPtr(Value, Type),
Load(Value),
MemCopyBytes {
dst_val_ptr: Value,
src_val_ptr: Value,
byte_len: u64,
},
MemCopyVal {
dst_val_ptr: Value,
src_val_ptr: Value,
},
Nop,
PtrToInt(Value, Type),
Ret(Value, Type),
Store {
dst_val_ptr: Value,
stored_val: Value,
},
}
#[derive(Debug, Clone, DebugWithContext)]
pub enum FuelVmInstruction {
Gtf {
index: Value,
tx_field_id: u64,
},
Log {
log_val: Value,
log_ty: Type,
log_id: Value,
},
ReadRegister(Register),
Revert(Value),
Smo {
recipient_and_message: Value,
message_size: Value,
output_index: Value,
coins: Value,
},
StateClear {
key: Value,
number_of_slots: Value,
},
StateLoadQuadWord {
load_val: Value,
key: Value,
number_of_slots: Value,
},
StateLoadWord(Value),
StateStoreQuadWord {
stored_val: Value,
key: Value,
number_of_slots: Value,
},
StateStoreWord {
stored_val: Value,
key: Value,
},
}
#[derive(Debug, Clone, Copy)]
pub enum Predicate {
Equal,
LessThan,
GreaterThan,
}
#[derive(Debug, Clone, Copy)]
pub enum BinaryOpKind {
Add,
Sub,
Mul,
Div,
And,
Or,
Xor,
}
#[derive(Debug, Clone, Copy)]
pub enum Register {
Of,
Pc,
Ssp,
Sp,
Fp,
Hp,
Error,
Ggas,
Cgas,
Bal,
Is,
Ret,
Retl,
Flag,
}
impl Instruction {
pub fn get_type(&self, context: &Context) -> Option<Type> {
match self {
Instruction::AsmBlock(asm_block, _) => Some(asm_block.get_type(context)),
Instruction::BinaryOp { arg1, .. } => arg1.get_type(context),
Instruction::BitCast(_, ty) => Some(*ty),
Instruction::Call(function, _) => Some(context.functions[function.0].return_type),
Instruction::CastPtr(_val, ty) => Some(*ty),
Instruction::Cmp(..) => Some(Type::get_bool(context)),
Instruction::ContractCall { return_type, .. } => Some(*return_type),
Instruction::FuelVm(FuelVmInstruction::Gtf { .. }) => Some(Type::get_uint64(context)),
Instruction::FuelVm(FuelVmInstruction::Log { .. }) => Some(Type::get_unit(context)),
Instruction::FuelVm(FuelVmInstruction::ReadRegister(_)) => {
Some(Type::get_uint64(context))
}
Instruction::FuelVm(FuelVmInstruction::Smo { .. }) => Some(Type::get_unit(context)),
Instruction::Load(ptr_val) => match &context.values[ptr_val.0].value {
ValueDatum::Argument(arg) => arg.ty.get_pointee_type(context),
ValueDatum::Configurable(conf) => conf.ty.get_pointee_type(context),
ValueDatum::Constant(cons) => cons.ty.get_pointee_type(context),
ValueDatum::Instruction(ins) => ins
.get_type(context)
.and_then(|ty| ty.get_pointee_type(context)),
},
Instruction::GetElemPtr { elem_ptr_ty, .. } => Some(*elem_ptr_ty),
Instruction::GetLocal(local_var) => Some(local_var.get_type(context)),
Instruction::IntToPtr(_, ptr_ty) => Some(*ptr_ty),
Instruction::PtrToInt(_, int_ty) => Some(*int_ty),
Instruction::Branch(_)
| Instruction::ConditionalBranch { .. }
| Instruction::FuelVm(FuelVmInstruction::Revert(..))
| Instruction::Ret(..) => None,
Instruction::Nop => None,
Instruction::FuelVm(FuelVmInstruction::StateLoadWord(_)) => {
Some(Type::get_uint64(context))
}
Instruction::FuelVm(FuelVmInstruction::StateClear { .. })
| Instruction::FuelVm(FuelVmInstruction::StateLoadQuadWord { .. })
| Instruction::FuelVm(FuelVmInstruction::StateStoreQuadWord { .. })
| Instruction::FuelVm(FuelVmInstruction::StateStoreWord { .. }) => {
Some(Type::get_bool(context))
}
Instruction::MemCopyBytes { .. }
| Instruction::MemCopyVal { .. }
| Instruction::Store { .. } => Some(Type::get_unit(context)),
}
}
pub fn get_operands(&self) -> Vec<Value> {
match self {
Instruction::AsmBlock(_, args) => args.iter().filter_map(|aa| aa.initializer).collect(),
Instruction::BitCast(v, _) => vec![*v],
Instruction::BinaryOp { op: _, arg1, arg2 } => vec![*arg1, *arg2],
Instruction::Branch(BranchToWithArgs { args, .. }) => args.clone(),
Instruction::Call(_, vs) => vs.clone(),
Instruction::CastPtr(val, _ty) => vec![*val],
Instruction::Cmp(_, lhs, rhs) => vec![*lhs, *rhs],
Instruction::ConditionalBranch {
cond_value,
true_block,
false_block,
} => {
let mut v = vec![*cond_value];
v.extend_from_slice(&true_block.args);
v.extend_from_slice(&false_block.args);
v
}
Instruction::ContractCall {
return_type: _,
name: _,
params,
coins,
asset_id,
gas,
} => vec![*params, *coins, *asset_id, *gas],
Instruction::GetElemPtr {
base,
elem_ptr_ty: _,
indices,
} => {
let mut vals = indices.clone();
vals.push(*base);
vals
}
Instruction::GetLocal(_local_var) => {
vec![]
}
Instruction::IntToPtr(v, _) => vec![*v],
Instruction::Load(v) => vec![*v],
Instruction::MemCopyBytes {
dst_val_ptr,
src_val_ptr,
byte_len: _,
} => {
vec![*dst_val_ptr, *src_val_ptr]
}
Instruction::MemCopyVal {
dst_val_ptr,
src_val_ptr,
} => {
vec![*dst_val_ptr, *src_val_ptr]
}
Instruction::Nop => vec![],
Instruction::PtrToInt(v, _) => vec![*v],
Instruction::Ret(v, _) => vec![*v],
Instruction::Store {
dst_val_ptr,
stored_val,
} => {
vec![*dst_val_ptr, *stored_val]
}
Instruction::FuelVm(fuel_vm_instr) => match fuel_vm_instr {
FuelVmInstruction::Gtf {
index,
tx_field_id: _,
} => vec![*index],
FuelVmInstruction::Log {
log_val, log_id, ..
} => vec![*log_val, *log_id],
FuelVmInstruction::ReadRegister(_) => vec![],
FuelVmInstruction::Revert(v) => vec![*v],
FuelVmInstruction::Smo {
recipient_and_message,
message_size,
output_index,
coins,
} => vec![*recipient_and_message, *message_size, *output_index, *coins],
FuelVmInstruction::StateClear {
key,
number_of_slots,
} => vec![*key, *number_of_slots],
FuelVmInstruction::StateLoadQuadWord {
load_val,
key,
number_of_slots,
} => vec![*load_val, *key, *number_of_slots],
FuelVmInstruction::StateLoadWord(key) => vec![*key],
FuelVmInstruction::StateStoreQuadWord {
stored_val,
key,
number_of_slots,
} => {
vec![*stored_val, *key, *number_of_slots]
}
FuelVmInstruction::StateStoreWord { stored_val, key } => vec![*stored_val, *key],
},
}
}
pub fn replace_values(&mut self, replace_map: &FxHashMap<Value, Value>) {
let replace = |val: &mut Value| {
while let Some(new_val) = replace_map.get(val) {
*val = *new_val;
}
};
match self {
Instruction::AsmBlock(_, args) => args.iter_mut().for_each(|asm_arg| {
asm_arg
.initializer
.iter_mut()
.for_each(|init_val| replace(init_val))
}),
Instruction::BitCast(value, _) => replace(value),
Instruction::BinaryOp { op: _, arg1, arg2 } => {
replace(arg1);
replace(arg2);
}
Instruction::Branch(block) => {
block.args.iter_mut().for_each(replace);
}
Instruction::Call(_, args) => args.iter_mut().for_each(replace),
Instruction::CastPtr(val, _ty) => replace(val),
Instruction::Cmp(_, lhs_val, rhs_val) => {
replace(lhs_val);
replace(rhs_val);
}
Instruction::ConditionalBranch {
cond_value,
true_block,
false_block,
} => {
replace(cond_value);
true_block.args.iter_mut().for_each(replace);
false_block.args.iter_mut().for_each(replace);
}
Instruction::ContractCall {
params,
coins,
asset_id,
gas,
..
} => {
replace(params);
replace(coins);
replace(asset_id);
replace(gas);
}
Instruction::GetLocal(_) => (),
Instruction::GetElemPtr {
base,
elem_ptr_ty: _,
indices,
} => {
replace(base);
indices.iter_mut().for_each(replace);
}
Instruction::IntToPtr(value, _) => replace(value),
Instruction::Load(ptr) => replace(ptr),
Instruction::MemCopyBytes {
dst_val_ptr,
src_val_ptr,
..
} => {
replace(dst_val_ptr);
replace(src_val_ptr);
}
Instruction::MemCopyVal {
dst_val_ptr,
src_val_ptr,
} => {
replace(dst_val_ptr);
replace(src_val_ptr);
}
Instruction::Nop => (),
Instruction::PtrToInt(value, _) => replace(value),
Instruction::Ret(ret_val, _) => replace(ret_val),
Instruction::Store {
stored_val,
dst_val_ptr,
} => {
replace(stored_val);
replace(dst_val_ptr);
}
Instruction::FuelVm(fuel_vm_instr) => match fuel_vm_instr {
FuelVmInstruction::Gtf { index, .. } => replace(index),
FuelVmInstruction::Log {
log_val, log_id, ..
} => {
replace(log_val);
replace(log_id);
}
FuelVmInstruction::ReadRegister { .. } => (),
FuelVmInstruction::Revert(revert_val) => replace(revert_val),
FuelVmInstruction::Smo {
recipient_and_message,
message_size,
output_index,
coins,
} => {
replace(recipient_and_message);
replace(message_size);
replace(output_index);
replace(coins);
}
FuelVmInstruction::StateClear {
key,
number_of_slots,
} => {
replace(key);
replace(number_of_slots);
}
FuelVmInstruction::StateLoadQuadWord {
load_val,
key,
number_of_slots,
} => {
replace(load_val);
replace(key);
replace(number_of_slots);
}
FuelVmInstruction::StateLoadWord(key) => {
replace(key);
}
FuelVmInstruction::StateStoreQuadWord {
stored_val,
key,
number_of_slots,
} => {
replace(key);
replace(stored_val);
replace(number_of_slots);
}
FuelVmInstruction::StateStoreWord { stored_val, key } => {
replace(key);
replace(stored_val);
}
},
}
}
pub fn may_have_side_effect(&self) -> bool {
match self {
Instruction::AsmBlock(_, _)
| Instruction::Call(..)
| Instruction::ContractCall { .. }
| Instruction::FuelVm(FuelVmInstruction::Log { .. })
| Instruction::FuelVm(FuelVmInstruction::Smo { .. })
| Instruction::FuelVm(FuelVmInstruction::StateClear { .. })
| Instruction::FuelVm(FuelVmInstruction::StateLoadQuadWord { .. })
| Instruction::FuelVm(FuelVmInstruction::StateStoreQuadWord { .. })
| Instruction::FuelVm(FuelVmInstruction::StateStoreWord { .. })
| Instruction::MemCopyBytes { .. }
| Instruction::MemCopyVal { .. }
| Instruction::Store { .. } => true,
Instruction::BinaryOp { .. }
| Instruction::BitCast(..)
| Instruction::Branch(_)
| Instruction::CastPtr { .. }
| Instruction::Cmp(..)
| Instruction::ConditionalBranch { .. }
| Instruction::FuelVm(FuelVmInstruction::Gtf { .. })
| Instruction::FuelVm(FuelVmInstruction::ReadRegister(_))
| Instruction::FuelVm(FuelVmInstruction::Revert(..))
| Instruction::FuelVm(FuelVmInstruction::StateLoadWord(_))
| Instruction::GetElemPtr { .. }
| Instruction::GetLocal(_)
| Instruction::IntToPtr(..)
| Instruction::Load(_)
| Instruction::Nop
| Instruction::PtrToInt(..)
| Instruction::Ret(..) => false,
}
}
pub fn is_terminator(&self) -> bool {
matches!(
self,
Instruction::Branch(_)
| Instruction::ConditionalBranch { .. }
| Instruction::Ret(..)
| Instruction::FuelVm(FuelVmInstruction::Revert(..))
)
}
}
pub struct InstructionIterator {
instructions: Vec<generational_arena::Index>,
next: usize,
next_back: isize,
}
impl InstructionIterator {
pub fn new(context: &Context, block: &Block) -> Self {
InstructionIterator {
instructions: context.blocks[block.0]
.instructions
.iter()
.map(|val| val.0)
.collect(),
next: 0,
next_back: context.blocks[block.0].instructions.len() as isize - 1,
}
}
}
impl Iterator for InstructionIterator {
type Item = Value;
fn next(&mut self) -> Option<Value> {
if self.next < self.instructions.len() {
let idx = self.next;
self.next += 1;
Some(Value(self.instructions[idx]))
} else {
None
}
}
}
impl DoubleEndedIterator for InstructionIterator {
fn next_back(&mut self) -> Option<Value> {
if self.next_back >= 0 {
let idx = self.next_back;
self.next_back -= 1;
Some(Value(self.instructions[idx as usize]))
} else {
None
}
}
}
pub struct InstructionInserter<'a> {
context: &'a mut Context,
block: Block,
}
macro_rules! make_instruction {
($self: ident, $ctor: expr) => {{
let instruction_val = Value::new_instruction($self.context, $ctor);
$self.context.blocks[$self.block.0]
.instructions
.push(instruction_val);
instruction_val
}};
}
impl<'a> InstructionInserter<'a> {
pub fn new(context: &'a mut Context, block: Block) -> InstructionInserter<'a> {
InstructionInserter { context, block }
}
pub fn asm_block(
self,
args: Vec<AsmArg>,
body: Vec<AsmInstruction>,
return_type: Type,
return_name: Option<Ident>,
) -> Value {
let asm = AsmBlock::new(
self.context,
args.iter().map(|arg| arg.name.clone()).collect(),
body,
return_type,
return_name,
);
self.asm_block_from_asm(asm, args)
}
pub fn asm_block_from_asm(self, asm: AsmBlock, args: Vec<AsmArg>) -> Value {
make_instruction!(self, Instruction::AsmBlock(asm, args))
}
pub fn bitcast(self, value: Value, ty: Type) -> Value {
make_instruction!(self, Instruction::BitCast(value, ty))
}
pub fn binary_op(self, op: BinaryOpKind, arg1: Value, arg2: Value) -> Value {
make_instruction!(self, Instruction::BinaryOp { op, arg1, arg2 })
}
pub fn branch(self, to_block: Block, dest_params: Vec<Value>) -> Value {
let br_val = Value::new_instruction(
self.context,
Instruction::Branch(BranchToWithArgs {
block: to_block,
args: dest_params,
}),
);
to_block.add_pred(self.context, &self.block);
self.context.blocks[self.block.0].instructions.push(br_val);
br_val
}
pub fn call(self, function: Function, args: &[Value]) -> Value {
make_instruction!(self, Instruction::Call(function, args.to_vec()))
}
pub fn cast_ptr(self, val: Value, ty: Type) -> Value {
make_instruction!(self, Instruction::CastPtr(val, ty))
}
pub fn cmp(self, pred: Predicate, lhs_value: Value, rhs_value: Value) -> Value {
make_instruction!(self, Instruction::Cmp(pred, lhs_value, rhs_value))
}
pub fn conditional_branch(
self,
cond_value: Value,
true_block: Block,
false_block: Block,
true_dest_params: Vec<Value>,
false_dest_params: Vec<Value>,
) -> Value {
let cbr_val = Value::new_instruction(
self.context,
Instruction::ConditionalBranch {
cond_value,
true_block: BranchToWithArgs {
block: true_block,
args: true_dest_params,
},
false_block: BranchToWithArgs {
block: false_block,
args: false_dest_params,
},
},
);
true_block.add_pred(self.context, &self.block);
false_block.add_pred(self.context, &self.block);
self.context.blocks[self.block.0].instructions.push(cbr_val);
cbr_val
}
pub fn contract_call(
self,
return_type: Type,
name: String,
params: Value,
coins: Value, asset_id: Value, gas: Value, ) -> Value {
make_instruction!(
self,
Instruction::ContractCall {
return_type,
name,
params,
coins,
asset_id,
gas,
}
)
}
pub fn gtf(self, index: Value, tx_field_id: u64) -> Value {
make_instruction!(
self,
Instruction::FuelVm(FuelVmInstruction::Gtf { index, tx_field_id })
)
}
pub fn get_elem_ptr(self, base: Value, elem_ty: Type, indices: Vec<Value>) -> Value {
let elem_ptr_ty = Type::new_ptr(self.context, elem_ty);
make_instruction!(
self,
Instruction::GetElemPtr {
base,
elem_ptr_ty,
indices
}
)
}
pub fn get_elem_ptr_with_idx(self, base: Value, elem_ty: Type, index: u64) -> Value {
let idx_val = Constant::get_uint(self.context, 64, index);
self.get_elem_ptr(base, elem_ty, vec![idx_val])
}
pub fn get_elem_ptr_with_idcs(self, base: Value, elem_ty: Type, indices: &[u64]) -> Value {
let idx_vals = indices
.iter()
.map(|idx| Constant::get_uint(self.context, 64, *idx))
.collect();
self.get_elem_ptr(base, elem_ty, idx_vals)
}
pub fn get_local(self, local_var: LocalVar) -> Value {
make_instruction!(self, Instruction::GetLocal(local_var))
}
pub fn int_to_ptr(self, value: Value, ty: Type) -> Value {
make_instruction!(self, Instruction::IntToPtr(value, ty))
}
pub fn load(self, src_val: Value) -> Value {
make_instruction!(self, Instruction::Load(src_val))
}
pub fn log(self, log_val: Value, log_ty: Type, log_id: Value) -> Value {
make_instruction!(
self,
Instruction::FuelVm(FuelVmInstruction::Log {
log_val,
log_ty,
log_id
})
)
}
pub fn mem_copy_bytes(self, dst_val_ptr: Value, src_val_ptr: Value, byte_len: u64) -> Value {
make_instruction!(
self,
Instruction::MemCopyBytes {
dst_val_ptr,
src_val_ptr,
byte_len
}
)
}
pub fn mem_copy_val(self, dst_val_ptr: Value, src_val_ptr: Value) -> Value {
make_instruction!(
self,
Instruction::MemCopyVal {
dst_val_ptr,
src_val_ptr,
}
)
}
pub fn nop(self) -> Value {
make_instruction!(self, Instruction::Nop)
}
pub fn ptr_to_int(self, value: Value, ty: Type) -> Value {
make_instruction!(self, Instruction::PtrToInt(value, ty))
}
pub fn read_register(self, reg: Register) -> Value {
make_instruction!(
self,
Instruction::FuelVm(FuelVmInstruction::ReadRegister(reg))
)
}
pub fn ret(self, value: Value, ty: Type) -> Value {
make_instruction!(self, Instruction::Ret(value, ty))
}
pub fn revert(self, value: Value) -> Value {
let revert_val = Value::new_instruction(
self.context,
Instruction::FuelVm(FuelVmInstruction::Revert(value)),
);
self.context.blocks[self.block.0]
.instructions
.push(revert_val);
revert_val
}
pub fn smo(
self,
recipient_and_message: Value,
message_size: Value,
output_index: Value,
coins: Value,
) -> Value {
make_instruction!(
self,
Instruction::FuelVm(FuelVmInstruction::Smo {
recipient_and_message,
message_size,
output_index,
coins,
})
)
}
pub fn state_clear(self, key: Value, number_of_slots: Value) -> Value {
make_instruction!(
self,
Instruction::FuelVm(FuelVmInstruction::StateClear {
key,
number_of_slots
})
)
}
pub fn state_load_quad_word(
self,
load_val: Value,
key: Value,
number_of_slots: Value,
) -> Value {
make_instruction!(
self,
Instruction::FuelVm(FuelVmInstruction::StateLoadQuadWord {
load_val,
key,
number_of_slots
})
)
}
pub fn state_load_word(self, key: Value) -> Value {
make_instruction!(
self,
Instruction::FuelVm(FuelVmInstruction::StateLoadWord(key))
)
}
pub fn state_store_quad_word(
self,
stored_val: Value,
key: Value,
number_of_slots: Value,
) -> Value {
make_instruction!(
self,
Instruction::FuelVm(FuelVmInstruction::StateStoreQuadWord {
stored_val,
key,
number_of_slots
})
)
}
pub fn state_store_word(self, stored_val: Value, key: Value) -> Value {
make_instruction!(
self,
Instruction::FuelVm(FuelVmInstruction::StateStoreWord { stored_val, key })
)
}
pub fn store(self, dst_val_ptr: Value, stored_val: Value) -> Value {
make_instruction!(
self,
Instruction::Store {
dst_val_ptr,
stored_val,
}
)
}
}