Skip to content

Commit

Permalink
Move Fuel specific files from asm_generation/ into `asm_generation/…
Browse files Browse the repository at this point in the history
…fuel/`. (FuelLabs#3872)
  • Loading branch information
otrho authored Jan 24, 2023
1 parent 9a980ac commit 289c0c9
Show file tree
Hide file tree
Showing 25 changed files with 184 additions and 168 deletions.
2 changes: 1 addition & 1 deletion sway-core/src/asm_generation/asm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use sway_ir::Function;

use crate::{asm_lang::Label, CompileResult};

use super::{evm::EvmAsmBuilderResult, fuel::FuelAsmBuilderResult};
use super::{evm::EvmAsmBuilderResult, fuel::fuel_asm_builder::FuelAsmBuilderResult};

pub enum AsmBuilderResult {
Fuel(FuelAsmBuilderResult),
Expand Down
25 changes: 13 additions & 12 deletions sway-core/src/asm_generation/evm/evm_asm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use crate::{
asm_generation::{
asm_builder::{AsmBuilder, AsmBuilderResult},
from_ir::StateAccessType,
register_sequencer::RegisterSequencer,
ProgramKind,
},
asm_lang::Label,
Expand Down Expand Up @@ -51,9 +50,6 @@ pub struct EvmAsmBuilder<'ir> {

sections: Vec<EvmAsmSection>,

// Register sequencer dishes out new registers and labels.
pub(super) reg_seqr: RegisterSequencer,

// Label maps are from IR functions or blocks to label name. Functions have a start and end
// label.
pub(super) func_label_map: HashMap<Function, (Label, Label)>,
Expand All @@ -65,6 +61,9 @@ pub struct EvmAsmBuilder<'ir> {

// Metadata manager for converting metadata to Spans, etc.
md_mgr: MetadataManager,

// Monotonically increasing unique identifier for label generation.
label_idx: usize,
}

#[derive(Default, Debug)]
Expand Down Expand Up @@ -112,19 +111,15 @@ impl<'ir> AsmBuilder for EvmAsmBuilder<'ir> {
#[allow(unused_variables)]
#[allow(dead_code)]
impl<'ir> EvmAsmBuilder<'ir> {
pub fn new(
program_kind: ProgramKind,
reg_seqr: RegisterSequencer,
context: &'ir Context,
) -> Self {
pub fn new(program_kind: ProgramKind, context: &'ir Context) -> Self {
let mut b = EvmAsmBuilder {
program_kind,
sections: Vec::new(),
reg_seqr,
func_label_map: HashMap::new(),
block_label_map: HashMap::new(),
context,
md_mgr: MetadataManager::default(),
label_idx: 0,
};
let s = b.generate_function();
b.sections.push(s);
Expand Down Expand Up @@ -293,6 +288,12 @@ impl<'ir> EvmAsmBuilder<'ir> {
Span::new(Arc::from(msg), 0, msg.len(), None).unwrap()
}

fn get_label(&mut self) -> Label {
let next_val = self.label_idx;
self.label_idx += 1;
Label(self.label_idx)
}

pub(super) fn compile_instruction(
&mut self,
instr_val: &Value,
Expand Down Expand Up @@ -350,7 +351,7 @@ impl<'ir> EvmAsmBuilder<'ir> {
} => self.compile_extract_value(instr_val, aggregate, indices),
Instruction::FuelVm(fuel_vm_instr) => {
errors.push(CompileError::Internal(
"Value not an instruction.",
"Invalid FuelVM IR instruction provided to the EVM code gen.",
self.md_mgr
.val_to_span(self.context, *instr_val)
.unwrap_or_else(Self::empty_span),
Expand Down Expand Up @@ -607,7 +608,7 @@ impl<'ir> EvmAsmBuilder<'ir> {

pub(super) fn func_to_labels(&mut self, func: &Function) -> (Label, Label) {
self.func_label_map.get(func).cloned().unwrap_or_else(|| {
let labels = (self.reg_seqr.get_label(), self.reg_seqr.get_label());
let labels = (self.get_label(), self.get_label());
self.func_label_map.insert(*func, labels);
labels
})
Expand Down
19 changes: 18 additions & 1 deletion sway-core/src/asm_generation/finalized_asm.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::instruction_set::InstructionSet;
use super::{DataSection, ProgramABI, ProgramKind};
use super::{
fuel::{checks, data_section::DataSection},
ProgramABI, ProgramKind,
};
use crate::asm_lang::allocated_ops::{AllocatedOp, AllocatedOpcode};
use crate::decl_engine::DeclId;
use crate::error::*;
Expand Down Expand Up @@ -178,3 +181,17 @@ fn to_bytecode_mut(
errors,
)
}

/// Checks for disallowed opcodes in non-contract code.
/// i.e., if this is a script or predicate, we can't use certain contract opcodes.
/// See https://github.com/FuelLabs/sway/issues/350 for details.
pub fn check_invalid_opcodes(asm: &FinalizedAsm) -> CompileResult<()> {
match &asm.program_section {
InstructionSet::Fuel { ops } => match asm.program_kind {
ProgramKind::Contract | ProgramKind::Library => ok((), vec![], vec![]),
ProgramKind::Script => checks::check_script_opcodes(&ops[..]),
ProgramKind::Predicate => checks::check_predicate_opcodes(&ops[..]),
},
InstructionSet::Evm { ops: _ } => ok((), vec![], vec![]),
}
}
13 changes: 7 additions & 6 deletions sway-core/src/asm_generation/from_ir.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use super::{
asm_builder::{AsmBuilder, AsmBuilderResult},
checks::check_invalid_opcodes,
evm::EvmAsmBuilder,
finalized_asm::FinalizedAsm,
fuel::FuelAsmBuilder,
finalized_asm::{check_invalid_opcodes, FinalizedAsm},
fuel::{
data_section::{DataId, DataSection},
fuel_asm_builder::FuelAsmBuilder,
register_sequencer::RegisterSequencer,
},
programs::{AbstractEntry, AbstractProgram, FinalProgram, ProgramKind},
register_sequencer::RegisterSequencer,
DataId, DataSection,
};

use crate::{err, ok, BuildConfig, BuildTarget, CompileResult, CompileWarning};
Expand Down Expand Up @@ -80,7 +81,7 @@ fn compile_module_to_asm(
reg_seqr,
context,
)),
BuildTarget::EVM => Box::new(EvmAsmBuilder::new(kind, reg_seqr, context)),
BuildTarget::EVM => Box::new(EvmAsmBuilder::new(kind, context)),
};

// Pre-create labels for all functions before we generate other code, so we can call them
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{
asm_generation::{register_allocator, AllocatedAbstractInstructionSet, RegisterSequencer},
asm_generation::fuel::{
allocated_abstract_instruction_set::AllocatedAbstractInstructionSet, register_allocator,
register_sequencer::RegisterSequencer,
},
asm_lang::{
allocated_ops::{AllocatedOp, AllocatedOpcode},
AllocatedAbstractOp, Op, OrganizationalOp, RealizedOp, VirtualOp, VirtualRegister,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ use crate::asm_lang::{
VirtualImmediate18, VirtualImmediate24,
};

use super::{compiler_constants as consts, DataSection, Entry, RealizedAbstractInstructionSet};
use super::{
abstract_instruction_set::RealizedAbstractInstructionSet,
compiler_constants as consts,
data_section::{DataSection, Entry},
};

use sway_types::span::Span;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,13 @@
use sway_error::error::CompileError;
use sway_types::Span;

use crate::asm_generation::{FinalizedAsm, ProgramKind};
use crate::asm_lang::allocated_ops::{AllocatedOp, AllocatedOpcode};
use crate::asm_lang::*;
use crate::error::*;

use super::instruction_set::InstructionSet;

/// Checks for disallowed opcodes in non-contract code.
/// i.e., if this is a script or predicate, we can't use certain contract opcodes.
/// See https://github.com/FuelLabs/sway/issues/350 for details.
pub fn check_invalid_opcodes(asm: &FinalizedAsm) -> CompileResult<()> {
match &asm.program_section {
InstructionSet::Fuel { ops } => match asm.program_kind {
ProgramKind::Contract | ProgramKind::Library => ok((), vec![], vec![]),
ProgramKind::Script => check_script_opcodes(&ops[..]),
ProgramKind::Predicate => check_predicate_opcodes(&ops[..]),
},
InstructionSet::Evm { ops: _ } => ok((), vec![], vec![]),
}
}
use crate::{
asm_lang::{
allocated_ops::{AllocatedOp, AllocatedOpcode},
VirtualImmediate18,
},
error::*,
};

/// Checks if an opcode is one that cannot be executed from within a script.
/// If so, throw an error.
Expand All @@ -35,7 +22,7 @@ pub fn check_invalid_opcodes(asm: &FinalizedAsm) -> CompileResult<()> {
/// }
/// }
/// ```
fn check_script_opcodes(ops: &[AllocatedOp]) -> CompileResult<()> {
pub(crate) fn check_script_opcodes(ops: &[AllocatedOp]) -> CompileResult<()> {
use AllocatedOpcode::*;
let mut errors = vec![];
for op in ops {
Expand Down Expand Up @@ -90,7 +77,7 @@ fn check_script_opcodes(ops: &[AllocatedOp]) -> CompileResult<()> {
/// the function verifies that the immediate of JI, JNEI, JNZI is greater than the opcode offset.
///
/// See: https://fuellabs.github.io/fuel-specs/master/vm/index.html?highlight=predicate#predicate-verification
fn check_predicate_opcodes(ops: &[AllocatedOp]) -> CompileResult<()> {
pub(crate) fn check_predicate_opcodes(ops: &[AllocatedOp]) -> CompileResult<()> {
use AllocatedOpcode::*;
let mut errors = vec![];

Expand Down
11 changes: 7 additions & 4 deletions sway-core/src/asm_generation/fuel/fuel_asm_builder.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use crate::{
asm_generation::{
abstract_instruction_set::AbstractInstructionSet,
asm_builder::{AsmBuilder, AsmBuilderResult},
compiler_constants,
from_ir::{
aggregate_idcs_to_field_layout, ir_type_size_in_bytes, StateAccessType, Storage,
},
register_sequencer::RegisterSequencer,
DataId, DataSection, Entry, ProgramKind,
fuel::{
abstract_instruction_set::AbstractInstructionSet,
compiler_constants,
data_section::{DataId, DataSection, Entry},
register_sequencer::RegisterSequencer,
},
ProgramKind,
},
asm_lang::{virtual_register::*, Label, Op, VirtualImmediate12, VirtualImmediate18, VirtualOp},
decl_engine::DeclId,
Expand Down
8 changes: 5 additions & 3 deletions sway-core/src/asm_generation/fuel/functions.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use crate::{
asm_generation::{compiler_constants, from_ir::*, Entry, ProgramKind},
asm_generation::{
from_ir::*,
fuel::{compiler_constants, data_section::Entry, fuel_asm_builder::FuelAsmBuilder},
ProgramKind,
},
asm_lang::{
virtual_register::*, Op, OrganizationalOp, VirtualImmediate12, VirtualImmediate18,
VirtualImmediate24, VirtualOp,
Expand All @@ -14,8 +18,6 @@ use sway_ir::*;

use either::Either;

use super::FuelAsmBuilder;

/// A summary of the adopted calling convention:
///
/// - Function arguments are passed left to right in the reserved registers. Extra args are passed
Expand Down
12 changes: 9 additions & 3 deletions sway-core/src/asm_generation/fuel/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
mod fuel_asm_builder;
pub use fuel_asm_builder::*;
pub(crate) mod compiler_constants;
pub(crate) mod data_section;
pub(crate) mod register_allocator;

pub(super) mod abstract_instruction_set;
pub(super) mod allocated_abstract_instruction_set;
pub(super) mod checks;
pub(super) mod fuel_asm_builder;
pub(super) mod register_sequencer;

mod functions;
pub use functions::*;
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use crate::{
asm_generation::{
register_sequencer::RegisterSequencer, RegisterAllocationStatus, RegisterPool,
},
asm_lang::{virtual_register::*, Op, VirtualOp},
asm_generation::fuel::{compiler_constants, register_sequencer::RegisterSequencer},
asm_lang::{allocated_ops::AllocatedRegister, virtual_register::*, Op, VirtualOp},
};

use std::collections::{BTreeSet, HashMap};
Expand All @@ -13,6 +11,80 @@ use petgraph::graph::NodeIndex;
pub type InterferenceGraph =
petgraph::stable_graph::StableGraph<VirtualRegister, (), petgraph::Undirected>;

// Initially, the bytecode will have a lot of individual registers being used. Each register will
// have a new unique identifier. For example, two separate invocations of `+` will result in 4
// registers being used for arguments and 2 for outputs.
//
// After that, the level 0 bytecode will go through a process where register use is minified,
// producing level 1 bytecode. This process is as such:
//
// 1. Detect the last time a register is read. After that, it can be reused and recycled to fit the
// needs of the next "level 0 bytecode" register
//
// 2. Detect needless assignments and movements, and substitute registers in.
// i.e.
// a = b
// c = a
//
// would become
// c = b
//
//
// After the level 1 bytecode is produced, level 2 bytecode is created by limiting the maximum
// number of registers and inserting bytecode to read from/write to memory where needed. Ideally,
// the algorithm for determining which registers will be written off to memory is based on how
// frequently that register is accessed in a particular section of code. Using this strategy, we
// hope to minimize memory writing.
//
// For each line, the number of times a virtual register is accessed between then and the end of the
// program is its register precedence. A virtual register's precedence is 0 if it is currently in
// "memory", and the above described number if it is not. This prevents over-prioritization of
// registers that have already been written off to memory.
//
/// The [SwayAsmSet] contains either a contract ABI and corresponding ASM, a script's main
/// function's ASM, or a predicate's main function's ASM. ASM is never generated for libraries,
/// as that happens when the library itself is imported.
#[derive(Debug)]
struct RegisterAllocationStatus {
reg: AllocatedRegister,
used_by: BTreeSet<VirtualRegister>,
}

#[derive(Debug)]
pub(crate) struct RegisterPool {
registers: Vec<RegisterAllocationStatus>,
}

impl RegisterPool {
fn init() -> Self {
let reg_pool: Vec<RegisterAllocationStatus> = (0
// - 1 because we reserve the final register for the data_section begin
..compiler_constants::NUM_ALLOCATABLE_REGISTERS)
.map(|x| RegisterAllocationStatus {
reg: AllocatedRegister::Allocated(x),
used_by: BTreeSet::new(),
})
.collect();
Self {
registers: reg_pool,
}
}

pub(crate) fn get_register(
&self,
virtual_register: &VirtualRegister,
) -> Option<AllocatedRegister> {
let allocated_reg =
self.registers
.iter()
.find(|RegisterAllocationStatus { reg: _, used_by }| {
used_by.contains(virtual_register)
});

allocated_reg.map(|RegisterAllocationStatus { reg, used_by: _ }| reg.clone())
}
}
/// Given a list of instructions `ops` of a program, do liveness analysis for the full program.
///
/// A virtual registers is live at some point in the program if it has previously been defined by
Expand Down
Loading

0 comments on commit 289c0c9

Please sign in to comment.