Open
Description
We should
- Remove parameters if they are never passed
- Remove parameters they are never used
- Remove parameters if TFA inferred the value to be constant
- Make parameters mandatory if they are passed on every call site
- Make parameters mandatory if we believe it benefits code size (and re-write call sites to pass the default value): We can have a heuristic for doing this.
Update:
- Do the same for type arguments (if caller passes always same value, ...)
if certain conditions are met (e.g. we know all call sites, ...)
Here's a concrete example: We should turn this
main() {
final l = <Base>[A(), B(), C()];
l[0].foo(alwaysPassed: 'a', constant: 10);
l[1].foo(alwaysPassed: 'b');
l[2].foo(alwaysPassed: 'c', unused: bar());
}
abstract class Base {
foo({alwaysPassed, constant: 10, unused});
}
class A extends Base {
foo({alwaysPassed, constant: 10, unused}) =>
print('A($constant $alwaysPassed)');
}
class B extends Base {
foo({alwaysPassed, constant: 10, unused}) =>
print('B($constant $alwaysPassed)');
}
class C extends Base {
foo({alwaysPassed, constant: 10, unused}) =>
print('C($constant $alwaysPassed)');
}
@pragma('vm:never-inline')
bar() {}
into this
main() {
final l = <Base>[A(), B(), C()];
l[0].foo('a');
l[1].foo('b');
final tmp = l[2];
bar();
tmp.foo('c');
}
abstract class Base {
foo(alwaysPassed);
}
class A extends Base {
foo(alwaysPassed) => print('A(${10} $alwaysPassed)');
}
class B extends Base {
foo(alwaysPassed) => print('B(${10} $alwaysPassed)');
}
class C extends Base {
foo(alwaysPassed) => print('C(${10} $alwaysPassed)');
}
@pragma('vm:never-inline')
bar() {}
That would reduce
*** BEGIN CFG
After SerializeGraph
==== file:///.../test.dart_A_foo
B0[graph]:0 {
v0 <- Constant(#null) T{Null?}
v1 <- Constant(#<optimized out>) T{_OneByteString}
v8 <- Constant(#1) [1, 1] T{_Smi}
v11 <- Constant(#true) T{bool}
v22 <- Constant(#0) [0, 0] T{_Smi}
v23 <- Constant(#2) [2, 2] T{_Smi}
v26 <- Constant(#3) [3, 3] T{_Smi}
v31 <- Constant(#alwaysPassed) T{_OneByteString}
v42 <- Constant(#constant) T{_OneByteString}
v43 <- Constant(#10) [10, 10] T{_Smi}
v52 <- Constant(#unused) T{_OneByteString}
v55 <- Constant(#5) [5, 5] T{_Smi}
v58 <- Constant(#A() T{_OneByteString}
v59 <- Constant(# ) T{_OneByteString}
v60 <- Constant(#4) [4, 4] T{_Smi}
v61 <- Constant(#)) T{_OneByteString}
}
B1[function entry]:2 {
v2 <- SpecialParameter(ArgDescriptor) T{_ImmutableList}
}
0: v4 <- LoadField(v2 . ArgumentsDescriptor.positional_count {final}) [0, 4611686018427387903] T{_Smi}
1: v6 <- LoadField(v2 . ArgumentsDescriptor.count {final}) [0, 4611686018427387903] T{_Smi}
Branch if RelationalOp:6(<=, v8, v4) T{bool} goto (3, 4)
B3[target]:12
Branch if RelationalOp:16(<=, v4, v8) T{bool} goto (5, 6)
B5[target]:22
v18 <- BinarySmiOp:30(- [tr], v6, v8) [-1, 4611686018427387902] T{_Smi}
2: v29 <- LoadIndexed(v2, v26) T{*?}
Branch if StrictCompare:36(===, v29, v31) goto (7, 8)
B7[target]:40
3: v86 <- LoadIndexed(v2, v60) T{*?}
v88 <- BinarySmiOp:48(- [tr], v6, v86) [-4611686018427387903, 4611686018427387903] T{_Smi}
v90 <- LoadIndexedUnsafe(rbp[v88 + 8]) T{*?}
goto:52 B9
B8[target]:42
goto:54 B9
B9[join]:44 pred(B7, B8) {
v32 <- phi(v90, v0) alive T{*?}
v34 <- phi(v8, v22) alive [0, 1] T{_Smi}
}
v94 <- BinarySmiOp:56(<< [tr], v34, v8) [0, 2] T{_Smi}
v38 <- BinarySmiOp:58(+ [tr], v94, v26) [3, 5] T{_Smi}
4: v40 <- LoadIndexed(v2, v38) T{*?}
Branch if StrictCompare:60(===, v40, v42) goto (10, 11)
B10[target]:64
v82 <- BinarySmiOp:74(+ [tr], v34, v8) [1, 2] T{_Smi}
goto:76 B12
B11[target]:66
goto:78 B12
B12[join]:68 pred(B10, B11) {
v44 <- phi(v82, v34) alive [0, 2] T{_Smi}
}
v96 <- BinarySmiOp:80(<< [tr], v44, v8) [0, 4] T{_Smi}
v48 <- BinarySmiOp:82(+ [tr], v96, v26) [3, 7] T{_Smi}
5: v50 <- LoadIndexed(v2, v48) T{*?}
Branch if StrictCompare:84(===, v50, v52) goto (13, 14)
B13[target]:88
v72 <- BinarySmiOp:98(+ [tr], v44, v8) [1, 3] T{_Smi}
goto:100 B15
B14[target]:90
goto:102 B15
B15[join]:92 pred(B13, B14) {
v53 <- phi(v72, v44) alive [0, 3] T{_Smi}
}
Branch if StrictCompare:104(===, v18, v53) goto (18, 17)
B18[target]:108
CheckStackOverflow:118(stack=0, loop=0)
v56 <- CreateArray:122(v0, v55) T{_List}
6: StoreIndexed(v56, v22, v58)
7: StoreIndexed(v56, v8, v43)
8: StoreIndexed(v56, v23, v59)
9: StoreIndexed(v56, v26, v32)
10: StoreIndexed(v56, v60, v61)
v62 <- StringInterpolate:124(v56) T{String}
StaticCall:126( print<0> v62, result_type = T{Null?})
Return:130(v0)
B17[target]:110
goto:112 B2
B6[target]:24
goto:28 B2
B4[target]:14
goto:26 B2
B2[join]:4 pred(B4, B6, B17)
TailCall(CallClosureNoSuchMethod(v2))
*** END CFG
to
*** BEGIN CFG
==== file:///.../test.dart_A_foo
B0[graph]:0 {
v0 <- Constant(#null) T{Null?}
v1 <- Constant(#<optimized out>) T{_OneByteString}
v4 <- Constant(#5) [5, 5] T{_Smi}
v7 <- Constant(#0) [0, 0] T{_Smi}
v8 <- Constant(#A() T{_OneByteString}
v9 <- Constant(#1) [1, 1] T{_Smi}
v10 <- Constant(#10) [10, 10] T{_Smi}
v11 <- Constant(#2) [2, 2] T{_Smi}
v12 <- Constant(# ) T{_OneByteString}
v13 <- Constant(#3) [3, 3] T{_Smi}
v14 <- Constant(#4) [4, 4] T{_Smi}
v15 <- Constant(#)) T{_OneByteString}
}
B1[function entry]:2 {
v2 <- Parameter(0) T{A}
v3 <- Parameter(1) T{_OneByteString}
}
CheckStackOverflow:8(stack=0, loop=0)
v5 <- CreateArray:12(v0, v4) T{_List}
0: StoreIndexed(v5, v7, v8)
1: StoreIndexed(v5, v9, v10)
2: StoreIndexed(v5, v11, v12)
3: StoreIndexed(v5, v13, v3)
4: StoreIndexed(v5, v14, v15)
v16 <- StringInterpolate:14(v5) T{String}
StaticCall:16( print<0> v16, result_type = T{Null?})
Return:20(v0)
*** END CFG
Given that flutter uses optional parameters pervasively this could give significant code size savings, and potential performance improvements.
@sjindel-google Can you take a look at this?