Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9a9a864
asm_generation: if a condition is constant, make jump unconditional
Dentosal Apr 15, 2025
7e576ac
Optimize using symbolic interpretation
Dentosal Apr 23, 2025
2f9ae92
Update tests
Dentosal Apr 23, 2025
a80b238
Fix typo
Dentosal Apr 23, 2025
2e3ecc5
Uncomment some tests
Dentosal Apr 23, 2025
f15e2fa
Eliminate instructions which do not produce a new value
Dentosal Apr 23, 2025
5fa3f51
Merge branch 'master' into dento/asm-gen-opt
Dentosal Apr 23, 2025
0547e61
Fix build_config args after merge
Dentosal Apr 23, 2025
1542cd3
clippy
Dentosal Apr 24, 2025
96c2336
Merge branch 'master' into dento/asm-gen-opt
Dentosal Apr 24, 2025
c0ae3dd
Fix cli output test pc values
Dentosal Apr 24, 2025
1bdcc16
Update contract ids and other hardcoded information
Dentosal Apr 24, 2025
db0494a
More test fixing
Dentosal Apr 24, 2025
077a343
More replacing of hard-coded test constants
Dentosal Apr 24, 2025
04254ee
Be more conservative on what effects instructions have
Dentosal Apr 24, 2025
da17436
Remove debug prints
Dentosal Apr 24, 2025
33570c9
Fix typo
Dentosal Apr 24, 2025
93dced8
More fixed address updates
Dentosal Apr 24, 2025
7d00bb2
And more
Dentosal Apr 24, 2025
a0aee80
and more...
Dentosal Apr 24, 2025
8b1b795
Update snap tests
Dentosal Apr 24, 2025
45a5921
Update some json snapshots
Dentosal Apr 24, 2025
bb98a7d
update-contract-ids.sh
Dentosal Apr 24, 2025
09f3a03
Update sway-core/src/asm_generation/fuel/optimizations/symbolic_inter…
Dentosal Apr 25, 2025
da598c4
Keep track of source counts for jump target labels
Dentosal Apr 25, 2025
812d6bc
Rename to constant_propagate
Dentosal Apr 25, 2025
846615d
Merge branch 'master' into dento/asm-gen-opt
Dentosal Apr 25, 2025
15a6f3d
Remove a needless clone
Dentosal Apr 25, 2025
a0115e3
Update forc-plugins/forc-debug/tests/server_integration.rs
Dentosal Apr 28, 2025
3219fbe
Update forc-plugins/forc-debug/tests/server_integration.rs
Dentosal Apr 28, 2025
d7a56d2
Merge branch 'master' into dento/asm-gen-opt
Dentosal Apr 29, 2025
bf2de77
Merge branch 'master' into dento/asm-gen-opt
Dentosal May 2, 2025
e5eb36b
Update snapshots
Dentosal May 2, 2025
4adc1e4
Merge branch 'master' into dento/asm-gen-opt
JoshuaBatty May 5, 2025
2143ccd
Merge branch 'master' into dento/asm-gen-opt
JoshuaBatty May 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions forc-plugins/forc-client/tests/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ async fn test_simple_deploy() {
node.kill().unwrap();
let expected = vec![DeployedPackage::Contract(DeployedContract {
id: ContractId::from_str(
"57a5ac7d952df0dd7e72812509fd373260bee5dac54cca48c4d0b2841e9bcee3",
"7338571226a3a07d411acc73bf31821d7315365e3e04e309f5055f0b567431fb",
)
.unwrap(),
proxy: None,
Expand Down Expand Up @@ -421,7 +421,7 @@ async fn test_deploy_submit_only() {
node.kill().unwrap();
let expected = vec![DeployedPackage::Contract(DeployedContract {
id: ContractId::from_str(
"57a5ac7d952df0dd7e72812509fd373260bee5dac54cca48c4d0b2841e9bcee3",
"7338571226a3a07d411acc73bf31821d7315365e3e04e309f5055f0b567431fb",
)
.unwrap(),
proxy: None,
Expand Down Expand Up @@ -468,12 +468,12 @@ async fn test_deploy_fresh_proxy() {
node.kill().unwrap();
let impl_contract = DeployedPackage::Contract(DeployedContract {
id: ContractId::from_str(
"57a5ac7d952df0dd7e72812509fd373260bee5dac54cca48c4d0b2841e9bcee3",
"7338571226a3a07d411acc73bf31821d7315365e3e04e309f5055f0b567431fb",
)
.unwrap(),
proxy: Some(
ContractId::from_str(
"b8588024c0581f715d79c4f5ebd0b9e07f4ed0028f4da4fcf245ec09e1400982",
"2d4f046d31ee52218c189d0c4f30ac74490ab73b0747b4b95908b1ca7f7f7976",
)
.unwrap(),
),
Expand Down
4 changes: 2 additions & 2 deletions forc-plugins/forc-debug/tests/server_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,10 @@ fn test_sourcemap_build() {
// Verify essential source locations are mapped correctly
let key_locations = [
// Main function and its contents
(3, 4, "main function parameters"), // Should have 4 instructions
(3, 2, "main function parameters"), // Should have 2 instructions
(4, 2, "addition operation"), // Should have 2 instructions (add operation)
// Helper function and its contents
(11, 4, "helper function parameters"), // Should have 4 instructions
(11, 2, "helper function parameters"), // Should have 2 instructions
(12, 2, "helper addition operation"), // Should have 2 instructions
// Test functions (identical patterns)
(21, 1, "test_1 first line"), // Each test line should have
Expand Down
8 changes: 4 additions & 4 deletions forc/tests/cli_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ fn test_forc_test_raw_logs() -> Result<(), rexpect::error::Error> {
// Assert that the output is correct
process.exp_string(" test test_log_4")?;
process.exp_string("Raw logs:")?;
process.exp_string(r#"[{"LogData":{"data":"0000000000000004","digest":"8005f02d43fa06e7d0585fb64c961d57e318b27a145c857bcd3a6bdb413ff7fc","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12384,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?;
process.exp_string(r#"[{"LogData":{"data":"0000000000000004","digest":"8005f02d43fa06e7d0585fb64c961d57e318b27a145c857bcd3a6bdb413ff7fc","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12300,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?;
process.exp_string(" test test_log_2")?;
process.exp_string("Raw logs:")?;
process.exp_string(r#"[{"LogData":{"data":"0000000000000002","digest":"cd04a4754498e06db5a13c5f371f1f04ff6d2470f24aa9bd886540e5dce77f70","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12384,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?;
process.exp_string(r#"[{"LogData":{"data":"0000000000000002","digest":"cd04a4754498e06db5a13c5f371f1f04ff6d2470f24aa9bd886540e5dce77f70","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12300,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?;

process.process.exit()?;
Ok(())
Expand All @@ -74,11 +74,11 @@ fn test_forc_test_both_logs() -> Result<(), rexpect::error::Error> {
process.exp_string(" test test_log_4")?;
process.exp_string("Decoded log value: 4, log rb: 1515152261580153489")?;
process.exp_string("Raw logs:")?;
process.exp_string(r#"[{"LogData":{"data":"0000000000000004","digest":"8005f02d43fa06e7d0585fb64c961d57e318b27a145c857bcd3a6bdb413ff7fc","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12384,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?;
process.exp_string(r#"[{"LogData":{"data":"0000000000000004","digest":"8005f02d43fa06e7d0585fb64c961d57e318b27a145c857bcd3a6bdb413ff7fc","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12300,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?;
process.exp_string(" test test_log_2")?;
process.exp_string("Decoded log value: 2, log rb: 1515152261580153489")?;
process.exp_string("Raw logs:")?;
process.exp_string(r#"[{"LogData":{"data":"0000000000000002","digest":"cd04a4754498e06db5a13c5f371f1f04ff6d2470f24aa9bd886540e5dce77f70","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12384,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?;
process.exp_string(r#"[{"LogData":{"data":"0000000000000002","digest":"cd04a4754498e06db5a13c5f371f1f04ff6d2470f24aa9bd886540e5dce77f70","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12300,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?;
process.process.exit()?;
Ok(())
}
163 changes: 5 additions & 158 deletions sway-core/src/asm_generation/fuel/abstract_instruction_set.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
use crate::{
asm_generation::fuel::{
allocated_abstract_instruction_set::AllocatedAbstractInstructionSet, register_allocator,
},
asm_lang::{
allocated_ops::AllocatedOp, Op, OrganizationalOp, RealizedOp, VirtualOp, VirtualRegister,
},
};

use sway_error::error::CompileError;
use sway_types::Span;

use std::{collections::HashSet, fmt};
use crate::asm_lang::{allocated_ops::AllocatedOp, Op, RealizedOp};

use either::Either;
use std::fmt;

use super::data_section::DataSection;
use super::{
allocated_abstract_instruction_set::AllocatedAbstractInstructionSet, register_allocator,
};

/// An [AbstractInstructionSet] is a set of instructions that use entirely virtual registers
/// and excessive moves, with the intention of later optimizing it.
Expand All @@ -24,151 +16,6 @@ pub struct AbstractInstructionSet {
}

impl AbstractInstructionSet {
pub(crate) fn optimize(self, data_section: &DataSection) -> AbstractInstructionSet {
self.const_indexing_aggregates_function(data_section)
.dce()
.simplify_cfg()
.remove_sequential_jumps()
.remove_redundant_moves()
.remove_redundant_ops()
}

/// Removes any jumps to the subsequent line.
fn remove_sequential_jumps(mut self) -> AbstractInstructionSet {
let dead_jumps: Vec<_> = self
.ops
.windows(2)
.enumerate()
.filter_map(|(idx, ops)| match (&ops[0].opcode, &ops[1].opcode) {
(
Either::Right(OrganizationalOp::Jump(dst_label)),
Either::Right(OrganizationalOp::Label(label)),
) if dst_label == label => Some(idx),
_otherwise => None,
})
.collect();

// Replace the dead jumps with NOPs, as it's cheaper.
for idx in dead_jumps {
self.ops[idx] = Op {
opcode: Either::Left(VirtualOp::NOOP),
comment: "remove redundant jump operation".into(),
owning_span: None,
};
}

self
}

fn remove_redundant_moves(mut self) -> AbstractInstructionSet {
// This has a lot of room for improvement.
//
// For now it is just removing MOVEs to registers which are _never_ used. It doesn't
// analyse control flow or other redundancies. Some obvious improvements are:
//
// - Perform a control flow analysis to remove MOVEs to registers which are not used
// _after_ the MOVE.
//
// - Remove the redundant use of temporaries. E.g.:
// MOVE t, a MOVE b, a
// MOVE b, t => USE b
// USE b
loop {
// Gather all the uses for each register.
let uses: HashSet<&VirtualRegister> =
self.ops.iter().fold(HashSet::new(), |mut acc, op| {
for u in &op.use_registers() {
acc.insert(u);
}
acc
});

// Loop again and find MOVEs which have a non-constant destination which is never used.
let mut dead_moves = Vec::new();
for (idx, op) in self.ops.iter().enumerate() {
if let Either::Left(VirtualOp::MOVE(
dst_reg @ VirtualRegister::Virtual(_),
_src_reg,
)) = &op.opcode
{
if !uses.contains(dst_reg) {
dead_moves.push(idx);
}
}
}

if dead_moves.is_empty() {
break;
}

// Replace the dead moves with NOPs, as it's cheaper.
for idx in dead_moves {
self.ops[idx] = Op {
opcode: Either::Left(VirtualOp::NOOP),
comment: "remove redundant move operation".into(),
owning_span: None,
};
}
}

self
}

fn remove_redundant_ops(mut self) -> AbstractInstructionSet {
self.ops.retain(|op| {
// It is easier to think in terms of operations we want to remove
// than the operations we want to retain ;-)
#[allow(clippy::match_like_matches_macro)]
// Keep the `match` for adding more ops in the future.
let remove = match &op.opcode {
Either::Left(VirtualOp::NOOP) => true,
_ => false,
};

!remove
});

self
}

// At the moment the only verification we do is to make sure used registers are
// initialised. Without doing dataflow analysis we still can't guarantee the init is
// _before_ the use, but future refactoring to convert abstract ops into SSA and BBs will
// make this possible or even make this check redundant.
pub(crate) fn verify(self) -> Result<AbstractInstructionSet, CompileError> {
macro_rules! add_virt_regs {
($regs: expr, $set: expr) => {
let mut regs = $regs;
regs.retain(|&reg| matches!(reg, VirtualRegister::Virtual(_)));
$set.extend(regs.into_iter());
};
}

let mut use_regs = HashSet::new();
let mut def_regs = HashSet::new();
for op in &self.ops {
add_virt_regs!(op.use_registers(), use_regs);
add_virt_regs!(op.def_registers(), def_regs);
}

if def_regs.is_superset(&use_regs) {
Ok(self)
} else {
let bad_regs = use_regs
.difference(&def_regs)
.map(|reg| match reg {
VirtualRegister::Virtual(name) => format!("$r{name}"),
VirtualRegister::Constant(creg) => creg.to_string(),
})
.collect::<Vec<_>>()
.join(", ");
Err(CompileError::InternalOwned(
format!("Program erroneously uses uninitialized virtual registers: {bad_regs}"),
Span::dummy(),
))
}
}

/// Allocate registers.
pub(crate) fn allocate_registers(
self,
Expand Down
6 changes: 5 additions & 1 deletion sway-core/src/asm_generation/fuel/fuel_asm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ impl AsmBuilder for FuelAsmBuilder<'_, '_> {
..
} = self;

let opt_level = build_config
.map(|cfg| cfg.optimization_level)
.unwrap_or_default();

let entries = entries
.clone()
.into_iter()
Expand Down Expand Up @@ -265,7 +269,7 @@ impl AsmBuilder for FuelAsmBuilder<'_, '_> {
}

let allocated_program = virtual_abstract_program
.into_allocated_program(fallback_fn)
.into_allocated_program(fallback_fn, opt_level)
.map_err(|e| handler.emit_err(e))?;

if build_config
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
use std::collections::{BTreeSet, HashMap};

use either::Either;
use rustc_hash::{FxHashMap, FxHashSet};
use rustc_hash::FxHashMap;

use crate::{
asm_generation::fuel::compiler_constants,
asm_lang::{ControlFlowOp, Label, VirtualImmediate12, VirtualOp, VirtualRegister},
asm_lang::{ControlFlowOp, VirtualImmediate12, VirtualOp, VirtualRegister},
};

use super::{
abstract_instruction_set::AbstractInstructionSet, analyses::liveness_analysis,
data_section::DataSection,
};
use super::super::{abstract_instruction_set::AbstractInstructionSet, data_section::DataSection};

impl AbstractInstructionSet {
// Aggregates that are const index accessed from a base address
Expand Down Expand Up @@ -259,105 +253,4 @@ impl AbstractInstructionSet {

self
}

pub(crate) fn dce(mut self) -> AbstractInstructionSet {
let liveness = liveness_analysis(&self.ops, false);
let ops = &self.ops;

let mut cur_live = BTreeSet::default();
let mut dead_indices = FxHashSet::default();
for (rev_ix, op) in ops.iter().rev().enumerate() {
let ix = ops.len() - rev_ix - 1;

let op_use = op.use_registers();
let mut op_def = op.def_registers();
op_def.append(&mut op.def_const_registers());

if let Either::Right(ControlFlowOp::Jump(_) | ControlFlowOp::JumpIfNotZero(..)) =
op.opcode
{
// Block boundary. Start afresh.
cur_live.clone_from(liveness.get(ix).expect("Incorrect liveness info"));
// Add use(op) to cur_live.
for u in op_use {
cur_live.insert(u.clone());
}
continue;
}

let dead = op_def.iter().all(|def| !cur_live.contains(def))
&& match &op.opcode {
Either::Left(op) => !op.has_side_effect(),
Either::Right(_) => false,
};
// Remove def(op) from cur_live.
for def in &op_def {
cur_live.remove(def);
}
if dead {
dead_indices.insert(ix);
} else {
// Add use(op) to cur_live
for u in op_use {
cur_live.insert(u.clone());
}
}
}

// Actually delete the instructions.
let mut new_ops: Vec<_> = std::mem::take(&mut self.ops)
.into_iter()
.enumerate()
.filter_map(|(idx, op)| {
if !dead_indices.contains(&idx) {
Some(op)
} else {
None
}
})
.collect();
std::mem::swap(&mut self.ops, &mut new_ops);

self
}

// Remove unreachable instructions.
pub(crate) fn simplify_cfg(mut self) -> AbstractInstructionSet {
let ops = &self.ops;

if ops.is_empty() {
return self;
}

// Keep track of a map between jump labels and op indices. Useful to compute op successors.
let mut label_to_index: HashMap<Label, usize> = HashMap::default();
for (idx, op) in ops.iter().enumerate() {
if let Either::Right(ControlFlowOp::Label(op_label)) = op.opcode {
label_to_index.insert(op_label, idx);
}
}

let mut reachables = vec![false; ops.len()];
let mut worklist = vec![0];
while let Some(op_idx) = worklist.pop() {
assert!(!reachables[op_idx]);
reachables[op_idx] = true;
let op = &ops[op_idx];
for s in &op.successors(op_idx, ops, &label_to_index) {
if !reachables[*s] {
worklist.push(*s);
}
}
}

let reachable_ops = self
.ops
.into_iter()
.enumerate()
.filter_map(|(idx, op)| if reachables[idx] { Some(op) } else { None })
.collect();
self.ops = reachable_ops;

self
}
}
Loading
Loading