use super::instruction_set::InstructionSet;
use super::ToMidenBytecode;
use super::{
fuel::{checks, data_section::DataSection},
ProgramABI, ProgramKind,
};
use crate::asm_lang::allocated_ops::{AllocatedOp, AllocatedOpcode};
use crate::decl_engine::DeclRefFunction;
use crate::source_map::SourceMap;
use crate::BuildConfig;
use etk_asm::asm::Assembler;
use sway_error::error::CompileError;
use sway_error::handler::{ErrorEmitted, Handler};
use sway_types::span::Span;
use sway_types::SourceEngine;
use either::Either;
use std::{collections::BTreeMap, fmt};
#[derive(Clone)]
pub struct FinalizedAsm {
pub data_section: DataSection,
pub program_section: InstructionSet,
pub program_kind: ProgramKind,
pub entries: Vec<FinalizedEntry>,
pub abi: Option<ProgramABI>,
}
#[derive(Clone, Debug)]
pub struct FinalizedEntry {
pub fn_name: String,
pub imm: u64,
pub selector: Option<[u8; 4]>,
pub test_decl_ref: Option<DeclRefFunction>,
}
pub struct CompiledBytecode {
pub bytecode: Vec<u8>,
pub config_const_offsets: BTreeMap<String, u64>,
}
impl FinalizedAsm {
pub(crate) fn to_bytecode_mut(
&mut self,
handler: &Handler,
source_map: &mut SourceMap,
source_engine: &SourceEngine,
build_config: &BuildConfig,
) -> Result<CompiledBytecode, ErrorEmitted> {
match &self.program_section {
InstructionSet::Fuel { ops } => Ok(to_bytecode_mut(
ops,
&mut self.data_section,
source_map,
source_engine,
build_config,
)),
InstructionSet::Evm { ops } => {
let mut assembler = Assembler::new();
if let Err(e) = assembler.push_all(ops.clone()) {
Err(handler.emit_err(CompileError::InternalOwned(e.to_string(), Span::dummy())))
} else {
Ok(CompiledBytecode {
bytecode: assembler.take(),
config_const_offsets: BTreeMap::new(),
})
}
}
InstructionSet::MidenVM { ops } => Ok(CompiledBytecode {
bytecode: ops.to_bytecode().into(),
config_const_offsets: Default::default(),
}),
}
}
}
impl FinalizedEntry {
pub fn is_test(&self) -> bool {
self.selector.is_none()
&& self.fn_name != sway_types::constants::DEFAULT_ENTRY_POINT_FN_NAME
}
}
impl fmt::Display for FinalizedAsm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}\n{}", self.program_section, self.data_section)
}
}
fn to_bytecode_mut(
ops: &[AllocatedOp],
data_section: &mut DataSection,
source_map: &mut SourceMap,
source_engine: &SourceEngine,
build_config: &BuildConfig,
) -> CompiledBytecode {
fn op_size_in_bytes(data_section: &DataSection, item: &AllocatedOp) -> u64 {
match &item.opcode {
AllocatedOpcode::LoadDataId(_reg, data_label)
if !data_section
.has_copy_type(data_label)
.expect("data label references non existent data -- internal error") =>
{
8
}
AllocatedOpcode::DataSectionOffsetPlaceholder => 8,
AllocatedOpcode::BLOB(count) => count.value as u64 * 4,
AllocatedOpcode::CFEI(i) | AllocatedOpcode::CFSI(i) if i.value == 0 => 0,
_ => 4,
}
}
let mut offset_to_data_section_in_bytes = ops
.iter()
.fold(0, |acc, item| acc + op_size_in_bytes(data_section, item));
let mut ops_padded = Vec::new();
let ops = if offset_to_data_section_in_bytes & 7 == 0 {
ops
} else {
ops_padded.reserve(ops.len() + 1);
ops_padded.extend(ops.iter().cloned());
ops_padded.push(AllocatedOp {
opcode: AllocatedOpcode::NOOP,
comment: "word-alignment of data section".into(),
owning_span: None,
});
offset_to_data_section_in_bytes += 4;
&ops_padded
};
let mut buf = Vec::with_capacity(offset_to_data_section_in_bytes as usize);
if build_config.print_bytecode {
println!(";; --- START OF TARGET BYTECODE ---\n");
}
let mut half_word_ix = 0;
let mut offset_from_instr_start = 0;
for op in ops.iter() {
let span = op.owning_span.clone();
let fuel_op = op.to_fuel_asm(
offset_to_data_section_in_bytes,
offset_from_instr_start,
data_section,
);
offset_from_instr_start += op_size_in_bytes(data_section, op);
match fuel_op {
Either::Right(data) => {
if build_config.print_bytecode {
println!("{:?}", data);
}
let _: [u8; 8] = data;
buf.extend(data.iter().cloned());
half_word_ix += 2;
}
Either::Left(ops) => {
for op in ops {
if build_config.print_bytecode {
println!("{:?}", op);
}
if let Some(span) = &span {
source_map.insert(source_engine, half_word_ix, span);
}
buf.extend(op.to_bytes().iter());
half_word_ix += 1;
}
}
}
}
if build_config.print_bytecode {
println!("{}", data_section);
println!(";; --- END OF TARGET BYTECODE ---\n");
}
assert_eq!(half_word_ix * 4, offset_to_data_section_in_bytes as usize);
assert_eq!(buf.len(), offset_to_data_section_in_bytes as usize);
let config_offsets = data_section
.config_map
.iter()
.map(|(name, id)| {
(
name.clone(),
offset_to_data_section_in_bytes + data_section.raw_data_id_to_offset(*id) as u64,
)
})
.collect::<BTreeMap<String, u64>>();
let mut data_section = data_section.serialize_to_bytes();
buf.append(&mut data_section);
CompiledBytecode {
bytecode: buf,
config_const_offsets: config_offsets,
}
}
pub fn check_invalid_opcodes(handler: &Handler, asm: &FinalizedAsm) -> Result<(), ErrorEmitted> {
match &asm.program_section {
InstructionSet::Fuel { ops } => match asm.program_kind {
ProgramKind::Contract | ProgramKind::Library => Ok(()),
ProgramKind::Script => checks::check_script_opcodes(handler, &ops[..]),
ProgramKind::Predicate => checks::check_predicate_opcodes(handler, &ops[..]),
},
InstructionSet::Evm { ops: _ } => Ok(()),
InstructionSet::MidenVM { ops: _ } => Ok(()),
}
}