Skip to content

Commit fa5d11d

Browse files
committed
improve asm sucessor to deal with arbritary jumps
1 parent cd3465c commit fa5d11d

4 files changed

Lines changed: 89 additions & 47 deletions

File tree

sway-core/src/asm_generation/fuel/analyses.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use either::Either;
44
use indexmap::IndexSet;
55
use sway_types::FxIndexSet;
66

7-
use crate::asm_lang::{ControlFlowOp, Label, Op, VirtualRegister};
7+
use crate::asm_lang::{ControlFlowOp, InstructionSuccessor, Label, Op, VirtualRegister};
88

99
/// Given a list of instructions `ops` of a program, do liveness analysis for the full program.
1010
///
@@ -84,9 +84,18 @@ pub(crate) fn liveness_analysis(
8484

8585
// Compute live_out(op) = live_in(s_1) UNION live_in(s_2) UNION ..., where s1, s_2, ...
8686
// are successors of op
87-
for s in &op.successors(rev_ix, ops, &label_to_index) {
88-
for l in live_in[*s].iter() {
89-
local_modified |= live_out[rev_ix].insert(l.clone());
87+
match op.successors(rev_ix, ops, &label_to_index) {
88+
InstructionSuccessor::Unknown => {
89+
// TODO we cannot fail here because this is used on register allocation
90+
// so any jumps, ECALs etc..., can potentially generate problems here
91+
}
92+
InstructionSuccessor::ReturnFromCall => {}
93+
InstructionSuccessor::Many(ids) => {
94+
for id in ids {
95+
for l in live_in[id].iter() {
96+
local_modified |= live_out[rev_ix].insert(l.clone());
97+
}
98+
}
9099
}
91100
}
92101

sway-core/src/asm_generation/fuel/optimizations/reachability.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::collections::{BTreeSet, HashMap};
33
use either::Either;
44
use rustc_hash::FxHashSet;
55

6-
use crate::asm_lang::{ControlFlowOp, JumpType, Label};
6+
use crate::asm_lang::{ControlFlowOp, InstructionSuccessor, JumpType, Label};
77

88
use super::super::{abstract_instruction_set::AbstractInstructionSet, analyses::liveness_analysis};
99

@@ -91,7 +91,8 @@ impl AbstractInstructionSet {
9191
label_to_index.insert(*op_label, idx);
9292
}
9393
// We cannot guarantee the jump will not end in an
94-
// instruction that will be eliminated below
94+
// instruction that will be eliminated below so we
95+
// bail any optimisation
9596
Either::Right(ControlFlowOp::JumpToAddr(..)) => {
9697
return self;
9798
}
@@ -101,17 +102,22 @@ impl AbstractInstructionSet {
101102

102103
let mut reachables = vec![false; ops.len()];
103104
let mut worklist = vec![0];
104-
while let Some(op_idx) = worklist.pop() {
105-
if reachables[op_idx] {
105+
while let Some(idx) = worklist.pop() {
106+
if reachables[idx] {
106107
continue;
107108
}
108-
reachables[op_idx] = true;
109-
let op = &ops[op_idx];
110-
for s in &op.successors(op_idx, ops, &label_to_index) {
111-
if reachables[*s] {
112-
continue;
109+
110+
reachables[idx] = true;
111+
112+
match ops[idx].successors(idx, ops, &label_to_index) {
113+
InstructionSuccessor::Unknown => {
114+
// bail any optimisation
115+
return self;
116+
}
117+
InstructionSuccessor::ReturnFromCall => {}
118+
InstructionSuccessor::Many(ids) => {
119+
worklist.extend(ids);
113120
}
114-
worklist.push(*s);
115121
}
116122
}
117123

sway-core/src/asm_lang/mod.rs

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,24 @@ pub(crate) struct RealizedOp {
114114
pub(crate) owning_span: Option<Span>,
115115
}
116116

117+
pub enum InstructionSuccessor {
118+
/// Used for arbritary jumps, ECAL etc.... We cannot guarantee which
119+
/// instruction is going to be the next one
120+
Unknown,
121+
/// Specifically to signal a return from call.
122+
/// This exists to allow each use to differentiate this from `Unknown` and empty vec.
123+
ReturnFromCall,
124+
/// Vast majority of instruction will only have the next instruction,
125+
/// but jumps, function calls and other can actually have multiple
126+
/// instructions.
127+
/// usize here is the index of the instruction. So the next instruction
128+
/// is <current index> + 1.
129+
///
130+
/// An empty vec is used for revert, contract return and the last instrution.
131+
/// Signals that no more instrctions will run.
132+
Many(Vec<usize>),
133+
}
134+
117135
impl Op {
118136
/// Moves the stack pointer by the given amount (i.e. allocates stack memory)
119137
pub(crate) fn unowned_stack_allocate_memory(
@@ -721,16 +739,17 @@ impl Op {
721739
}
722740
}
723741

724-
/// Returns a list of indices that represent the successors of `self` in the list of
725-
/// instructions `ops`. For most instructions, the successor is simply the next instruction in
726-
/// `ops`. The exceptions are jump instructions that can have arbitrary successors and RVRT
727-
/// which does not have any successors.
742+
/// Returns all successors of `self`.
743+
/// For most instructions, the successor is simply the next instruction in
744+
/// `ops`, if there is any.
745+
/// Exceptions are jump instructions which can have arbitrary successors; RVRT
746+
/// which does not have any successors, and ECAL would can do pretty much anything.
728747
pub(crate) fn successors(
729748
&self,
730749
index: usize,
731750
ops: &[Op],
732751
label_to_index: &HashMap<Label, usize>,
733-
) -> Vec<usize> {
752+
) -> InstructionSuccessor {
734753
match &self.opcode {
735754
Either::Left(virt_op) => virt_op.successors(index, ops),
736755
Either::Right(org_op) => org_op.successors(index, ops, label_to_index),
@@ -1423,23 +1442,21 @@ impl<Reg: Clone + Eq + Ord + Hash> ControlFlowOp<Reg> {
14231442
index: usize,
14241443
ops: &[Op],
14251444
label_to_index: &HashMap<Label, usize>,
1426-
) -> Vec<usize> {
1427-
use ControlFlowOp::*;
1428-
1445+
) -> InstructionSuccessor {
14291446
let mut next_ops = Vec::new();
14301447

14311448
match self {
1432-
Label(_)
1433-
| Comment
1434-
| DataSectionOffsetPlaceholder
1435-
| ConfigurablesOffsetPlaceholder
1436-
| PushAll(_)
1437-
| PopAll(_) => {
1449+
ControlFlowOp::Label(_)
1450+
| ControlFlowOp::Comment
1451+
| ControlFlowOp::DataSectionOffsetPlaceholder
1452+
| ControlFlowOp::ConfigurablesOffsetPlaceholder
1453+
| ControlFlowOp::PushAll(_)
1454+
| ControlFlowOp::PopAll(_) => {
14381455
if index + 1 < ops.len() {
14391456
next_ops.push(index + 1);
14401457
}
14411458
}
1442-
Jump { to, type_, .. } => match type_ {
1459+
ControlFlowOp::Jump { to, type_, .. } => match type_ {
14431460
JumpType::Unconditional => {
14441461
next_ops.push(label_to_index[to]);
14451462
}
@@ -1455,12 +1472,11 @@ impl<Reg: Clone + Eq + Ord + Hash> ControlFlowOp<Reg> {
14551472
}
14561473
}
14571474
},
1458-
// Impossible to know, so we return empty.
1459-
JumpToAddr(_) => {}
1460-
ReturnFromCall { .. } => {}
1461-
};
1475+
ControlFlowOp::JumpToAddr(_) => return InstructionSuccessor::Unknown,
1476+
ControlFlowOp::ReturnFromCall { .. } => return InstructionSuccessor::ReturnFromCall,
1477+
}
14621478

1463-
next_ops
1479+
InstructionSuccessor::Many(next_ops)
14641480
}
14651481
}
14661482

sway-core/src/asm_lang/virtual_ops.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ use super::{
1212
virtual_register::*,
1313
Op,
1414
};
15-
use crate::asm_generation::fuel::{data_section::DataId, register_allocator::RegisterPool};
15+
use crate::{
16+
asm_generation::fuel::{data_section::DataId, register_allocator::RegisterPool},
17+
asm_lang::InstructionSuccessor,
18+
};
1619

1720
use std::collections::{BTreeSet, HashMap};
1821

@@ -952,20 +955,28 @@ impl VirtualOp {
952955
.collect()
953956
}
954957

955-
/// Returns a list of indices that represent the successors of `self` in the list of
956-
/// instructions `ops`. For most instructions, the successor is simply the next instruction in
957-
/// `ops`. The exceptions are jump instructions that can have arbitrary successors and RVRT
958+
/// Returns all successors of `self`.
959+
/// For most instructions, the successor is simply the next instruction in
960+
/// `ops`, if there is any.
961+
/// Exceptions are jump instructions which can have arbitrary successors; RVRT
958962
/// which does not have any successors.
959-
pub(crate) fn successors(&self, index: usize, ops: &[Op]) -> Vec<usize> {
960-
use VirtualOp::*;
961-
let next_op = if index >= ops.len() - 1 {
962-
vec![]
963-
} else {
964-
vec![index + 1]
965-
};
963+
/// ECAL can do pretty much anything, but it executes the next instruction, if "$pc"
964+
/// is not manipulated.
965+
pub(crate) fn successors(&self, index: usize, ops: &[Op]) -> InstructionSuccessor {
966+
assert!(index <= ops.len());
966967
match self {
967-
RVRT(_) => vec![],
968-
_ => next_op,
968+
VirtualOp::RET(..) | VirtualOp::RETD(..) | VirtualOp::RVRT(..) => {
969+
InstructionSuccessor::Many(vec![])
970+
}
971+
_ => {
972+
// If the last instruction is none of the above,
973+
// we return empty vec as the VM would halt.
974+
InstructionSuccessor::Many(if index >= ops.len() - 1 {
975+
vec![]
976+
} else {
977+
vec![index + 1]
978+
})
979+
}
969980
}
970981
}
971982

0 commit comments

Comments
 (0)