Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f730122
optimisations running on loop
xunilrj Jun 5, 2026
5ccb2da
compat script to macos
xunilrj Jun 5, 2026
771d8cd
update tests
xunilrj Jun 5, 2026
da11217
update tests
xunilrj Jun 5, 2026
f55e964
update tests
xunilrj Jun 5, 2026
e512c14
avoid infinite loop on local_copy_prop
xunilrj Jun 5, 2026
b66deda
fix justfile
xunilrj Jun 5, 2026
fc12a28
fmt and clippy issues
xunilrj Jun 5, 2026
238481f
new inline threshold
xunilrj Jun 8, 2026
9ccf15d
do not inline with asm blocks
xunilrj Jun 8, 2026
287425d
increase inlining threshold on call sites inside loops
xunilrj Jun 10, 2026
3aa8d99
increase inlining threshold on call sites inside loops
xunilrj Jun 10, 2026
0462710
fmt and clippy issues
xunilrj Jun 10, 2026
405397c
update tests
xunilrj Jun 10, 2026
ebbb6b0
update tests
xunilrj Jun 10, 2026
7328702
fix justfile for mac
xunilrj Jun 10, 2026
7ea6c4a
do not inline fns with asm blocks
xunilrj Jun 10, 2026
7c6f65c
new inline threshold
xunilrj Jun 10, 2026
62e0580
new inline threshold
xunilrj Jun 10, 2026
35c185d
new inline threshold
xunilrj Jun 10, 2026
15dcbe7
new inline threshold
xunilrj Jun 10, 2026
f6a8bc9
new inline threshold
xunilrj Jun 10, 2026
62f3910
new inline threshold
xunilrj Jun 10, 2026
9172bd4
finishing up PR
xunilrj Jun 10, 2026
110ab01
fmt and clippy issues
xunilrj Jun 10, 2026
3846fc6
update tests
xunilrj Jun 11, 2026
15d1424
update tests
xunilrj Jun 11, 2026
0857beb
fix typo
xunilrj Jun 11, 2026
b6843d5
update tests
xunilrj Jun 11, 2026
d02031b
update tests
xunilrj Jun 11, 2026
abfa7dd
update tests
xunilrj Jun 11, 2026
9cee248
update tests
xunilrj Jul 8, 2026
6d03a84
fix rebase issues
xunilrj Jul 8, 2026
91a1931
fix update test ids
xunilrj Jul 8, 2026
7ab5ae2
fix update contract ids script
xunilrj Jul 8, 2026
cb90f4f
fix rebase issues
xunilrj Jul 8, 2026
84363d7
removing forc cli tests
xunilrj Jul 11, 2026
63ac4f3
update tests
xunilrj Jul 11, 2026
52096f1
update tests
xunilrj Jul 11, 2026
8c87395
fmt and clippy issues
xunilrj Jul 13, 2026
a269d74
update tests
xunilrj Jul 13, 2026
5f162af
update tests
xunilrj Jul 14, 2026
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 22 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ benchmark-tests:
# The `performance` group contains recipes related to benchmarking the performance of compiled code:
# gas usages and bytecode sizes.

CURRENT_BRANCH := `git branch --show-current`

pe2e-against-master:
echo "Performance: {{CURRENT_BRANCH}} vs master"
git clean -xfd test/perf_out
git checkout master
cargo r -p test --release -- XX # update reduced std-libs
just pa
git checkout {{CURRENT_BRANCH}}
cargo r -p test --release -- XX # update reduced std-libs
just pa
just pdl
echo "performance reports at ./test/perf_out"

pe2e-o2-against-master:
echo "Performance: {{CURRENT_BRANCH}} vs master"
git checkout master
cd ../sway-program-collection && git clean -xfd && just a
git checkout {{CURRENT_BRANCH}}
cd ../sway-program-collection && just a && just opd

alias pe2e := perf-e2e
# collect gas usages and bytecode sizes from E2E tests
[group('performance')]
Expand Down Expand Up @@ -103,7 +124,7 @@ perf-diff-latest format='md':
# format: output format, either `csv` or `html` (default: `csv`)
# open: for `html` output, `-o` opens the report in the default browser

alias psh := perf-snapshot-historical
#alias psh := perf-snapshot-historical
# collect historic gas usages from a snapshot test that has a `forc test` output
[linux]
[macos]
Expand Down
20 changes: 17 additions & 3 deletions scripts/perf/perf-diff-latest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ perf_diff_stats_script="./scripts/perf/perf-diff-stats.sh"

categories=("e2e-gas-usages" "e2e-bytecode-sizes" "in-language-gas-usages" "in-language-bytecode-sizes")


# macOS compatibility: gfind from `brew install findutils`
if [ -x "$(command -v gfind)" ]; then
find="gfind"
else
find="find"
fi

err() {
echo "ERROR: $*" >&2
exit 1
Expand All @@ -28,8 +36,11 @@ process_category() {
local category="$1"

# Collect candidates: "*-<category>-*.csv", but skip "-<category>-historical-*.csv".
mapfile -t candidates < <(
find "$perf_out_dir" -maxdepth 1 -type f -name "*-${category}-*.csv" -printf "%p\n" 2>/dev/null |
candidates=()
while IFS= read -r line; do
candidates+=("$line")
done < <(
$find "$perf_out_dir" -maxdepth 1 -type f -name "*-${category}-*.csv" -printf "%p\n" 2>/dev/null |
while IFS= read -r f; do
base="$(basename -- "$f")"

Expand All @@ -50,7 +61,10 @@ process_category() {
fi

# Sort by timestamp (asc) and take last two paths.
mapfile -t sorted_paths < <(
sorted_paths=()
while IFS= read -r line; do
sorted_paths+=("$line")
done < <(
printf "%s\n" "${candidates[@]}" |
sort -n -k1,1 |
awk -F'\t' '{print $2}' |
Expand Down
1 change: 1 addition & 0 deletions sway-ir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ sway-types.workspace = true
sway-utils.workspace = true

[dev-dependencies]
expect-test.workspace = true
insta.workspace = true
vte.workspace = true

Expand Down
2 changes: 2 additions & 0 deletions sway-ir/src/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ pub mod dominator;
pub use dominator::*;
pub mod memory_utils;
pub use memory_utils::*;
pub mod loop_analysis;
pub use loop_analysis::*;
183 changes: 107 additions & 76 deletions sway-ir/src/analysis/dominator.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
block::Block, AnalysisResult, AnalysisResultT, AnalysisResults, BranchToWithArgs, Context,
Function, IrError, Pass, PassMutability, ScopedPass, Value,
block::Block, AnalysisResult, AnalysisResultT, AnalysisResults, Context, Function, IrError,
Pass, PassMutability, ScopedPass, Value,
};
use indexmap::IndexSet;
/// Dominator tree and related algorithms.
Expand Down Expand Up @@ -43,6 +43,15 @@ pub struct PostOrder {
}
impl AnalysisResultT for PostOrder {}

impl PostOrder {
/// If `block` was found by the `PostOrder` analysis
/// it is reachable from the entry function.
#[inline(always)]
pub fn is_reachable(&self, block: &Block) -> bool {
self.block_to_po.contains_key(block)
}
}

pub const POSTORDER_NAME: &str = "postorder";

pub fn create_postorder_pass() -> Pass {
Expand All @@ -62,42 +71,44 @@ pub fn compute_post_order_pass(
Ok(Box::new(compute_post_order(context, &function)))
}

fn post_order(
context: &Context,
block: Block,
result: &mut PostOrder,
visited: &mut FxHashSet<Block>,
counter: &mut usize,
) {
if !visited.insert(block) {
return;
}

for successor in block.successors(context) {
post_order(context, successor.block, result, visited, counter);
}

result.block_to_po.insert(block, *counter);
result.po_to_block.push(block);
*counter += 1;
}

/// Compute the post-order traversal of the CFG.
///
/// **BEWARE: Unreachable blocks aren't part of the result.**
pub fn compute_post_order(context: &Context, function: &Function) -> PostOrder {
let mut res = PostOrder {
let mut result = PostOrder {
block_to_po: FxHashMap::default(),
po_to_block: Vec::default(),
};
let entry = function.get_entry_block(context);

let mut counter = 0;
let mut on_stack = FxHashSet::<Block>::default();
fn post_order(
context: &Context,
n: Block,
res: &mut PostOrder,
on_stack: &mut FxHashSet<Block>,
counter: &mut usize,
) {
if on_stack.contains(&n) {
return;
}
on_stack.insert(n);
for BranchToWithArgs { block: n_succ, .. } in n.successors(context) {
post_order(context, n_succ, res, on_stack, counter);
}
res.block_to_po.insert(n, *counter);
res.po_to_block.push(n);
*counter += 1;
}
post_order(context, entry, &mut res, &mut on_stack, &mut counter);
let mut visited = FxHashSet::<Block>::default();
let entry = function.get_entry_block(context);
post_order(context, entry, &mut result, &mut visited, &mut counter);

// We could assert the whole thing, but it'd be expensive.
assert!(res.po_to_block.last().unwrap() == &entry);
assert!(result.po_to_block.last().unwrap() == &entry);

res
result
}

pub const DOMINATORS_NAME: &str = "dominators";
Expand All @@ -107,56 +118,96 @@ pub fn create_dominators_pass() -> Pass {
name: DOMINATORS_NAME,
descr: "Dominator tree computation",
deps: vec![POSTORDER_NAME],
runner: ScopedPass::FunctionPass(PassMutability::Analysis(compute_dom_tree)),
runner: ScopedPass::FunctionPass(PassMutability::Analysis(compute_dom_tree_pass)),
}
}

// Find the nearest common dominator of two blocks,
// using the partially computed dominator tree.
fn nearest_common_dominator_of_two_blocks(
po: &PostOrder,
dom_tree: &FxIndexMap<Block, DomTreeNode>,
mut block_a: Block,
mut block_b: Block,
) -> Block {
while block_a != block_b {
while po.block_to_po[&block_a] < po.block_to_po[&block_b] {
block_a = dom_tree[&block_a].parent.unwrap();
}
while po.block_to_po[&block_b] < po.block_to_po[&block_a] {
block_b = dom_tree[&block_b].parent.unwrap();
}
}
block_a
}

/// Compute the dominator tree for the CFG.
fn compute_dom_tree(
fn compute_dom_tree_pass(
context: &Context,
analyses: &AnalysisResults,
function: Function,
) -> Result<AnalysisResult, IrError> {
let po: &PostOrder = analyses.get_analysis_result(function);
let mut dom_tree = DomTree::default();
let entry = function.get_entry_block(context);
let domtree = compute_dom_tree(context, function, po)?;
Ok(Box::new(domtree))
}

pub(crate) fn compute_dom_tree(
context: &Context<'_>,
function: Function,
po: &PostOrder,
) -> Result<DomTree, IrError> {
let mut dom_tree = FxIndexMap::default();

// This is to make the algorithm happy. It'll be changed to None later.
dom_tree.0.insert(entry, DomTreeNode::new(Some(entry)));
let entry = function.get_entry_block(context);
dom_tree.insert(entry, DomTreeNode::new(Some(entry)));

// initialize the dominators tree. This allows us to do dom_tree[b] fearlessly.
// Note that we just previously initialized "entry", so we skip that here.
for b in po.po_to_block.iter().take(po.po_to_block.len() - 1) {
dom_tree.0.insert(*b, DomTreeNode::new(None));
for block in po.po_to_block.iter().take(po.po_to_block.len() - 1) {
dom_tree.insert(*block, DomTreeNode::new(None));
}

let mut changed = true;

while changed {
changed = false;

// For all nodes, b, in reverse postorder (except start node)
for b in po.po_to_block.iter().rev().skip(1) {
// new_idom <- first (processed) predecessor of b (pick one)
let mut new_idom = b
for block in po.po_to_block.iter().rev().skip(1) {
let current_block_po = po.block_to_po[block];

// new_idom <- first (processed) predecessor of `block` (pick one)
let mut new_idom = block
.pred_iter(context)
.find(|p| {
// "p" may not be reachable, and hence not in dom_tree.
po.block_to_po
.get(p)
.is_some_and(|p_po| *p_po > po.block_to_po[b])
.find(|pred| {
// "pred" may not be reachable, and hence not in the cfg.
let Some(pred_po) = po.block_to_po.get(pred) else {
return false;
};
*pred_po > current_block_po
})
.cloned()
.unwrap();

let picked_pred = new_idom;
// for all other (reachable) predecessors, p, of b:
for p in b

// for all other (reachable) predecessors, `pred` of `block`:
// if doms[pred] already calculated
// then new_idom is the common dominator of both
for pred in block
.pred_iter(context)
.filter(|p| **p != picked_pred && po.block_to_po.contains_key(p))
.filter(|p| **p != picked_pred && po.is_reachable(p))
{
if dom_tree.0[p].parent.is_some() {
// if doms[p] already calculated
new_idom = intersect(po, &dom_tree, *p, new_idom);
if dom_tree[pred].parent.is_some() {
new_idom =
nearest_common_dominator_of_two_blocks(po, &dom_tree, *pred, new_idom);
}
}
let b_node = dom_tree.0.get_mut(b).unwrap();

// update doms[block] if needed
let b_node = dom_tree.get_mut(block).unwrap();
match b_node.parent {
Some(idom) if idom == new_idom => {}
_ => {
Expand All @@ -167,38 +218,18 @@ fn compute_dom_tree(
}
}

// Find the nearest common dominator of two blocks,
// using the partially computed dominator tree.
fn intersect(
po: &PostOrder,
dom_tree: &DomTree,
mut finger1: Block,
mut finger2: Block,
) -> Block {
while finger1 != finger2 {
while po.block_to_po[&finger1] < po.block_to_po[&finger2] {
finger1 = dom_tree.0[&finger1].parent.unwrap();
}
while po.block_to_po[&finger2] < po.block_to_po[&finger1] {
finger2 = dom_tree.0[&finger2].parent.unwrap();
}
}
finger1
}

// Fix the root.
dom_tree.0.get_mut(&entry).unwrap().parent = None;
dom_tree.get_mut(&entry).unwrap().parent = None;

// Build the children.
let child_parent: Vec<_> = dom_tree
.0
.iter()
.filter_map(|(n, n_node)| n_node.parent.map(|n_parent| (*n, n_parent)))
.collect();
for (child, parent) in child_parent {
dom_tree.0.get_mut(&parent).unwrap().children.push(child);
for block in po.po_to_block.iter() {
let Some(parent) = dom_tree[block].parent else {
continue;
};
dom_tree[&parent].children.push(*block);
}

Ok(Box::new(dom_tree))
Ok(DomTree(dom_tree))
}

impl DomTree {
Expand Down
Loading
Loading