Skip to content

Commit ffde158

Browse files
Clement Skaucommit-bot@chromium.org
Clement Skau
authored and
commit-bot@chromium.org
committed
[VM] Removes types from reused temps in async.
When we detect that a temporary variable is being reused with a different type, we change the type of the temp to dynamic. This allows us to retain types for any non-reused temps. For any dynamic temps we still have type information available for use sites, which we can pass on via unsafeCast. One drawback of this approach is that we don't know ahead of time which temps are reused, and get turned into dynamic, so we have to unsafeCast all uses. This change is similar to what was done in: https://dart-review.googlesource.com/c/sdk/+/138500 TEST=Adds tests/language/vm/regress_flutter_85311_test.dart, updates relevant expect files. Bug: flutter/flutter#85311 Change-Id: I821c5266327892d5c3fd5bae1bebba7f3fe3931b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/205647 Commit-Queue: Clement Skau <cskau@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
1 parent 284695f commit ffde158

16 files changed

+149
-61
lines changed

pkg/front_end/testcases/general/async_nested.dart.weak.transformed.expect

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ static method main() → void /* originally async */ {
3636
core::int* :await_jump_var = 0;
3737
dynamic :await_ctx_var;
3838
dynamic :saved_try_context_var0;
39-
dynamic :async_temporary_0;
40-
dynamic :async_temporary_1;
41-
dynamic :async_temporary_2;
39+
self::Node* :async_temporary_0;
40+
self::Node* :async_temporary_1;
41+
self::Node* :async_temporary_2;
4242
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
4343
try {
4444
#L1:

pkg/front_end/testcases/general/await_complex.dart.weak.transformed.expect

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ static method staticMembers() → dynamic /* originally async */ {
6565
core::int* :await_jump_var = 0;
6666
dynamic :await_ctx_var;
6767
dynamic :saved_try_context_var0;
68-
dynamic :async_temporary_0;
69-
dynamic :async_temporary_1;
70-
dynamic :async_temporary_2;
71-
dynamic :async_temporary_3;
72-
dynamic :async_temporary_4;
73-
dynamic :async_temporary_5;
68+
core::int* :async_temporary_0;
69+
core::int* :async_temporary_1;
70+
core::int* :async_temporary_2;
71+
core::int* :async_temporary_3;
72+
core::int* :async_temporary_4;
73+
core::int* :async_temporary_5;
7474
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
7575
try {
7676
#L1:
@@ -121,11 +121,11 @@ static method topLevelMembers() → dynamic /* originally async */ {
121121
core::int* :await_jump_var = 0;
122122
dynamic :await_ctx_var;
123123
dynamic :saved_try_context_var0;
124-
dynamic :async_temporary_0;
125-
dynamic :async_temporary_1;
126-
dynamic :async_temporary_2;
127-
dynamic :async_temporary_3;
128-
dynamic :async_temporary_4;
124+
core::int* :async_temporary_0;
125+
core::int* :async_temporary_1;
126+
core::int* :async_temporary_2;
127+
core::int* :async_temporary_3;
128+
core::int* :async_temporary_4;
129129
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
130130
try {
131131
#L2:
@@ -172,11 +172,11 @@ static method instanceMembers() → dynamic /* originally async */ {
172172
core::int* :await_jump_var = 0;
173173
dynamic :await_ctx_var;
174174
dynamic :saved_try_context_var0;
175-
dynamic :async_temporary_0;
176-
dynamic :async_temporary_1;
177-
dynamic :async_temporary_2;
178-
dynamic :async_temporary_3;
179-
dynamic :async_temporary_4;
175+
core::int* :async_temporary_0;
176+
core::int* :async_temporary_1;
177+
core::int* :async_temporary_2;
178+
core::int* :async_temporary_3;
179+
core::int* :async_temporary_4;
180180
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
181181
try {
182182
#L3:
@@ -225,10 +225,10 @@ static method others() → dynamic /* originally async */ {
225225
dynamic :await_ctx_var;
226226
dynamic :saved_try_context_var0;
227227
dynamic :async_temporary_0;
228-
dynamic :async_temporary_1;
229-
dynamic :async_temporary_2;
230-
dynamic :async_temporary_3;
231-
dynamic :async_temporary_4;
228+
core::int* :async_temporary_1;
229+
core::int* :async_temporary_2;
230+
core::List<core::int*>* :async_temporary_3;
231+
core::int* :async_temporary_4;
232232
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
233233
try {
234234
#L4:
@@ -288,13 +288,13 @@ static method conditionals() → dynamic /* originally async */ {
288288
core::bool* a = false;
289289
core::bool* b = true;
290290
:async_temporary_0 = (a || b) =={core::Object::==}{(core::Object) → core::bool} true;
291-
if(:async_temporary_0)
291+
if(_in::unsafeCast<core::bool*>(:async_temporary_0))
292292
;
293293
else {
294294
[yield] let dynamic #t22 = asy::_awaitHelper(self::dummy(), :async_op_then, :async_op_error, :async_op) in null;
295295
:async_temporary_0 = :result as{TypeError,ForDynamic} core::bool* =={core::Object::==}{(core::Object) → core::bool} true;
296296
}
297-
core::bool* c = :async_temporary_0;
297+
core::bool* c = _in::unsafeCast<core::bool*>(:async_temporary_0);
298298
self::expect(true, c);
299299
if(a || b) {
300300
:async_temporary_1 = a;
@@ -426,8 +426,8 @@ static method controlFlow() → dynamic /* originally async */ {
426426
dynamic :saved_try_context_var3;
427427
dynamic :exception0;
428428
dynamic :stack_trace0;
429-
dynamic :async_temporary_0;
430-
dynamic :async_temporary_1;
429+
core::List<dynamic>* :async_temporary_0;
430+
core::List<dynamic>* :async_temporary_1;
431431
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
432432
try {
433433
#L7:

pkg/front_end/testcases/general/bug33206.dart.weak.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ static method foo() → asy::Future<self::X*>* /* originally async */ {
105105
core::int* :await_jump_var = 0;
106106
dynamic :await_ctx_var;
107107
dynamic :saved_try_context_var0;
108-
dynamic :async_temporary_0;
108+
self::Y* :async_temporary_0;
109109
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
110110
try {
111111
#L3:

pkg/front_end/testcases/general/issue40662.dart.weak.transformed.expect

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ static method foo(core::int* x) → dynamic /* originally async */ {
1717
core::int* :await_jump_var = 0;
1818
dynamic :await_ctx_var;
1919
dynamic :saved_try_context_var0;
20-
dynamic :async_temporary_0;
21-
dynamic :async_temporary_1;
20+
core::int* :async_temporary_0;
21+
core::int* :async_temporary_1;
2222
core::List<core::int*>* :async_temporary_2;
23-
dynamic :async_temporary_3;
23+
core::int* :async_temporary_3;
2424
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
2525
try {
2626
#L1:
@@ -35,7 +35,7 @@ static method foo(core::int* x) → dynamic /* originally async */ {
3535
else {
3636
:async_temporary_2 = null;
3737
}
38-
:return_value = self::bar(_in::unsafeCast<core::int*>(:async_temporary_3), :async_temporary_2);
38+
:return_value = self::bar(_in::unsafeCast<core::int*>(:async_temporary_3), _in::unsafeCast<core::List<core::int*>*>(:async_temporary_2));
3939
break #L1;
4040
}
4141
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);
@@ -88,4 +88,4 @@ static method expect(dynamic expected, dynamic actual) → dynamic {
8888
Extra constant evaluation status:
8989
Evaluated: InstanceInvocation @ org-dartlang-testcase:///issue40662.dart:8:10 -> IntConstant(-1)
9090
Evaluated: InstanceInvocation @ org-dartlang-testcase:///issue40662.dart:9:10 -> IntConstant(-1)
91-
Extra constant evaluation: evaluated: 93, effectively constant: 2
91+
Extra constant evaluation: evaluated: 94, effectively constant: 2

pkg/front_end/testcases/general/regression_flutter51828.dart.weak.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static method main() → dynamic /* originally async */ {
9191
core::int* :await_jump_var = 0;
9292
dynamic :await_ctx_var;
9393
dynamic :saved_try_context_var0;
94-
dynamic :async_temporary_0;
94+
self::B* :async_temporary_0;
9595
dynamic :async_temporary_1;
9696
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
9797
try {

pkg/front_end/testcases/inference/future_then_conditional.dart.weak.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static method test() → void {
5656
[yield] let dynamic #t1 = asy::_awaitHelper(asy::Future::value<core::int*>(3), :async_op_then, :async_op_error, :async_op) in null;
5757
:async_temporary_0 = _in::unsafeCast<core::int*>(:result);
5858
}
59-
:return_value = :async_temporary_0;
59+
:return_value = _in::unsafeCast<core::int*>(:async_temporary_0);
6060
break #L1;
6161
}
6262
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);

pkg/front_end/testcases/inference/future_then_conditional_2.dart.weak.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static method test() → void {
5656
[yield] let dynamic #t1 = asy::_awaitHelper(new self::MyFuture::value<core::int*>(3), :async_op_then, :async_op_error, :async_op) in null;
5757
:async_temporary_0 = _in::unsafeCast<core::int*>(:result);
5858
}
59-
:return_value = :async_temporary_0;
59+
:return_value = _in::unsafeCast<core::int*>(:async_temporary_0);
6060
break #L1;
6161
}
6262
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);

pkg/front_end/testcases/inference/future_then_conditional_3.dart.weak.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static method test() → void {
5656
[yield] let dynamic #t1 = asy::_awaitHelper(asy::Future::value<core::int*>(3), :async_op_then, :async_op_error, :async_op) in null;
5757
:async_temporary_0 = _in::unsafeCast<core::int*>(:result);
5858
}
59-
:return_value = :async_temporary_0;
59+
:return_value = _in::unsafeCast<core::int*>(:async_temporary_0);
6060
break #L1;
6161
}
6262
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);

pkg/front_end/testcases/inference/future_then_conditional_4.dart.weak.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static method test() → void {
5656
[yield] let dynamic #t1 = asy::_awaitHelper(new self::MyFuture::value<core::int*>(3), :async_op_then, :async_op_error, :async_op) in null;
5757
:async_temporary_0 = _in::unsafeCast<core::int*>(:result);
5858
}
59-
:return_value = :async_temporary_0;
59+
:return_value = _in::unsafeCast<core::int*>(:async_temporary_0);
6060
break #L1;
6161
}
6262
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);

pkg/front_end/testcases/inference/future_then_conditional_5.dart.weak.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static method test() → void {
5656
[yield] let dynamic #t1 = asy::_awaitHelper(new self::MyFuture::value<core::int*>(3), :async_op_then, :async_op_error, :async_op) in null;
5757
:async_temporary_0 = _in::unsafeCast<core::int*>(:result);
5858
}
59-
:return_value = :async_temporary_0;
59+
:return_value = _in::unsafeCast<core::int*>(:async_temporary_0);
6060
break #L1;
6161
}
6262
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);

pkg/front_end/testcases/inference/future_then_conditional_6.dart.weak.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static method test() → void {
5656
[yield] let dynamic #t1 = asy::_awaitHelper(asy::Future::value<core::int*>(3), :async_op_then, :async_op_error, :async_op) in null;
5757
:async_temporary_0 = _in::unsafeCast<core::int*>(:result);
5858
}
59-
:return_value = :async_temporary_0;
59+
:return_value = _in::unsafeCast<core::int*>(:async_temporary_0);
6060
break #L1;
6161
}
6262
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);

pkg/front_end/testcases/inference/future_then_ifNull.dart.weak.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ static method test() → void {
5757
else {
5858
:async_temporary_0 = #t1;
5959
}
60-
:return_value = :async_temporary_0;
60+
:return_value = _in::unsafeCast<core::int*>(:async_temporary_0);
6161
break #L1;
6262
}
6363
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);

pkg/kernel/lib/transformations/async.dart

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -115,21 +115,40 @@ class ExpressionLifter extends Transformer {
115115
return result;
116116
}
117117

118+
// Wraps VariableGet in an unsafeCast if `type` isn't dynamic.
119+
Expression unsafeCastVariableGet(
120+
VariableDeclaration variable, DartType type) {
121+
if (type != const DynamicType()) {
122+
return StaticInvocation(
123+
continuationRewriter.helper.unsafeCast,
124+
Arguments(<Expression>[VariableGet(variable)],
125+
types: <DartType>[type]));
126+
}
127+
return VariableGet(variable);
128+
}
129+
118130
// Name an expression by emitting an assignment to a temporary variable.
119131
Expression name(Expression expr) {
120-
// Allocate as dynamic as temps might be reused with different types.
121-
VariableDeclaration temp =
122-
allocateTemporary(nameIndex, const DynamicType());
123-
statements.add(ExpressionStatement(VariableSet(temp, expr)));
124-
// Type annotate the get via an unsafe cast since all temps are allocated
125-
// as dynamic.
126132
DartType type = expr.getStaticType(_staticTypeContext);
127-
return StaticInvocation(continuationRewriter.helper.unsafeCast,
128-
Arguments(<Expression>[VariableGet(temp)], types: <DartType>[type]));
133+
VariableDeclaration temp = allocateTemporary(nameIndex, type);
134+
statements.add(ExpressionStatement(VariableSet(temp, expr)));
135+
// Wrap in unsafeCast to make sure we pass type information even if we later
136+
// have to re-type the temporary variable to dynamic.
137+
return unsafeCastVariableGet(temp, type);
129138
}
130139

131140
VariableDeclaration allocateTemporary(int index,
132141
[DartType type = const DynamicType()]) {
142+
if (variables.length > index) {
143+
// Re-type temporary to dynamic if we detect reuse with different type.
144+
// Note: We should make sure all uses use `unsafeCast(...)` to pass their
145+
// type information on, as that is lost otherwise.
146+
if (variables[index].type != const DynamicType() &&
147+
variables[index].type != type) {
148+
variables[index].type = const DynamicType();
149+
}
150+
return variables[index];
151+
}
133152
for (var i = variables.length; i <= index; i++) {
134153
variables.add(VariableDeclaration(":async_temporary_${i}", type: type));
135154
}
@@ -384,10 +403,9 @@ class ExpressionLifter extends Transformer {
384403
// so any statements it emits occur after in the accumulated list (that is,
385404
// so they occur before in the corresponding block).
386405
var rightBody = blockOf(rightStatements);
387-
var result = allocateTemporary(
388-
nameIndex,
389-
_staticTypeContext.typeEnvironment.coreTypes
390-
.boolRawType(_staticTypeContext.nonNullable));
406+
final type = _staticTypeContext.typeEnvironment.coreTypes
407+
.boolRawType(_staticTypeContext.nonNullable);
408+
final result = allocateTemporary(nameIndex, type);
391409
final objectEquals = continuationRewriter.helper.coreTypes.objectEquals;
392410
rightBody.addStatement(new ExpressionStatement(new VariableSet(
393411
result,
@@ -402,7 +420,8 @@ class ExpressionLifter extends Transformer {
402420
then = new EmptyStatement();
403421
otherwise = rightBody;
404422
}
405-
statements.add(new IfStatement(new VariableGet(result), then, otherwise));
423+
statements.add(
424+
new IfStatement(unsafeCastVariableGet(result, type), then, otherwise));
406425

407426
final test = new EqualsCall(expr.left, new BoolLiteral(true),
408427
interfaceTarget: objectEquals,
@@ -414,7 +433,7 @@ class ExpressionLifter extends Transformer {
414433

415434
++nameIndex;
416435
seenAwait = seenAwait || rightAwait;
417-
return new VariableGet(result);
436+
return unsafeCastVariableGet(result, type);
418437
}
419438

420439
TreeNode visitConditionalExpression(ConditionalExpression expr) {
@@ -455,15 +474,15 @@ class ExpressionLifter extends Transformer {
455474
});
456475
}
457476

458-
// If then or otherwise has emitted statements we will produce a temporary t
459-
// and emit:
477+
// If `then` or `otherwise` has emitted statements we will produce a
478+
// temporary t and emit:
460479
//
461480
// if ([condition]) {
462481
// t = [left];
463482
// } else {
464483
// t = [right];
465484
// }
466-
var result = allocateTemporary(nameIndex, expr.staticType);
485+
final result = allocateTemporary(nameIndex, expr.staticType);
467486
var thenBody = blockOf(thenStatements);
468487
var otherwiseBody = blockOf(otherwiseStatements);
469488
thenBody.addStatement(
@@ -478,7 +497,7 @@ class ExpressionLifter extends Transformer {
478497

479498
++nameIndex;
480499
seenAwait = seenAwait || thenAwait || otherwiseAwait;
481-
return new VariableGet(result);
500+
return unsafeCastVariableGet(result, expr.staticType);
482501
}
483502

484503
// Others.

pkg/vm/testcases/transformations/type_flow/transformer/async_await.dart.expect

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ library #lib;
22
import self as self;
33
import "dart:core" as core;
44
import "dart:async" as asy;
5-
import "dart:_internal" as _in;
65

76
class A extends core::Object {
87
synthetic constructor •() → self::A*
@@ -86,7 +85,7 @@ static method main() → dynamic /* originally async */ {
8685
{
8786
:async_temporary_0 = [@vm.inferred-type.metadata=#lib::A] self::foo();
8887
[yield] let dynamic #t1 = asy::_awaitHelper([@vm.inferred-type.metadata=dart.async::_Future<dynamic>] self::baz(), :async_op_then, :async_op_error, :async_op) in null;
89-
[yield] let dynamic #t2 = asy::_awaitHelper([@vm.direct-call.metadata=#lib::A.bar??] [@vm.inferred-type.metadata=dart.async::_Future<dynamic> (receiver not int)] _in::unsafeCast<dynamic>(:async_temporary_0){dynamic}.bar(:result), :async_op_then, :async_op_error, :async_op) in null;
88+
[yield] let dynamic #t2 = asy::_awaitHelper([@vm.direct-call.metadata=#lib::A.bar??] [@vm.inferred-type.metadata=dart.async::_Future<dynamic> (receiver not int)] :async_temporary_0{dynamic}.bar(:result), :async_op_then, :async_op_error, :async_op) in null;
9089
:result;
9190
}
9291
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// This is a regression test for the bug in
6+
// https://github.com/flutter/flutter/issues/51828.
7+
// Verifies that temporaries aren't incorrectly assigned types when they're
8+
// reused.
9+
10+
import "package:expect/expect.dart";
11+
12+
bool wasCalled = false;
13+
14+
class Z {
15+
bool operator ==(Object other) {
16+
wasCalled = true;
17+
return true;
18+
}
19+
}
20+
21+
class Y {
22+
final dynamic v;
23+
Y(this.v);
24+
}
25+
26+
Future<bool> crash(dynamic y) async {
27+
return (y.v == (await 7) || y == (await 9));
28+
}
29+
30+
void main() async {
31+
await crash(Y(Z()));
32+
33+
Expect.isTrue(wasCalled);
34+
}

0 commit comments

Comments
 (0)