Skip to content

Commit a347631

Browse files
authored
arg and ret demotion passes should be module passes (#7343)
These passes modify functions other than the one they receive as an argument, so they cannot be function passes. Make them module passes instead.
1 parent be5871d commit a347631

File tree

4 files changed

+108
-88
lines changed

4 files changed

+108
-88
lines changed

sway-ir/src/optimize/arg_demotion.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
/// specific parameters.
55
use crate::{
66
AnalysisResults, Block, BlockArgument, Context, Function, InstOp, Instruction,
7-
InstructionInserter, IrError, Pass, PassMutability, ScopedPass, Type, Value, ValueDatum,
7+
InstructionInserter, IrError, Module, Pass, PassMutability, ScopedPass, Type, Value,
8+
ValueDatum,
89
};
910

1011
use rustc_hash::FxHashMap;
@@ -16,20 +17,25 @@ pub fn create_arg_demotion_pass() -> Pass {
1617
name: ARG_DEMOTION_NAME,
1718
descr: "Demotion of by-value function arguments to by-reference",
1819
deps: Vec::new(),
19-
runner: ScopedPass::FunctionPass(PassMutability::Transform(arg_demotion)),
20+
runner: ScopedPass::ModulePass(PassMutability::Transform(arg_demotion)),
2021
}
2122
}
2223

2324
pub fn arg_demotion(
2425
context: &mut Context,
2526
_: &AnalysisResults,
26-
function: Function,
27+
module: Module,
2728
) -> Result<bool, IrError> {
28-
let mut result = fn_arg_demotion(context, function)?;
29-
30-
// We also need to be sure that block args within this function are demoted.
31-
for block in function.block_iter(context) {
32-
result |= demote_block_signature(context, &function, block);
29+
let mut result = false;
30+
// This is a module pass because modifying the signature of a function may affect the
31+
// call sites in other functions, requiring their modification as well.
32+
for function in module.function_iter(context) {
33+
result |= fn_arg_demotion(context, function)?;
34+
35+
// We also need to be sure that block args within this function are demoted.
36+
for block in function.block_iter(context) {
37+
result |= demote_block_signature(context, &function, block);
38+
}
3339
}
3440

3541
Ok(result)

sway-ir/src/optimize/ret_demotion.rs

Lines changed: 88 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/// return value is mem_copied to the new argument instead of being returned by value.
88
use crate::{
99
AnalysisResults, BlockArgument, Context, Function, InstOp, Instruction, InstructionInserter,
10-
IrError, Pass, PassMutability, ScopedPass, Type, Value,
10+
IrError, Module, Pass, PassMutability, ScopedPass, Type, Value,
1111
};
1212

1313
pub const RET_DEMOTION_NAME: &str = "ret-demotion";
@@ -17,91 +17,104 @@ pub fn create_ret_demotion_pass() -> Pass {
1717
name: RET_DEMOTION_NAME,
1818
descr: "Demotion of by-value function return values to by-reference",
1919
deps: Vec::new(),
20-
runner: ScopedPass::FunctionPass(PassMutability::Transform(ret_val_demotion)),
20+
runner: ScopedPass::ModulePass(PassMutability::Transform(ret_val_demotion)),
2121
}
2222
}
2323

2424
pub fn ret_val_demotion(
2525
context: &mut Context,
2626
_: &AnalysisResults,
27-
function: Function,
27+
module: Module,
2828
) -> Result<bool, IrError> {
29-
// Reject non-candidate.
30-
let ret_type = function.get_return_type(context);
31-
if !super::target_fuel::is_demotable_type(context, &ret_type) {
32-
// Return type fits in a register.
33-
return Ok(false);
34-
}
29+
// This is a module pass because we need to update all the callers of a function if we change
30+
// its signature.
31+
let mut changed = false;
32+
for function in module.function_iter(context) {
33+
// Reject non-candidate.
34+
let ret_type = function.get_return_type(context);
35+
if !super::target_fuel::is_demotable_type(context, &ret_type) {
36+
// Return type fits in a register.
37+
continue;
38+
}
39+
40+
changed = true;
41+
42+
// Change the function signature. It now returns a pointer.
43+
let ptr_ret_type = Type::new_typed_pointer(context, ret_type);
44+
function.set_return_type(context, ptr_ret_type);
45+
46+
// The storage for the return value must be determined. For entry-point functions it's a new
47+
// local and otherwise it's an extra argument.
48+
let entry_block = function.get_entry_block(context);
49+
let ptr_arg_val = if function.is_entry(context) {
50+
let ret_var = function.new_unique_local_var(
51+
context,
52+
"__ret_value".to_owned(),
53+
ret_type,
54+
None,
55+
false,
56+
);
57+
58+
// Insert the return value pointer at the start of the entry block.
59+
let get_ret_var =
60+
Value::new_instruction(context, entry_block, InstOp::GetLocal(ret_var));
61+
entry_block.prepend_instructions(context, vec![get_ret_var]);
62+
get_ret_var
63+
} else {
64+
let ptr_arg_val = Value::new_argument(
65+
context,
66+
BlockArgument {
67+
block: entry_block,
68+
idx: function.num_args(context),
69+
ty: ptr_ret_type,
70+
},
71+
);
72+
function.add_arg(context, "__ret_value", ptr_arg_val);
73+
entry_block.add_arg(context, ptr_arg_val);
74+
ptr_arg_val
75+
};
3576

36-
// Change the function signature. It now returns a pointer.
37-
let ptr_ret_type = Type::new_typed_pointer(context, ret_type);
38-
function.set_return_type(context, ptr_ret_type);
39-
40-
// The storage for the return value must be determined. For entry-point functions it's a new
41-
// local and otherwise it's an extra argument.
42-
let entry_block = function.get_entry_block(context);
43-
let ptr_arg_val = if function.is_entry(context) {
44-
let ret_var =
45-
function.new_unique_local_var(context, "__ret_value".to_owned(), ret_type, None, false);
46-
47-
// Insert the return value pointer at the start of the entry block.
48-
let get_ret_var = Value::new_instruction(context, entry_block, InstOp::GetLocal(ret_var));
49-
entry_block.prepend_instructions(context, vec![get_ret_var]);
50-
get_ret_var
51-
} else {
52-
let ptr_arg_val = Value::new_argument(
53-
context,
54-
BlockArgument {
55-
block: entry_block,
56-
idx: function.num_args(context),
57-
ty: ptr_ret_type,
58-
},
59-
);
60-
function.add_arg(context, "__ret_value", ptr_arg_val);
61-
entry_block.add_arg(context, ptr_arg_val);
62-
ptr_arg_val
63-
};
64-
65-
// Gather the blocks which are returning.
66-
let ret_blocks = function
67-
.block_iter(context)
68-
.filter_map(|block| {
69-
block.get_terminator(context).and_then(|term| {
70-
if let InstOp::Ret(ret_val, _ty) = term.op {
71-
Some((block, ret_val))
72-
} else {
73-
None
74-
}
77+
// Gather the blocks which are returning.
78+
let ret_blocks = function
79+
.block_iter(context)
80+
.filter_map(|block| {
81+
block.get_terminator(context).and_then(|term| {
82+
if let InstOp::Ret(ret_val, _ty) = term.op {
83+
Some((block, ret_val))
84+
} else {
85+
None
86+
}
87+
})
7588
})
76-
})
77-
.collect::<Vec<_>>();
78-
79-
// Update each `ret` to store the return value to the 'out' arg and then return the pointer.
80-
for (ret_block, ret_val) in ret_blocks {
81-
// This is a special case where we're replacing the terminator. We can just pop it off the
82-
// end of the block and add new instructions.
83-
let last_instr_pos = ret_block.num_instructions(context) - 1;
84-
let orig_ret_val = ret_block.get_instruction_at(context, last_instr_pos);
85-
ret_block.remove_instruction_at(context, last_instr_pos);
86-
let md_idx = orig_ret_val.and_then(|val| val.get_metadata(context));
87-
88-
ret_block
89-
.append(context)
90-
.store(ptr_arg_val, ret_val)
91-
.add_metadatum(context, md_idx);
92-
ret_block
93-
.append(context)
94-
.ret(ptr_arg_val, ptr_ret_type)
95-
.add_metadatum(context, md_idx);
96-
}
97-
98-
// If the function isn't an entry point we need to update all the callers to pass the extra
99-
// argument.
100-
if !function.is_entry(context) {
101-
update_callers(context, function, ret_type);
89+
.collect::<Vec<_>>();
90+
91+
// Update each `ret` to store the return value to the 'out' arg and then return the pointer.
92+
for (ret_block, ret_val) in ret_blocks {
93+
// This is a special case where we're replacing the terminator. We can just pop it off the
94+
// end of the block and add new instructions.
95+
let last_instr_pos = ret_block.num_instructions(context) - 1;
96+
let orig_ret_val = ret_block.get_instruction_at(context, last_instr_pos);
97+
ret_block.remove_instruction_at(context, last_instr_pos);
98+
let md_idx = orig_ret_val.and_then(|val| val.get_metadata(context));
99+
100+
ret_block
101+
.append(context)
102+
.store(ptr_arg_val, ret_val)
103+
.add_metadatum(context, md_idx);
104+
ret_block
105+
.append(context)
106+
.ret(ptr_arg_val, ptr_ret_type)
107+
.add_metadatum(context, md_idx);
108+
}
109+
110+
// If the function isn't an entry point we need to update all the callers to pass the extra
111+
// argument.
112+
if !function.is_entry(context) {
113+
update_callers(context, function, ret_type);
114+
}
102115
}
103116

104-
Ok(true)
117+
Ok(changed)
105118
}
106119

107120
fn update_callers(context: &mut Context, function: Function, ret_type: Type) {

test/src/e2e_vm_tests/test_programs/should_pass/language/const_generics/stdout.snap

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
source: test/src/snapshot/mod.rs
3+
assertion_line: 162
34
---
45
> forc build --path test/src/e2e_vm_tests/test_programs/should_pass/language/const_generics --release
56
exit status: 1
@@ -443,12 +444,12 @@ warning
443444
____
444445

445446
Compiled script "const_generics" with 2 warnings.
446-
Finished debug [unoptimized + fuel] target(s) [9.552 KB] in ???
447+
Finished debug [unoptimized + fuel] target(s) [9.56 KB] in ???
447448
Running 1 test, filtered 0 tests
448449

449450
tested -- const_generics
450451

451-
test run_main ... ok (???, 17570 gas)
452+
test run_main ... ok (???, 17574 gas)
452453
debug output:
453454
[src/main.sw:105:13] a = [1, 2]
454455
[src/main.sw:109:13] [C {}].len() = 1

test/src/e2e_vm_tests/test_programs/should_pass/language/panic_expression/panicking_lib/stdout.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ output:
88
Building test/src/e2e_vm_tests/test_programs/should_pass/language/panic_expression/panicking_lib
99
Compiling library std (test/src/e2e_vm_tests/reduced_std_libs/sway-lib-std-core)
1010
Compiling library panicking_lib (test/src/e2e_vm_tests/test_programs/should_pass/language/panic_expression/panicking_lib)
11-
Finished release [optimized + fuel] target(s) [5.656 KB] in ???
11+
Finished release [optimized + fuel] target(s) [5.672 KB] in ???
1212
Running 18 tests, filtered 0 tests
1313

1414
tested -- panicking_lib
@@ -65,14 +65,14 @@ AsciiString { data: "generic panic with string" }, log rb: 10098701174489624218
6565
└─ panicked in: panicking_lib, src/lib.sw:74:5
6666
decoded log values:
6767
AsciiString { data: "generic panic different string" }, log rb: 10098701174489624218
68-
test test_generic_panic_with_error_type_enum_variant ... ok (???, 293 gas)
68+
test test_generic_panic_with_error_type_enum_variant ... ok (???, 297 gas)
6969
revert code: ffffffff00000004
7070
├─ panic message: Error A.
7171
├─ panic value: A
7272
└─ panicked in: panicking_lib, src/lib.sw:74:5
7373
decoded log values:
7474
A, log rb: 2721958641300806892
75-
test test_generic_panic_with_error_type_enum_different_variant_same_revert_code ... ok (???, 293 gas)
75+
test test_generic_panic_with_error_type_enum_different_variant_same_revert_code ... ok (???, 297 gas)
7676
revert code: ffffffff00000004
7777
├─ panic message: Error A.
7878
├─ panic value: A

0 commit comments

Comments
 (0)