[vm] Re-land use of multiple entrypoints for closure calls.
Original patchset is in revision 1.
Change-Id: I29ab0dbc3711f99895fd4f04a49d4185463a1602
Cq-Include-Trybots: luci.dart.try:vm-kernel-win-release-x64-try,vm-kernel-optcounter-threshold-linux-release-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-release-simarm-try,vm-kernel-precomp-linux-release-simarm64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-win-release-x64-try,vm-kernel-linux-release-simarm-try,vm-kernel-linux-release-simarm64-try,vm-kernel-reload-linux-debug-x64-try,vm-kernel-reload-linux-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/71241
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/pkg/vm/lib/transformations/call_site_annotator.dart b/pkg/vm/lib/transformations/call_site_annotator.dart
index dd87e57..3d80936 100644
--- a/pkg/vm/lib/transformations/call_site_annotator.dart
+++ b/pkg/vm/lib/transformations/call_site_annotator.dart
@@ -65,9 +65,12 @@
}
// TODO(vegorov) handle setters as well.
+ // TODO(34162): We don't need to save the type here, just whether or not it's
+ // a statically-checked call.
static bool shouldAnnotate(MethodInvocation node) =>
- node.interfaceTarget != null &&
- hasGenericCovariantParameters(node.interfaceTarget);
+ (node.interfaceTarget != null &&
+ hasGenericCovariantParameters(node.interfaceTarget)) ||
+ node.name.name == "call";
/// Return [true] if the given list of [VariableDeclaration] contains
/// any annotated with generic-covariant-impl.
diff --git a/pkg/vm/testcases/bytecode/asserts.dart.expect b/pkg/vm/testcases/bytecode/asserts.dart.expect
index 3f5cf69..176424f 100644
--- a/pkg/vm/testcases/bytecode/asserts.dart.expect
+++ b/pkg/vm/testcases/bytecode/asserts.dart.expect
@@ -65,7 +65,7 @@
[7] = Null
}
]static method test2(() → core::bool condition, () → core::String message) → void {
- assert(condition.call(), message.call());
+ assert([@vm.call-site-attributes.metadata=receiverType:() → dart.core::bool] condition.call(), [@vm.call-site-attributes.metadata=receiverType:() → dart.core::String] message.call());
}
[@vm.bytecode=
Bytecode {
diff --git a/pkg/vm/testcases/bytecode/bootstrapping.dart.expect b/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
index 16c0efe..a20221a 100644
--- a/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
+++ b/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
@@ -365,7 +365,7 @@
}
] static get platformScript() → dynamic {
if(self::VMLibraryHooks::_cachedScript.{core::Object::==}(null) && !self::VMLibraryHooks::_computeScriptUri.{core::Object::==}(null)) {
- self::VMLibraryHooks::_cachedScript = self::VMLibraryHooks::_computeScriptUri.call();
+ self::VMLibraryHooks::_cachedScript = [@vm.call-site-attributes.metadata=receiverType:dynamic] self::VMLibraryHooks::_computeScriptUri.call();
}
return self::VMLibraryHooks::_cachedScript;
}
diff --git a/pkg/vm/testcases/bytecode/closures.dart.expect b/pkg/vm/testcases/bytecode/closures.dart.expect
index 6912332..3347fd6 100644
--- a/pkg/vm/testcases/bytecode/closures.dart.expect
+++ b/pkg/vm/testcases/bytecode/closures.dart.expect
@@ -474,13 +474,13 @@
core::print(<core::Type>[self::A::T1, self::A::T2, self::A::foo::T3, self::A::foo::T4, T5, T6, T7, T8]);
self::callWithArgs<self::A::T1, self::A::T2, self::A::foo::T3, self::A::foo::T4, T5, T6, T7, T8>();
};
- nested3.call();
+ [@vm.call-site-attributes.metadata=receiverType:() → dart.core::Null] nested3.call();
}
- nested2.call<self::C7, self::C8>();
- nested2.call<core::List<self::C7>, core::List<self::C8>>();
+ [@vm.call-site-attributes.metadata=receiverType:<T7 extends dart.core::Object = dynamic, T8 extends dart.core::Object = dynamic>() → void] nested2.call<self::C7, self::C8>();
+ [@vm.call-site-attributes.metadata=receiverType:<T7 extends dart.core::Object = dynamic, T8 extends dart.core::Object = dynamic>() → void] nested2.call<core::List<self::C7>, core::List<self::C8>>();
}
- nested1.call<self::C5, self::C6>();
- nested1.call<core::List<self::C5>, core::List<self::C6>>();
+ [@vm.call-site-attributes.metadata=receiverType:<T5 extends dart.core::Object = dynamic, T6 extends dart.core::Object = dynamic>() → void] nested1.call<self::C5, self::C6>();
+ [@vm.call-site-attributes.metadata=receiverType:<T5 extends dart.core::Object = dynamic, T6 extends dart.core::Object = dynamic>() → void] nested1.call<core::List<self::C5>, core::List<self::C6>>();
}
}
class B extends core::Object {
@@ -768,12 +768,12 @@
z = x.{core::num::+}(2);
w = this.{self::B::foo}.{core::num::+}(y);
}
- closure2.call();
+ [@vm.call-site-attributes.metadata=receiverType:() → void] closure2.call();
core::print(w);
}
};
- closure1.call(10);
- closure1.call(11);
+ [@vm.call-site-attributes.metadata=receiverType:(dart.core::int) → dart.core::Null] closure1.call(10);
+ [@vm.call-site-attributes.metadata=receiverType:(dart.core::int) → dart.core::Null] closure1.call(11);
core::print(y);
core::print(z);
}
@@ -784,7 +784,7 @@
() → core::Null closure3 = () → core::Null {
this.{self::B::foo} = x;
};
- closure3.call();
+ [@vm.call-site-attributes.metadata=receiverType:() → dart.core::Null] closure3.call();
}
}
}
@@ -1118,7 +1118,7 @@
() → core::Null inc = () → core::Null {
i = i.{core::num::+}(1);
};
- inc.call();
+ [@vm.call-site-attributes.metadata=receiverType:() → dart.core::Null] inc.call();
core::print(i);
}
}
@@ -1313,7 +1313,7 @@
(core::int) → core::Null inc = (core::int y) → core::Null {
x = x.{core::num::+}(y);
};
- inc.call(3);
+ [@vm.call-site-attributes.metadata=receiverType:(dart.core::int) → dart.core::Null] inc.call(3);
return x;
}
[@vm.bytecode=
diff --git a/pkg/vm/testcases/bytecode/super_calls.dart.expect b/pkg/vm/testcases/bytecode/super_calls.dart.expect
index d0e8cf4..64c96cf 100644
--- a/pkg/vm/testcases/bytecode/super_calls.dart.expect
+++ b/pkg/vm/testcases/bytecode/super_calls.dart.expect
@@ -205,7 +205,7 @@
[6] = Null
}
] method testSuperCallViaGetter() → dynamic
- return super.{self::Base1::bar}.call<core::int>("param");
+ return [@vm.call-site-attributes.metadata=receiverType:dynamic] super.{self::Base1::bar}.call<core::int>("param");
[@vm.bytecode=
Bytecode {
Entry 1
@@ -467,7 +467,7 @@
[14] = Null
}
] method testSuperCallViaGetter() → dynamic
- return super.{self::Base2::bar}.call<core::int>("param");
+ return [@vm.call-site-attributes.metadata=receiverType:dynamic] super.{self::Base2::bar}.call<core::int>("param");
[@vm.bytecode=
Bytecode {
Entry 1
diff --git a/pkg/vm/testcases/bytecode/try_blocks.dart.expect b/pkg/vm/testcases/bytecode/try_blocks.dart.expect
index 696f42d..bd7e762 100644
--- a/pkg/vm/testcases/bytecode/try_blocks.dart.expect
+++ b/pkg/vm/testcases/bytecode/try_blocks.dart.expect
@@ -522,7 +522,7 @@
y = 3;
}
}
- foo.call();
+ [@vm.call-site-attributes.metadata=receiverType:() → void] foo.call();
core::print(y);
}
on dynamic catch(final dynamic e, final core::StackTrace st) {
@@ -968,7 +968,7 @@
core::print(x);
core::print(y);
}
- foo.call();
+ [@vm.call-site-attributes.metadata=receiverType:() → void] foo.call();
continue #L4;
}
finally {
@@ -1293,7 +1293,7 @@
}
finally {
core::print(x);
- y.call();
+ [@vm.call-site-attributes.metadata=receiverType:dynamic] y.call();
}
}
[@vm.bytecode=
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/future_or.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/future_or.dart.expect
index f58857b..b75afef 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/future_or.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/future_or.dart.expect
@@ -35,7 +35,7 @@
self::foo2_a4(a4);
}
static method getDynamic() → dynamic
- return self::unknown.call();
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown.call();
static method main(core::List<core::String> args) → dynamic {
self::foo1([@vm.inferred-type.metadata=dart.async::_Future] asy::Future::value<self::B>(new self::B::•()), new self::B::•(), [@vm.inferred-type.metadata=dart.async::_Future] asy::Future::value<self::B>(new self::B::•()), new self::B::•());
self::foo2(self::getDynamic() as{TypeError} asy::Future<self::A>, self::getDynamic() as{TypeError} self::A, self::getDynamic() as{TypeError} asy::FutureOr<self::A>, self::getDynamic() as{TypeError} asy::FutureOr<self::A>);
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_field_initializer.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_field_initializer.dart.expect
index 740d76d..a8d6be6 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_field_initializer.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_field_initializer.dart.expect
@@ -64,7 +64,7 @@
[@vm.inferred-type.metadata=dart.core::Null?]static field core::Function unknown;
static field core::Object field1 = [@vm.inferred-type.metadata=!] self::getValue();
static method getDynamic() → dynamic
- return self::unknown.call();
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown.call();
static method getValue() → core::Object {
self::A aa = self::getDynamic() as{TypeError} self::A;
return [@vm.inferred-type.metadata=!] aa.{self::A::foo}();
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_class1.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_class1.dart.expect
index bee0c8e..9388a7d 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_class1.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_class1.dart.expect
@@ -33,7 +33,7 @@
static method use2([@vm.inferred-type.metadata=#lib::Intermediate] self::Intermediate i, [@vm.inferred-type.metadata=#lib::B?] self::A aa) → dynamic
return [@vm.direct-call.metadata=#lib::Intermediate::bar] [@vm.inferred-type.metadata=#lib::T1] i.{self::Intermediate::bar}(aa);
static method getDynamic() → dynamic
- return self::unknown.call();
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown.call();
static method allocateB() → dynamic {
new self::B::•();
}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_class2.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_class2.dart.expect
index 2a40057..b6e35fd 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_class2.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_class2.dart.expect
@@ -59,7 +59,7 @@
static method use3([@vm.inferred-type.metadata=#lib::Intermediate] self::Intermediate i, self::A aa) → dynamic
return [@vm.direct-call.metadata=#lib::Intermediate::bar] [@vm.inferred-type.metadata=!] i.{self::Intermediate::bar}(aa);
static method getDynamic() → dynamic
- return self::unknown.call();
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown.call();
static method allocateB() → dynamic {
new self::B::•();
}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_dynamic_target.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_dynamic_target.dart.expect
index 55d3d4e..3fd5f9f 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_dynamic_target.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_new_dynamic_target.dart.expect
@@ -49,7 +49,7 @@
static method use_bazz(dynamic x) → dynamic
return [@vm.inferred-type.metadata=#lib::T3] x.bazz();
static method getDynamic() → dynamic
- return self::unknown.call();
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown.call();
static method allocateA() → dynamic {
new self::A::•();
}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field.dart.expect
index 78b259d..10fdcc6 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field.dart.expect
@@ -51,7 +51,7 @@
static method use2([@vm.inferred-type.metadata=#lib::DeepCaller2] self::DeepCaller2 x, [@vm.inferred-type.metadata=#lib::A?] self::A aa) → dynamic
return [@vm.direct-call.metadata=#lib::DeepCaller2::barL1] [@vm.inferred-type.metadata=!] x.{self::DeepCaller2::barL1}(aa);
static method getDynamic() → dynamic
- return self::unknown.call();
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown.call();
static method setField2([@vm.inferred-type.metadata=#lib::A] self::A aa, [@vm.inferred-type.metadata=#lib::T2] dynamic value) → void {
[@vm.direct-call.metadata=#lib::A::field2] aa.{self::A::field2} = value;
}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect
index 86a3d0f..64585aa 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect
@@ -113,7 +113,7 @@
[@vm.inferred-type.metadata=#lib::D?]static field self::A dd = new self::D::•();
[@vm.inferred-type.metadata=dart.core::Null?]static field core::Function unknown;
static method getDynamic() → dynamic
- return self::unknown.call();
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown.call();
static method main(core::List<core::String> args) → dynamic {
core::print([@vm.direct-call.metadata=#lib::B::foo??] [@vm.inferred-type.metadata=#lib::T1] [@vm.inferred-type.metadata=#lib::B?] self::bb.{self::A::foo}());
core::print([@vm.direct-call.metadata=#lib::B::bar??] [@vm.inferred-type.metadata=#lib::T1] [@vm.inferred-type.metadata=#lib::B?] self::bb.{self::A::bar});
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/param_types_before_strong_mode_checks.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/param_types_before_strong_mode_checks.dart.expect
index 3ea010c..eb9c442 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/param_types_before_strong_mode_checks.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/param_types_before_strong_mode_checks.dart.expect
@@ -49,9 +49,9 @@
[@vm.direct-call.metadata=#lib::T2::foo??] t0.{self::T0::foo}();
}
static method getDynamic() → dynamic
- return self::unknown.call();
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown.call();
static method use(dynamic x) → dynamic
- return self::unknown.call(x);
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown.call(x);
static method main(core::List<core::String> args) → dynamic {
self::func1(self::getDynamic() as{TypeError} self::T0);
self::use(self::func2);
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter16182.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter16182.dart.expect
index 1ad7392..8ef426f 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter16182.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter16182.dart.expect
@@ -56,7 +56,7 @@
: super self::B2Base::•()
;
method doSuperCall() → void {
- [@vm.direct-call.metadata=#lib::A2::call] [@vm.inferred-type.metadata=#lib::A2] super.{self::B2Base::aa2}.call(1, 2, 3, 4, 5, new self::T2::•());
+ [@vm.call-site-attributes.metadata=receiverType:dynamic] [@vm.direct-call.metadata=#lib::A2::call] [@vm.inferred-type.metadata=#lib::A2] super.{self::B2Base::aa2}.call(1, 2, 3, 4, 5, new self::T2::•());
}
}
class T3 extends core::Object {
@@ -127,7 +127,7 @@
exp::Expect::isTrue([@vm.inferred-type.metadata=dart.core::bool?] self::ok);
}
static method getDynamic3() → dynamic
- return self::unknown3.call();
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown3.call();
static method test3() → void {
self::getDynamic3().aa3(1, 2, 3, 4, 5, 6, new self::T3::•());
self::ok = false;
@@ -135,7 +135,7 @@
exp::Expect::isTrue([@vm.inferred-type.metadata=dart.core::bool?] self::ok);
}
static method getDynamic4() → dynamic
- return self::unknown4.call();
+ return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown4.call();
static method test4() → void {
self::getDynamic4().aa4(1, 2, 3, 4, 5, 6, 7, new self::T4::•());
self::ok = false;
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/tear_off_dynamic_method.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/tear_off_dynamic_method.dart.expect
index 8492698..871430a 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/tear_off_dynamic_method.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/tear_off_dynamic_method.dart.expect
@@ -25,5 +25,5 @@
return new self::B::•();
static method main(core::List<core::String> args) → dynamic {
core::Function closure = () → self::B => new self::B::•();
- new self::TearOffDynamicMethod::•(closure.call());
+ new self::TearOffDynamicMethod::•([@vm.call-site-attributes.metadata=receiverType:dart.core::Function] closure.call());
}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/tear_off_super_method.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/tear_off_super_method.dart.expect
index 689ae8c..eed4b00 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/tear_off_super_method.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/tear_off_super_method.dart.expect
@@ -22,7 +22,7 @@
method foo() → core::int
return [@vm.direct-call.metadata=dart.core::_IntegerImplementation::+] [@vm.inferred-type.metadata=int?] 3.{core::num::+}([@vm.direct-call.metadata=#lib::B::foo] [@vm.inferred-type.metadata=int?] [@vm.inferred-type.metadata=#lib::B] self::knownResult().foo() as{TypeError} core::num) as{TypeError} core::int;
method doCall(dynamic x) → core::int
- return x.call() as{TypeError} core::int;
+ return [@vm.call-site-attributes.metadata=receiverType:dynamic] x.call() as{TypeError} core::int;
}
class TearOffSuperMethod extends self::Base {
synthetic constructor •() → void
diff --git a/runtime/tests/vm/dart/entrypoints/tearoff.dart b/runtime/tests/vm/dart/entrypoints/tearoff.dart
new file mode 100644
index 0000000..cdebd3a
--- /dev/null
+++ b/runtime/tests/vm/dart/entrypoints/tearoff.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Test that typed calls against tearoffs go into the unchecked entrypoint.
+
+import "package:expect/expect.dart";
+import "common.dart";
+
+class C<T> {
+ @NeverInline
+ @pragma("vm:testing.unsafe.trace-entrypoints-fn", validateTearoff)
+ void target1(T x, String y) {
+ Expect.notEquals(x, -1);
+ Expect.equals(y, "foo");
+ }
+}
+
+test(List<String> args) {
+ var f = (new C<int>()).target1;
+
+ // Warmup.
+ expectedEntryPoint = -1;
+ expectedTearoffEntryPoint = -1;
+ for (int i = 0; i < 100; ++i) {
+ f(i, "foo");
+ }
+
+ expectedEntryPoint = 0;
+ expectedTearoffEntryPoint = 1;
+ const int iterations = benchmarkMode ? 100000000 : 100;
+ for (int i = 0; i < iterations; ++i) {
+ f(i, "foo");
+ }
+
+ Expect.isTrue(validateRan);
+}
diff --git a/runtime/tests/vm/dart/entrypoints/tearoff_inline_test.dart b/runtime/tests/vm/dart/entrypoints/tearoff_inline_test.dart
new file mode 100644
index 0000000..843324a
--- /dev/null
+++ b/runtime/tests/vm/dart/entrypoints/tearoff_inline_test.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// VMOptions=--enable-testing-pragmas --no-background-compilation --enable-inlining-annotations --optimization-counter-threshold=10 -Denable_inlining=true
+
+import "tearoff.dart";
+
+main(args) => test(args);
diff --git a/runtime/tests/vm/dart/entrypoints/tearoff_noinline_test.dart b/runtime/tests/vm/dart/entrypoints/tearoff_noinline_test.dart
new file mode 100644
index 0000000..aac3f75
--- /dev/null
+++ b/runtime/tests/vm/dart/entrypoints/tearoff_noinline_test.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// VMOptions=--enable-testing-pragmas --no-background-compilation --enable-inlining-annotations --optimization-counter-threshold=10
+
+import "tearoff.dart";
+
+main(args) => test(args);
diff --git a/runtime/tests/vm/dart/entrypoints/tearoff_prologue.dart b/runtime/tests/vm/dart/entrypoints/tearoff_prologue.dart
new file mode 100644
index 0000000..8ce9916
--- /dev/null
+++ b/runtime/tests/vm/dart/entrypoints/tearoff_prologue.dart
@@ -0,0 +1,35 @@
+// No type checks are removed here, but we can skip the argument count check.
+
+import "package:expect/expect.dart";
+import "common.dart";
+
+class C<T> {
+ @NeverInline
+ @pragma("vm:testing.unsafe.trace-entrypoints-fn", validateTearoff)
+ void samir1(T x) {
+ if (x == -1) {
+ throw "oh no";
+ }
+ }
+}
+
+test(List<String> args) {
+ var c = new C<int>();
+ var f = c.samir1;
+
+ // Warmup.
+ expectedEntryPoint = -1;
+ expectedTearoffEntryPoint = -1;
+ for (int i = 0; i < 100; ++i) {
+ f(i);
+ }
+
+ expectedEntryPoint = 0;
+ expectedTearoffEntryPoint = 1;
+ int iterations = benchmarkMode ? 100000000 : 100;
+ for (int i = 0; i < iterations; ++i) {
+ f(i);
+ }
+
+ Expect.isTrue(validateRan);
+}
diff --git a/runtime/tests/vm/dart/entrypoints/tearoff_prologue_inline_test.dart b/runtime/tests/vm/dart/entrypoints/tearoff_prologue_inline_test.dart
new file mode 100644
index 0000000..3ccf462
--- /dev/null
+++ b/runtime/tests/vm/dart/entrypoints/tearoff_prologue_inline_test.dart
@@ -0,0 +1,5 @@
+// VMOptions=--enable-testing-pragmas --no-background-compilation --enable-inlining-annotations --optimization-counter-threshold=10 -Denable_inlining=true
+
+import "tearoff_prologue.dart";
+
+main(args) => test(args);
diff --git a/runtime/tests/vm/dart/entrypoints/tearoff_prologue_noinline_test.dart b/runtime/tests/vm/dart/entrypoints/tearoff_prologue_noinline_test.dart
new file mode 100644
index 0000000..4f1bbcd
--- /dev/null
+++ b/runtime/tests/vm/dart/entrypoints/tearoff_prologue_noinline_test.dart
@@ -0,0 +1,5 @@
+// VMOptions=--enable-testing-pragmas --no-background-compilation --enable-inlining-annotations --optimization-counter-threshold=10
+
+import "tearoff_prologue.dart";
+
+main(args) => test(args);
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index e4fc014..d767ec2 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -3193,12 +3193,15 @@
ClosureCallInstr(Value* function,
ClosureCallNode* node,
PushArgumentsArray* arguments,
- intptr_t deopt_id)
+ intptr_t deopt_id,
+ Code::EntryKind entry_kind = Code::EntryKind::kNormal)
: TemplateDartCall(deopt_id,
node->arguments()->type_args_len(),
node->arguments()->names(),
arguments,
- node->token_pos()) {
+ node->token_pos()),
+ entry_kind_(entry_kind) {
+ ASSERT(entry_kind != Code::EntryKind::kMonomorphic);
ASSERT(!arguments->is_empty());
SetInputAt(0, function);
}
@@ -3208,12 +3211,14 @@
intptr_t type_args_len,
const Array& argument_names,
TokenPosition token_pos,
- intptr_t deopt_id)
+ intptr_t deopt_id,
+ Code::EntryKind entry_kind = Code::EntryKind::kNormal)
: TemplateDartCall(deopt_id,
type_args_len,
argument_names,
arguments,
- token_pos) {
+ token_pos),
+ entry_kind_(entry_kind) {
ASSERT(!arguments->is_empty());
SetInputAt(0, function);
}
@@ -3227,9 +3232,13 @@
virtual bool HasUnknownSideEffects() const { return true; }
+ Code::EntryKind entry_kind() const { return entry_kind_; }
+
PRINT_OPERANDS_TO_SUPPORT
private:
+ const Code::EntryKind entry_kind_;
+
DISALLOW_COPY_AND_ASSIGN(ClosureCallInstr);
};
diff --git a/runtime/vm/compiler/backend/il_arm.cc b/runtime/vm/compiler/backend/il_arm.cc
index 446bde8..290267c 100644
--- a/runtime/vm/compiler/backend/il_arm.cc
+++ b/runtime/vm/compiler/backend/il_arm.cc
@@ -265,25 +265,14 @@
// R0: Function.
ASSERT(locs()->in(0).reg() == R0);
__ ldr(CODE_REG, FieldAddress(R0, Function::code_offset()));
- __ ldr(R2, FieldAddress(R0, Function::entry_point_offset()));
+ __ ldr(R2, FieldAddress(R0, Code::function_entry_point_offset(entry_kind())));
// R2: instructions entry point.
// R9: Smi 0 (no IC data; the lazy-compile stub expects a GC-safe value).
__ LoadImmediate(R9, 0);
__ blx(R2);
- compiler->RecordSafepoint(locs());
- compiler->EmitCatchEntryState();
- // Marks either the continuation point in unoptimized code or the
- // deoptimization point in optimized code, after call.
- const intptr_t deopt_id_after = Thread::ToDeoptAfter(deopt_id());
- if (compiler->is_optimizing()) {
- compiler->AddDeoptIndexAtCall(deopt_id_after);
- }
- // Add deoptimization continuation point after the call and before the
- // arguments are removed.
- // In optimized code this descriptor is needed for exception handling.
- compiler->AddCurrentDescriptor(RawPcDescriptors::kDeopt, deopt_id_after,
- token_pos());
+ compiler->EmitCallsiteMetadata(token_pos(), deopt_id(),
+ RawPcDescriptors::kOther, locs());
__ Drop(argument_count);
}
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index 18780af..dc73b6f 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -268,19 +268,8 @@
__ LoadImmediate(R5, 0);
//??
__ blr(R2);
- compiler->RecordSafepoint(locs());
- compiler->EmitCatchEntryState();
- // Marks either the continuation point in unoptimized code or the
- // deoptimization point in optimized code, after call.
- const intptr_t deopt_id_after = Thread::ToDeoptAfter(deopt_id());
- if (compiler->is_optimizing()) {
- compiler->AddDeoptIndexAtCall(deopt_id_after);
- }
- // Add deoptimization continuation point after the call and before the
- // arguments are removed.
- // In optimized code this descriptor is needed for exception handling.
- compiler->AddCurrentDescriptor(RawPcDescriptors::kDeopt, deopt_id_after,
- token_pos());
+ compiler->EmitCallsiteMetadata(token_pos(), deopt_id(),
+ RawPcDescriptors::kOther, locs());
__ Drop(argument_count);
}
diff --git a/runtime/vm/compiler/backend/il_ia32.cc b/runtime/vm/compiler/backend/il_ia32.cc
index b6d4586..bcc4b04 100644
--- a/runtime/vm/compiler/backend/il_ia32.cc
+++ b/runtime/vm/compiler/backend/il_ia32.cc
@@ -6147,18 +6147,8 @@
// ECX: Smi 0 (no IC data; the lazy-compile stub expects a GC-safe value).
__ xorl(ECX, ECX);
__ call(EBX);
- compiler->RecordSafepoint(locs());
- // Marks either the continuation point in unoptimized code or the
- // deoptimization point in optimized code, after call.
- const intptr_t deopt_id_after = Thread::ToDeoptAfter(deopt_id());
- if (compiler->is_optimizing()) {
- compiler->AddDeoptIndexAtCall(deopt_id_after);
- }
- // Add deoptimization continuation point after the call and before the
- // arguments are removed.
- // In optimized code this descriptor is needed for exception handling.
- compiler->AddCurrentDescriptor(RawPcDescriptors::kDeopt, deopt_id_after,
- token_pos());
+ compiler->EmitCallsiteMetadata(token_pos(), deopt_id(),
+ RawPcDescriptors::kOther, locs());
__ Drop(argument_count);
}
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index b1570d7..322e888 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -489,6 +489,9 @@
f->Print(", ");
PushArgumentAt(i)->value()->PrintTo(f);
}
+ if (entry_kind() == Code::EntryKind::kUnchecked) {
+ f->Print(" using unchecked entrypoint");
+ }
}
void InstanceCallInstr::PrintOperandsTo(BufferFormatter* f) const {
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index 1cf3331..7379b83 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -6238,26 +6238,16 @@
// Function in RAX.
ASSERT(locs()->in(0).reg() == RAX);
__ movq(CODE_REG, FieldAddress(RAX, Function::code_offset()));
- __ movq(RCX, FieldAddress(RAX, Function::entry_point_offset()));
+ __ movq(RCX,
+ FieldAddress(RAX, Code::function_entry_point_offset(entry_kind())));
// RAX: Function.
// R10: Arguments descriptor array.
// RBX: Smi 0 (no IC data; the lazy-compile stub expects a GC-safe value).
__ xorq(RBX, RBX);
__ call(RCX);
- compiler->RecordSafepoint(locs());
- compiler->EmitCatchEntryState();
- // Marks either the continuation point in unoptimized code or the
- // deoptimization point in optimized code, after call.
- const intptr_t deopt_id_after = Thread::ToDeoptAfter(deopt_id());
- if (compiler->is_optimizing()) {
- compiler->AddDeoptIndexAtCall(deopt_id_after);
- }
- // Add deoptimization continuation point after the call and before the
- // arguments are removed.
- // In optimized code this descriptor is needed for exception handling.
- compiler->AddCurrentDescriptor(RawPcDescriptors::kDeopt, deopt_id_after,
- token_pos());
+ compiler->EmitCallsiteMetadata(token_pos(), deopt_id(),
+ RawPcDescriptors::kOther, locs());
__ Drop(argument_count);
}
diff --git a/runtime/vm/compiler/backend/inliner.cc b/runtime/vm/compiler/backend/inliner.cc
index dace5a9..6988601 100644
--- a/runtime/vm/compiler/backend/inliner.cc
+++ b/runtime/vm/compiler/backend/inliner.cc
@@ -971,6 +971,9 @@
} else if (PolymorphicInstanceCallInstr* instr =
call_data->call->AsPolymorphicInstanceCall()) {
entry_kind = instr->instance_call()->entry_kind();
+ } else if (ClosureCallInstr* instr =
+ call_data->call->AsClosureCall()) {
+ entry_kind = instr->entry_kind();
}
kernel::FlowGraphBuilder builder(
parsed_function, *ic_data_array, /* not building var desc */ NULL,
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index b7124b2..0cac1db 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -1624,7 +1624,8 @@
call_hook += LoadLocal(entry_point_num);
call_hook += PushArgument();
call_hook += Constant(Function::ZoneHandle(Z, closure.function()));
- call_hook += B->ClosureCall(/*type_args_len=*/0, /*argument_count=*/3,
+ call_hook += B->ClosureCall(TokenPosition::kNoSource,
+ /*type_args_len=*/0, /*argument_count=*/3,
/*argument_names=*/Array::Handle());
call_hook += Drop(); // result of closure call
call_hook += Drop(); // entrypoint number
@@ -2388,10 +2389,13 @@
return flow_graph_builder_->LoadStaticField();
}
-Fragment StreamingFlowGraphBuilder::CheckNull(TokenPosition position,
- LocalVariable* receiver,
- const String& function_name) {
- return flow_graph_builder_->CheckNull(position, receiver, function_name);
+Fragment StreamingFlowGraphBuilder::CheckNull(
+ TokenPosition position,
+ LocalVariable* receiver,
+ const String& function_name,
+ bool clear_the_temp /* = true */) {
+ return flow_graph_builder_->CheckNull(position, receiver, function_name,
+ clear_the_temp);
}
Fragment StreamingFlowGraphBuilder::StaticCall(TokenPosition position,
@@ -2840,8 +2844,8 @@
}
for (intptr_t i = 0; i < list_length; ++i) {
String& name =
- H.DartSymbolObfuscate(ReadStringReference()); // read ith name index.
- instructions += BuildExpression(); // read ith expression.
+ H.DartSymbolObfuscate(ReadStringReference()); // read ith name index.
+ instructions += BuildExpression(); // read ith expression.
if (!skip_push_arguments) instructions += PushArgument();
if (do_drop) instructions += Drop();
if (argument_names != NULL) {
@@ -3452,6 +3456,11 @@
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
+#ifndef TARGET_ARCH_DBC
+ const CallSiteAttributesMetadata call_site_attributes =
+ call_site_attributes_metadata_helper_.GetCallSiteAttributes(offset);
+#endif
+
const Tag receiver_tag = PeekTag(); // peek tag for receiver.
if (IsNumberLiteral(receiver_tag) &&
(!optimizing() || constant_evaluator_.IsCached(offset))) {
@@ -3487,6 +3496,16 @@
SetOffset(before_branch_offset);
}
+ bool is_unchecked_closure_call = false;
+#ifndef TARGET_ARCH_DBC
+ if (call_site_attributes.receiver_type != nullptr &&
+ call_site_attributes.receiver_type->IsFunctionType()) {
+ AlternativeReadingScope alt(&reader_);
+ SkipExpression(); // skip receiver
+ is_unchecked_closure_call = ReadNameAsMethodName().Equals(Symbols::Call());
+ }
+#endif
+
Fragment instructions;
intptr_t type_args_len = 0;
@@ -3501,7 +3520,7 @@
const TypeArguments& type_arguments =
T.BuildTypeArguments(list_length); // read types.
instructions += TranslateInstantiatedTypeArguments(type_arguments);
- if (direct_call.check_receiver_for_null_) {
+ if (direct_call.check_receiver_for_null_ || is_unchecked_closure_call) {
// Don't yet push type arguments if we need to check receiver for null.
// In this case receiver will be duplicated so instead of pushing
// type arguments here we need to push it between receiver_temp
@@ -3538,7 +3557,7 @@
}
LocalVariable* receiver_temp = NULL;
- if (direct_call.check_receiver_for_null_) {
+ if (direct_call.check_receiver_for_null_ || is_unchecked_closure_call) {
// Duplicate receiver for CheckNull before it is consumed by PushArgument.
receiver_temp = MakeTemporary();
if (type_arguments_temp != NULL) {
@@ -3583,11 +3602,26 @@
Field::GetterSymbol(name) == interface_target->name()));
}
- if (direct_call.check_receiver_for_null_) {
- instructions += CheckNull(position, receiver_temp, name);
+ // TODO(sjindel): Avoid the check for null on unchecked closure calls if TFA
+ // allows.
+ if (direct_call.check_receiver_for_null_ || is_unchecked_closure_call) {
+ // Receiver temp is needed to load the function to call from the closure.
+ instructions += CheckNull(position, receiver_temp, name,
+ /*clear_temp=*/!is_unchecked_closure_call);
}
- if (!direct_call.target_.IsNull()) {
+ if (is_unchecked_closure_call) {
+ // Lookup the function in the closure.
+ instructions += LoadLocal(receiver_temp);
+ instructions += LoadField(Closure::function_offset());
+ if (parsed_function()->function().is_debuggable()) {
+ ASSERT(!parsed_function()->function().is_native());
+ instructions += DebugStepCheck(position);
+ }
+ instructions +=
+ B->ClosureCall(position, type_args_len, argument_count, argument_names,
+ /*use_unchecked_entry=*/true);
+ } else if (!direct_call.target_.IsNull()) {
ASSERT(FLAG_precompiled_mode);
instructions += StaticCall(position, direct_call.target_, argument_count,
argument_names, ICData::kNoRebind, &result_type,
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
index ebdf3a0..c4aecce 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
@@ -209,7 +209,8 @@
Fragment LoadStaticField();
Fragment CheckNull(TokenPosition position,
LocalVariable* receiver,
- const String& function_name);
+ const String& function_name,
+ bool clear_the_temp = true);
Fragment StaticCall(TokenPosition position,
const Function& target,
intptr_t argument_count,
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 769f2a9..53d17b4 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -356,10 +356,10 @@
const InferredTypeMetadata* result_type) {
const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0);
ArgumentArray arguments = GetArguments(total_count);
- InstanceCallInstr* call = new (Z) InstanceCallInstr(
- position, name, kind, arguments, type_args_len, argument_names,
- checked_argument_count, ic_data_array_, GetNextDeoptId(),
- interface_target);
+ InstanceCallInstr* call = new (Z)
+ InstanceCallInstr(position, name, kind, arguments, type_args_len,
+ argument_names, checked_argument_count, ic_data_array_,
+ GetNextDeoptId(), interface_target);
if ((result_type != NULL) && !result_type->IsTrivial()) {
call->SetResultType(Z, result_type->ToCompileType(Z));
}
@@ -367,15 +367,19 @@
return Fragment(call);
}
-Fragment FlowGraphBuilder::ClosureCall(intptr_t type_args_len,
+Fragment FlowGraphBuilder::ClosureCall(TokenPosition position,
+ intptr_t type_args_len,
intptr_t argument_count,
- const Array& argument_names) {
+ const Array& argument_names,
+ bool is_statically_checked) {
Value* function = Pop();
const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0);
ArgumentArray arguments = GetArguments(total_count);
ClosureCallInstr* call = new (Z)
ClosureCallInstr(function, arguments, type_args_len, argument_names,
- TokenPosition::kNoSource, GetNextDeoptId());
+ position, GetNextDeoptId(),
+ is_statically_checked ? Code::EntryKind::kUnchecked
+ : Code::EntryKind::kNormal);
Push(call);
return Fragment(call);
}
@@ -486,7 +490,8 @@
Fragment FlowGraphBuilder::CheckNull(TokenPosition position,
LocalVariable* receiver,
- const String& function_name) {
+ const String& function_name,
+ bool clear_the_temp /* = true */) {
Fragment instructions = LoadLocal(receiver);
CheckNullInstr* check_null =
@@ -494,11 +499,13 @@
instructions <<= check_null;
- // Null out receiver to make sure it is not saved into the frame before
- // doing the call.
- instructions += NullConstant();
- instructions += StoreLocal(TokenPosition::kNoSource, receiver);
- instructions += Drop();
+ if (clear_the_temp) {
+ // Null out receiver to make sure it is not saved into the frame before
+ // doing the call.
+ instructions += NullConstant();
+ instructions += StoreLocal(TokenPosition::kNoSource, receiver);
+ instructions += Drop();
+ }
return instructions;
}
@@ -1352,8 +1359,8 @@
body += LoadLocal(closure);
body += LoadField(Closure::function_offset());
- body += ClosureCall(descriptor.TypeArgsLen(), descriptor.Count(),
- argument_names);
+ body += ClosureCall(TokenPosition::kNoSource, descriptor.TypeArgsLen(),
+ descriptor.Count(), argument_names);
} else {
const intptr_t kNumArgsChecked = 1;
body += InstanceCall(TokenPosition::kMinSource, Symbols::Call(),
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h
index ee8efe8..49332d5 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.h
+++ b/runtime/vm/compiler/frontend/kernel_to_il.h
@@ -101,9 +101,11 @@
intptr_t checked_argument_count,
const Function& interface_target,
const InferredTypeMetadata* result_type = NULL);
- Fragment ClosureCall(intptr_t type_args_len,
+ Fragment ClosureCall(TokenPosition position,
+ intptr_t type_args_len,
intptr_t argument_count,
- const Array& argument_names);
+ const Array& argument_names,
+ bool use_unchecked_entry = false);
Fragment RethrowException(TokenPosition position, int catch_try_index);
Fragment LoadClassId();
Fragment LoadField(intptr_t offset, intptr_t class_id = kDynamicCid);
@@ -114,7 +116,8 @@
Fragment Return(TokenPosition position, bool omit_result_type_check = false);
Fragment CheckNull(TokenPosition position,
LocalVariable* receiver,
- const String& function_name);
+ const String& function_name,
+ bool clear_the_temp = true);
void SetResultTypeForStaticCall(StaticCallInstr* call,
const Function& target,
intptr_t argument_count,
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 9fed0ee..d6fd7cb 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -5959,7 +5959,7 @@
StorePointer(&raw_ptr()->code_, value.raw());
StoreNonPointer(&raw_ptr()->entry_point_, value.EntryPoint());
StoreNonPointer(&raw_ptr()->unchecked_entry_point_,
- value.unchecked_entry_point());
+ value.UncheckedEntryPoint());
}
void Function::AttachCode(const Code& value) const {
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 81a4686..9628163 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -5005,6 +5005,18 @@
}
}
+ static intptr_t function_entry_point_offset(EntryKind kind) {
+ switch (kind) {
+ case Code::EntryKind::kNormal:
+ return Function::entry_point_offset();
+ case Code::EntryKind::kUnchecked:
+ return Function::unchecked_entry_point_offset();
+ default:
+ ASSERT(false && "Invalid entry kind.");
+ UNREACHABLE();
+ }
+ }
+
RawObjectPool* object_pool() const { return raw_ptr()->object_pool_; }
static intptr_t object_pool_offset() {
return OFFSET_OF(RawCode, object_pool_);
@@ -5024,9 +5036,9 @@
uword PayloadStart() const {
return Instructions::PayloadStart(instructions());
}
- uword EntryPoint() const {
- const Instructions& instr = Instructions::Handle(instructions());
- return instr.EntryPoint();
+ uword EntryPoint() const { return Instructions::EntryPoint(instructions()); }
+ uword UncheckedEntryPoint() const {
+ return Instructions::UncheckedEntryPoint(instructions());
}
uword MonomorphicEntryPoint() const {
const Instructions& instr = Instructions::Handle(instructions());
@@ -5321,14 +5333,6 @@
bool IsDisabled() const { return instructions() != active_instructions(); }
- uword unchecked_entry_point() const {
- return raw_ptr()->unchecked_entry_point_;
- }
-
- void set_unchecked_entry_point(uword value) const {
- StoreNonPointer(&raw_ptr()->unchecked_entry_point_, value);
- }
-
private:
void set_state_bits(intptr_t bits) const;
diff --git a/tests/language_2/language_2_kernel.status b/tests/language_2/language_2_kernel.status
index c1330b4..76a6c49 100644
--- a/tests/language_2/language_2_kernel.status
+++ b/tests/language_2/language_2_kernel.status
@@ -1388,7 +1388,7 @@
enum_test: Crash
[ $mode == debug && ($compiler == dartk || $compiler == dartkb) && ($hot_reload || $hot_reload_rollback) ]
-enum_duplicate_test/01: Crash
+enum_duplicate_test/01: Pass, Crash
[ $mode == product && $runtime == vm && ($compiler == dartk || $compiler == dartkb) ]
deferred_load_constants_test/02: Fail