use zkevm_opcode_defs::decoding::encoding_mode_production::EncodingModeProduction;
use zkevm_opcode_defs::decoding::VmEncodingMode;
use super::*;
use crate::assembly::constants::*;
use crate::assembly::section::*;
pub const DEFAULT_UNWIND_LABEL: &str = "DEFAULT_UNWIND";
const DEFAULT_UNWIND_LANDING_PAD_ASSEMBLY: &str = "ret.panic.to_label r0, @DEFAULT_UNWIND";
lazy_static::lazy_static! {
pub(crate) static ref DEFAULT_UNWIND_LANDING_PAD_INSTURUCTION_ASSEMBLY: Instruction = {
crate::assembly::parse::code_element::parse_code_element(DEFAULT_UNWIND_LANDING_PAD_ASSEMBLY).unwrap()
};
}
pub const DEFAULT_FAR_RETURN_LABEL: &str = "DEFAULT_FAR_RETURN";
const DEFAULT_FAR_RETURN_LANDING_PAD_ASSEMBLY: &str = "ret.ok.to_label r1, @DEFAULT_FAR_RETURN";
lazy_static::lazy_static! {
pub(crate) static ref DEFAULT_FAR_RETURN_LANDING_PAD_INSTURUCTION_ASSEMBLY: Instruction = {
crate::assembly::parse::code_element::parse_code_element(DEFAULT_FAR_RETURN_LANDING_PAD_ASSEMBLY).unwrap()
};
}
pub const DEFAULT_FAR_REVERT_LABEL: &str = "DEFAULT_FAR_REVERT";
const DEFAULT_FAR_REVERT_LANDING_PAD_ASSEMBLY: &str = "ret.revert.to_label r1, @DEFAULT_FAR_REVERT";
lazy_static::lazy_static! {
pub(crate) static ref DEFAULT_FAR_REVERT_LANDING_PAD_INSTURUCTION_ASSEMBLY: Instruction = {
crate::assembly::parse::code_element::parse_code_element(DEFAULT_FAR_REVERT_LANDING_PAD_ASSEMBLY).unwrap()
};
}
#[derive(Clone, Debug)]
pub enum AlignedRawBytecode {
Instructions(smallvec::SmallVec<[Instruction; 4]>),
Data(ConstantValue),
}
pub fn production_linker() -> Linker<8, EncodingModeProduction> {
Linker::new()
}
#[derive(Clone, Copy, Debug)]
pub struct Linker<const N: usize = 8, E: VmEncodingMode<N> = EncodingModeProduction> {
_marker: std::marker::PhantomData<E>,
}
impl<const N: usize, E: VmEncodingMode<N>> Linker<N, E> {
pub fn new() -> Self {
Self {
_marker: std::marker::PhantomData,
}
}
pub(crate) fn link(
&self,
sections: Vec<ParsedSection>,
mut labels: HashSet<String>,
metadata_hash: Option<[u8; 32]>,
) -> Result<
(
Vec<AlignedRawBytecode>,
HashMap<usize, usize>,
HashMap<String, usize>,
),
AssemblyParseError,
> {
let mut result = vec![];
let mut aligned_code = vec![];
let mut data_elements = vec![];
let mut aligned_globals_values = vec![];
let mut function_labels_to_pc = HashMap::new();
let mut constant_labels_to_offset = HashMap::new();
let mut globals_labels_to_offset = HashMap::new();
let mut pc_to_line_mapping = HashMap::new();
let mut non_trivial_initializers = vec![];
for section in sections.iter() {
match section {
ParsedSection::Globals(section) => {
for el in section.elements.iter().cloned() {
match el {
GlobalsSectionElement::Unlabeled(constant) => {
aligned_globals_values.push(constant);
}
GlobalsSectionElement::Labeled(LabeledGlobal {
label,
source_line: _,
content,
}) => {
let offset = aligned_globals_values.len();
assert!(labels.remove(&*label));
globals_labels_to_offset.insert(label.clone(), offset);
for (sub_idx, constant) in content.into_iter().enumerate() {
if !constant.is_empty() {
non_trivial_initializers.push((
label.clone(),
sub_idx,
constant.clone(),
));
}
aligned_globals_values.push(constant);
}
}
}
}
}
_ => {}
}
}
if !aligned_globals_values.is_empty() {
use crate::assembly::parse::code_element::parse_code_element;
let asm_line = format!("nop r0, r0, stack+=[{}], r0", aligned_globals_values.len());
let opcode = parse_code_element(&asm_line).unwrap();
aligned_code.push(opcode);
}
assert!(non_trivial_initializers.len() <= aligned_globals_values.len());
for (label, in_variable_idx, constant) in non_trivial_initializers.into_iter() {
let initializing_label = format!("_INTERNAL_INIT_{}_{}", &label, in_variable_idx);
let offset = data_elements.len();
constant_labels_to_offset.insert(initializing_label.clone(), offset);
data_elements.push(DataElement::Constant(constant));
use crate::assembly::parse::code_element::parse_code_element;
let asm_line = format!(
"add @{}[0], r0, stack[@{} + {}]",
initializing_label, label, in_variable_idx,
);
let opcode = parse_code_element(&asm_line).unwrap();
aligned_code.push(opcode);
}
for section in sections.into_iter() {
match section {
ParsedSection::Text(section) => {
for el in section.elements.into_iter() {
match el {
TextSectionElement::Unlabeled(code) => {
let CodeElement {
source_line,
instruction,
} = code;
let pc = aligned_code.len();
aligned_code.push(instruction);
pc_to_line_mapping.insert(pc, source_line);
}
TextSectionElement::Labeled(LabeledFunction {
label,
source_line: _,
content,
}) => {
let pc = aligned_code.len();
assert!(labels.remove(&*label));
function_labels_to_pc.insert(label, pc);
for code in content.into_iter() {
let CodeElement {
source_line,
instruction,
} = code;
let pc = aligned_code.len();
aligned_code.push(instruction);
pc_to_line_mapping.insert(pc, source_line);
}
}
}
}
}
ParsedSection::Data(section) => {
for el in section.elements.into_iter() {
match el {
DataSectionElement::Unlabeled(element) => {
data_elements.push(element);
}
DataSectionElement::Labeled(LabeledConstant {
label,
source_line: _,
content,
}) => {
let offset = data_elements.len();
assert!(labels.remove(&*label));
constant_labels_to_offset.insert(label, offset);
for element in content.into_iter() {
data_elements.push(element);
}
}
}
}
}
ParsedSection::Globals(..) => {
}
}
}
let add_landing_pad = |label: &str,
landing_pad_instruction: Instruction,
function_labels_to_pc: &mut HashMap<String, usize>,
aligned_code: &mut Vec<Instruction>| {
if !function_labels_to_pc.contains_key(label) {
let pc = aligned_code.len();
aligned_code.push(landing_pad_instruction);
function_labels_to_pc.insert(label.to_owned(), pc);
}
};
add_landing_pad(
DEFAULT_UNWIND_LABEL,
DEFAULT_UNWIND_LANDING_PAD_INSTURUCTION_ASSEMBLY.clone(),
&mut function_labels_to_pc,
&mut aligned_code,
);
add_landing_pad(
DEFAULT_FAR_RETURN_LABEL,
DEFAULT_FAR_RETURN_LANDING_PAD_INSTURUCTION_ASSEMBLY.clone(),
&mut function_labels_to_pc,
&mut aligned_code,
);
add_landing_pad(
DEFAULT_FAR_REVERT_LABEL,
DEFAULT_FAR_REVERT_LANDING_PAD_INSTURUCTION_ASSEMBLY.clone(),
&mut function_labels_to_pc,
&mut aligned_code,
);
let opcodes_per_word = 32 / N;
for _ in (aligned_code.len() % opcodes_per_word)..opcodes_per_word {
aligned_code.push(PADDING_INSTRUCTION.clone());
}
assert_eq!(aligned_code.len() % opcodes_per_word, 0);
let data_offset = aligned_code.len() / opcodes_per_word;
for (_, v) in constant_labels_to_offset.iter_mut() {
*v += data_offset;
}
let all_function_labels_to_pc: HashSet<_> = function_labels_to_pc.keys().cloned().collect();
let all_constant_labels_to_offset = constant_labels_to_offset.keys().cloned().collect();
let all_globals_labels_to_offset = globals_labels_to_offset.keys().cloned().collect();
if let Some(el) = all_function_labels_to_pc
.intersection(&all_constant_labels_to_offset)
.next()
{
return Err(AssemblyParseError::DuplicateLabel(el.clone()));
}
if let Some(el) = all_function_labels_to_pc
.intersection(&all_globals_labels_to_offset)
.next()
{
return Err(AssemblyParseError::DuplicateLabel(el.clone()));
}
if let Some(el) = all_constant_labels_to_offset
.intersection(&all_globals_labels_to_offset)
.next()
{
return Err(AssemblyParseError::DuplicateLabel(el.clone()));
}
for el in aligned_code.iter_mut() {
el.link::<N, E>(
&function_labels_to_pc,
&constant_labels_to_offset,
&globals_labels_to_offset,
)?;
}
let mut aligned_constants = Vec::new();
for element in data_elements {
match element {
DataElement::Constant(value) => {
aligned_constants.push(value);
}
DataElement::LabelName(name) => {
use crate::assembly::parse::data_element::resolve_to_constant;
if let Some(value) = resolve_to_constant(&name, &function_labels_to_pc) {
aligned_constants.push(value);
} else {
return Err(AssemblyParseError::RelocationError(name));
}
}
}
}
let mut it = aligned_code.chunks_exact(opcodes_per_word);
for chunk in &mut it {
let as_smallvec = smallvec::SmallVec::from_iter(chunk.iter().cloned());
let raw = AlignedRawBytecode::Instructions(as_smallvec);
result.push(raw);
}
assert!(it.remainder().is_empty(), "invalid code padding performed");
for el in aligned_constants {
let raw = AlignedRawBytecode::Data(el);
result.push(raw);
}
if let Some(metadata_hash) = metadata_hash {
if result.len() % 2 == 1 {
let raw = AlignedRawBytecode::Data(ConstantValue::Cell([0u8; 32]));
result.push(raw);
}
let raw = AlignedRawBytecode::Data(ConstantValue::Cell(metadata_hash));
result.push(raw);
} else if result.len() % 2 != 1 {
let raw = AlignedRawBytecode::Data(ConstantValue::Cell([0u8; 32]));
result.push(raw);
}
assert_eq!(result.len() % 2, 1);
Ok((result, pc_to_line_mapping, function_labels_to_pc))
}
}