[vm] Fix finalization of classes when instances are allocated
Classes should be allocate-finalized before instance can be allocated
in order to properly update class hierarchy information and invalidate
code which was speculatively optimized.
TEST=runtime/tests/vm/dart/regress_44342_test.dart
Fixes https://github.com/dart-lang/sdk/issues/44342
Change-Id: I52a415f4cf25010eb23be339266a91ae6b7d7261
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175106
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: RĂ©gis Crelier <regis@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc
index bb85a96..2144be8 100644
--- a/runtime/lib/ffi.cc
+++ b/runtime/lib/ffi.cc
@@ -224,7 +224,7 @@
const auto& typed_data_class =
Class::Handle(zone, isolate->class_table()->At(cid));
const auto& error =
- Error::Handle(zone, typed_data_class.EnsureIsFinalized(thread));
+ Error::Handle(zone, typed_data_class.EnsureIsAllocateFinalized(thread));
if (!error.IsNull()) {
Exceptions::PropagateError(error);
}
diff --git a/runtime/tests/vm/dart/regress_44342_test.dart b/runtime/tests/vm/dart/regress_44342_test.dart
new file mode 100644
index 0000000..98514fc
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_44342_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2020, 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=--deterministic --optimization_counter_threshold=100
+
+// Verifies that class of a constant participates in CHA decisions.
+// Regression test for https://github.com/dart-lang/sdk/issues/44342.
+
+import 'package:expect/expect.dart';
+
+class A {
+ const A();
+
+ int foo() => 3;
+
+ @pragma("vm:never-inline")
+ int bar() => foo();
+}
+
+class B extends A {}
+
+class C extends A {
+ const C();
+
+ int foo() => 4;
+}
+
+A x = B();
+A y = const C();
+
+main() {
+ for (int i = 0; i < 200; ++i) {
+ // Optimize A.bar() with inlined A.foo().
+ int result = x.bar();
+ Expect.equals(3, result);
+ }
+ int result = y.bar();
+ Expect.equals(4, result);
+}
diff --git a/runtime/tests/vm/dart_2/regress_44342_test.dart b/runtime/tests/vm/dart_2/regress_44342_test.dart
new file mode 100644
index 0000000..98514fc
--- /dev/null
+++ b/runtime/tests/vm/dart_2/regress_44342_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2020, 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=--deterministic --optimization_counter_threshold=100
+
+// Verifies that class of a constant participates in CHA decisions.
+// Regression test for https://github.com/dart-lang/sdk/issues/44342.
+
+import 'package:expect/expect.dart';
+
+class A {
+ const A();
+
+ int foo() => 3;
+
+ @pragma("vm:never-inline")
+ int bar() => foo();
+}
+
+class B extends A {}
+
+class C extends A {
+ const C();
+
+ int foo() => 4;
+}
+
+A x = B();
+A y = const C();
+
+main() {
+ for (int i = 0; i < 200; ++i) {
+ // Optimize A.bar() with inlined A.foo().
+ int result = x.bar();
+ Expect.equals(3, result);
+ }
+ int result = y.bar();
+ Expect.equals(4, result);
+}
diff --git a/runtime/tests/vm/vm.status b/runtime/tests/vm/vm.status
index cdc1186..802cc8b 100644
--- a/runtime/tests/vm/vm.status
+++ b/runtime/tests/vm/vm.status
@@ -115,6 +115,7 @@
[ $mode == debug ]
cc/CorelibIsolateStartup: SkipByDesign # This is a benchmark that is not informative in debug mode.
+cc/SixtyThousandDartClasses: SkipSlow # Finalization of 64K classes is too slow in debug mode.
cc/VerifyExplicit_Crash: Crash # Negative tests of VerifiedMemory should crash iff in DEBUG mode. TODO(koda): Improve support for negative tests.
cc/VerifyImplicit_Crash: Crash # Negative tests of VerifiedMemory should crash iff in DEBUG mode. TODO(koda): Improve support for negative tests.
dart/appjit_cha_deopt_test: Pass, Slow # Quite slow in debug mode, uses --optimization-counter-threshold=100
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index ea2fc4a..61cd91d 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -1122,7 +1122,7 @@
class_count_++;
cls.set_is_allocated(true);
- error_ = cls.EnsureIsFinalized(T);
+ error_ = cls.EnsureIsAllocateFinalized(T);
if (!error_.IsNull()) {
Jump(error_);
}
diff --git a/runtime/vm/compiler/frontend/constant_reader.cc b/runtime/vm/compiler/frontend/constant_reader.cc
index d6bff45..6eb0a03 100644
--- a/runtime/vm/compiler/frontend/constant_reader.cc
+++ b/runtime/vm/compiler/frontend/constant_reader.cc
@@ -233,7 +233,8 @@
"%s is not loaded yet.",
klass.ToCString());
}
- const auto& obj = Object::Handle(Z, klass.EnsureIsFinalized(H.thread()));
+ const auto& obj =
+ Object::Handle(Z, klass.EnsureIsAllocateFinalized(H.thread()));
ASSERT(obj.IsNull());
ASSERT(klass.is_enum_class() || klass.is_const());
instance = Instance::New(klass, Heap::kOld);
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 2b0c369..49c32bc 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -4627,7 +4627,7 @@
return Api::NewError("Precompilation dropped '%s'", cls.ToCString());
}
#endif
- CHECK_ERROR_HANDLE(cls.EnsureIsFinalized(T));
+ CHECK_ERROR_HANDLE(cls.EnsureIsAllocateFinalized(T));
return Api::NewHandle(T, AllocateObject(T, cls));
}
@@ -4653,7 +4653,7 @@
return Api::NewError("Precompilation dropped '%s'", cls.ToCString());
}
#endif
- CHECK_ERROR_HANDLE(cls.EnsureIsFinalized(T));
+ CHECK_ERROR_HANDLE(cls.EnsureIsAllocateFinalized(T));
if (num_native_fields != cls.num_native_fields()) {
return Api::NewError(
"%s: invalid number of native fields %" Pd " passed in, expected %d",
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 7870499..f11ffea 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -4310,6 +4310,10 @@
UNREACHABLE();
}
}
+ // May be allocate-finalized recursively during EnsureIsFinalized.
+ if (is_allocate_finalized()) {
+ return Error::null();
+ }
error ^= ClassFinalizer::AllocateFinalizeClass(*this);
return error.raw();
}
@@ -18756,7 +18760,7 @@
InstancePtr Instance::New(const Class& cls, Heap::Space space) {
Thread* thread = Thread::Current();
- if (cls.EnsureIsFinalized(thread) != Error::null()) {
+ if (cls.EnsureIsAllocateFinalized(thread) != Error::null()) {
return Instance::null();
}
intptr_t instance_size = cls.host_instance_size();
@@ -23912,7 +23916,7 @@
const Class& cls =
Class::Handle(Isolate::Current()->class_table()->At(kFfiPointerCid));
- cls.EnsureIsFinalized(Thread::Current());
+ cls.EnsureIsAllocateFinalized(Thread::Current());
Pointer& result = Pointer::Handle(zone);
result ^= Object::Allocate(kFfiPointerCid, Pointer::InstanceSize(), space);