Skip to content

Commit 39251b6

Browse files
Refactor jump label realization (#7088)
## Description Simplifies jump instruction generation, moving the actual instruction selection to a single location. Removes lots of error-prone duplicate logic around far jump rewriting. This theoretically has a slight size penalty in jump-heavy contracts, but in practice this should be neglible. For instance `mira-v1-core` isn't affected at all. This PR is makes it easier to implement further optimizations, especially those around the new JAL instruction and u16/u32 integer loading opcodes. In particular, data section packing should be a lot easier after this. ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [ ] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [x] I have added tests that prove my fix is effective or that my feature works: just a refactor, existing tests suffice - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --------- Co-authored-by: Joshua Batty <[email protected]>
1 parent aafcdc8 commit 39251b6

File tree

9 files changed

+504
-410
lines changed

9 files changed

+504
-410
lines changed

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

Lines changed: 338 additions & 316 deletions
Large diffs are not rendered by default.

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use crate::{
1616
FinalizedAsm, ProgramKind,
1717
},
1818
asm_lang::{
19-
virtual_register::*, Label, Op, VirtualImmediate06, VirtualImmediate12, VirtualImmediate18,
20-
VirtualOp, WideCmp, WideOperations,
19+
virtual_register::*, JumpType, Label, Op, VirtualImmediate06, VirtualImmediate12,
20+
VirtualImmediate18, VirtualOp, WideCmp, WideOperations,
2121
},
2222
decl_engine::DeclRefFunction,
2323
metadata::MetadataManager,
@@ -174,7 +174,10 @@ impl AsmBuilder for FuelAsmBuilder<'_, '_> {
174174

175175
// call decode
176176
self.before_entries.push(Op {
177-
opcode: Either::Right(crate::asm_lang::ControlFlowOp::Call(*decode_fn_label)),
177+
opcode: Either::Right(crate::asm_lang::ControlFlowOp::Jump {
178+
to: *decode_fn_label,
179+
type_: JumpType::Call,
180+
}),
178181
comment: format!("decode configurable {}", name),
179182
owning_span: None,
180183
});

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
},
1111
asm_lang::{
1212
virtual_register::{self, *},
13-
Op, OrganizationalOp, VirtualImmediate12, VirtualImmediate18, VirtualImmediate24,
13+
JumpType, Op, OrganizationalOp, VirtualImmediate12, VirtualImmediate18, VirtualImmediate24,
1414
VirtualOp,
1515
},
1616
decl_engine::DeclRef,
@@ -191,7 +191,10 @@ impl FuelAsmBuilder<'_, '_> {
191191
// Jump to function and insert return label.
192192
let (fn_label, _) = self.func_to_labels(function);
193193
self.cur_bytecode.push(Op {
194-
opcode: Either::Right(OrganizationalOp::Call(fn_label)),
194+
opcode: Either::Right(OrganizationalOp::Jump {
195+
to: fn_label,
196+
type_: JumpType::Call,
197+
}),
195198
comment: format!("[call]: call {}", function.get_name(self.context)),
196199
owning_span: None,
197200
});

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

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::collections::hash_map::Entry;
33
use either::Either;
44
use rustc_hash::FxHashMap;
55

6-
use crate::asm_lang::{ConstantRegister, ControlFlowOp, Label, Op, VirtualOp, VirtualRegister};
6+
use crate::asm_lang::{
7+
ConstantRegister, ControlFlowOp, JumpType, Label, Op, VirtualOp, VirtualRegister,
8+
};
79

810
use super::super::abstract_instruction_set::AbstractInstructionSet;
911

@@ -117,13 +119,8 @@ impl AbstractInstructionSet {
117119
// todo: build proper control flow graph instead
118120
let mut jump_target_labels = FxHashMap::<Label, usize>::default();
119121
for op in &self.ops {
120-
if let Either::Right(
121-
ControlFlowOp::Jump(label)
122-
| ControlFlowOp::JumpIfNotZero(_, label)
123-
| ControlFlowOp::Call(label),
124-
) = &op.opcode
125-
{
126-
*jump_target_labels.entry(*label).or_default() += 1;
122+
if let Either::Right(ControlFlowOp::Jump { to, .. }) = &op.opcode {
123+
*jump_target_labels.entry(*to).or_default() += 1;
127124
}
128125
}
129126

@@ -145,20 +142,26 @@ impl AbstractInstructionSet {
145142
// Some instructions can be further simplified with the known values.
146143
match &mut op.opcode {
147144
// Conditional jumps can be simplified if we know the value of the register.
148-
Either::Right(ControlFlowOp::JumpIfNotZero(reg, lab)) => {
145+
Either::Right(ControlFlowOp::Jump {
146+
to,
147+
type_: JumpType::NotZero(reg),
148+
}) => {
149149
if let Some(con) = known_values.resolve(reg).and_then(|r| r.value()) {
150150
if con == 0 {
151-
let Entry::Occupied(mut count) = jump_target_labels.entry(*lab) else {
151+
let Entry::Occupied(mut count) = jump_target_labels.entry(*to) else {
152152
unreachable!("Jump target label not found in jump_target_labels");
153153
};
154154
*count.get_mut() -= 1;
155155
if *count.get() == 0 {
156156
// Nobody jumps to this label anymore
157-
jump_target_labels.remove(lab);
157+
jump_target_labels.remove(to);
158158
}
159159
op.opcode = Either::Left(VirtualOp::NOOP);
160160
} else {
161-
op.opcode = Either::Right(ControlFlowOp::Jump(*lab));
161+
op.opcode = Either::Right(ControlFlowOp::Jump {
162+
to: *to,
163+
type_: JumpType::Unconditional,
164+
});
162165
}
163166
}
164167
}
@@ -210,19 +213,18 @@ impl AbstractInstructionSet {
210213
ResetKnown::Defs
211214
}
212215
}
213-
// Jumping away doesn't invalidate state
214-
ControlFlowOp::Jump(_) | ControlFlowOp::JumpIfNotZero(_, _) => {
215-
ResetKnown::Defs
216-
}
216+
// Jumping away doesn't invalidate state, but for calls:
217217
// TODO: `def_const_registers` doesn't contain return value, which
218218
// seems incorrect, so I'm clearing everything as a precaution
219-
ControlFlowOp::Call(_) => ResetKnown::Full,
219+
ControlFlowOp::Jump { type_, .. } => match type_ {
220+
JumpType::Call => ResetKnown::Full,
221+
_ => ResetKnown::Defs,
222+
},
220223
// These ops mark their outputs properly and cause no control-flow effects
221224
ControlFlowOp::Comment
222225
| ControlFlowOp::SaveRetAddr(_, _)
223226
| ControlFlowOp::ConfigurablesOffsetPlaceholder
224-
| ControlFlowOp::DataSectionOffsetPlaceholder
225-
| ControlFlowOp::LoadLabel(_, _) => ResetKnown::Defs,
227+
| ControlFlowOp::DataSectionOffsetPlaceholder => ResetKnown::Defs,
226228
// This changes the stack pointer
227229
ControlFlowOp::PushAll(_) => ResetKnown::NonVirtual,
228230
// This can be considered to destroy all known values

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::super::abstract_instruction_set::AbstractInstructionSet;
22

3-
use crate::asm_lang::{Op, OrganizationalOp, VirtualOp, VirtualRegister};
3+
use crate::asm_lang::{JumpType, Op, OrganizationalOp, VirtualOp, VirtualRegister};
44

55
use std::collections::HashSet;
66

@@ -15,7 +15,11 @@ impl AbstractInstructionSet {
1515
.enumerate()
1616
.filter_map(|(idx, ops)| match (&ops[0].opcode, &ops[1].opcode) {
1717
(
18-
Either::Right(OrganizationalOp::Jump(dst_label)),
18+
Either::Right(OrganizationalOp::Jump {
19+
to: dst_label,
20+
type_: JumpType::Unconditional | JumpType::NotZero(_),
21+
..
22+
}),
1923
Either::Right(OrganizationalOp::Label(label)),
2024
) if dst_label == label => Some(idx),
2125
_otherwise => None,

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

Lines changed: 16 additions & 13 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, Label};
6+
use crate::asm_lang::{ControlFlowOp, JumpType, Label};
77

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

@@ -21,16 +21,16 @@ impl AbstractInstructionSet {
2121
let mut op_def = op.def_registers();
2222
op_def.append(&mut op.def_const_registers());
2323

24-
if let Either::Right(ControlFlowOp::Jump(_) | ControlFlowOp::JumpIfNotZero(..)) =
25-
op.opcode
26-
{
27-
// Block boundary. Start afresh.
28-
cur_live.clone_from(liveness.get(ix).expect("Incorrect liveness info"));
29-
// Add use(op) to cur_live.
30-
for u in op_use {
31-
cur_live.insert(u.clone());
24+
if let Either::Right(ControlFlowOp::Jump { type_, .. }) = &op.opcode {
25+
if !matches!(type_, JumpType::Call) {
26+
// Block boundary. Start afresh.
27+
cur_live.clone_from(liveness.get(ix).expect("Incorrect liveness info"));
28+
// Add use(op) to cur_live.
29+
for u in op_use {
30+
cur_live.insert(u.clone());
31+
}
32+
continue;
3233
}
33-
continue;
3434
}
3535

3636
let dead = op_def.iter().all(|def| !cur_live.contains(def))
@@ -88,13 +88,16 @@ impl AbstractInstructionSet {
8888
let mut reachables = vec![false; ops.len()];
8989
let mut worklist = vec![0];
9090
while let Some(op_idx) = worklist.pop() {
91-
assert!(!reachables[op_idx]);
91+
if reachables[op_idx] {
92+
continue;
93+
}
9294
reachables[op_idx] = true;
9395
let op = &ops[op_idx];
9496
for s in &op.successors(op_idx, ops, &label_to_index) {
95-
if !reachables[*s] && !worklist.contains(s) {
96-
worklist.push(*s);
97+
if reachables[*s] {
98+
continue;
9799
}
100+
worklist.push(*s);
98101
}
99102
}
100103

sway-core/src/asm_generation/fuel/programs/abstract.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
},
1414
asm_lang::{
1515
allocated_ops::{AllocatedInstruction, AllocatedRegister},
16-
AllocatedAbstractOp, ConstantRegister, ControlFlowOp, Label, VirtualImmediate12,
16+
AllocatedAbstractOp, ConstantRegister, ControlFlowOp, JumpType, Label, VirtualImmediate12,
1717
VirtualImmediate18, VirtualImmediate24,
1818
},
1919
decl_engine::DeclRefFunction,
@@ -205,7 +205,10 @@ impl AbstractProgram {
205205
},
206206
// word 1.5
207207
AllocatedAbstractOp {
208-
opcode: Either::Right(ControlFlowOp::Jump(label)),
208+
opcode: Either::Right(ControlFlowOp::Jump {
209+
to: label,
210+
type_: JumpType::Unconditional,
211+
}),
209212
comment: String::new(),
210213
owning_span: None,
211214
},
@@ -255,7 +258,10 @@ impl AbstractProgram {
255258
fn append_jump_to_entry(&mut self, asm: &mut AllocatedAbstractInstructionSet) {
256259
let entry = self.entries.iter().find(|x| x.name == "__entry").unwrap();
257260
asm.ops.push(AllocatedAbstractOp {
258-
opcode: Either::Right(ControlFlowOp::Jump(entry.label)),
261+
opcode: Either::Right(ControlFlowOp::Jump {
262+
to: entry.label,
263+
type_: JumpType::Unconditional,
264+
}),
259265
comment: "jump to ABI function selector".into(),
260266
owning_span: None,
261267
});
@@ -341,15 +347,21 @@ impl AbstractProgram {
341347
// Jump to the function label if the selector was equal.
342348
asm.ops.push(AllocatedAbstractOp {
343349
// If the comparison result is _not_ equal to 0, then it was indeed equal.
344-
opcode: Either::Right(ControlFlowOp::JumpIfNotZero(CMP_RESULT_REG, entry.label)),
350+
opcode: Either::Right(ControlFlowOp::Jump {
351+
to: entry.label,
352+
type_: JumpType::NotZero(CMP_RESULT_REG),
353+
}),
345354
comment: "[function selection]: jump to selected contract function".into(),
346355
owning_span: None,
347356
});
348357
}
349358

350359
if let Some(fallback_fn) = fallback_fn {
351360
asm.ops.push(AllocatedAbstractOp {
352-
opcode: Either::Right(ControlFlowOp::Call(fallback_fn)),
361+
opcode: Either::Right(ControlFlowOp::Jump {
362+
to: fallback_fn,
363+
type_: JumpType::Call,
364+
}),
353365
comment: "[function selection]: call contract fallback function".into(),
354366
owning_span: None,
355367
});

sway-core/src/asm_generation/fuel/programs/allocated.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ impl AllocatedProgram {
3232
.collect(),
3333
};
3434

35+
let far_jump_indices = abstract_ops.collect_far_jumps();
3536
let (realized_ops, mut label_offsets) =
36-
abstract_ops.realize_labels(&mut self.data_section)?;
37+
abstract_ops.realize_labels(&mut self.data_section, &far_jump_indices)?;
3738
let ops = realized_ops.allocated_ops();
3839

3940
// Collect the entry point offsets.

0 commit comments

Comments
 (0)