9b06e26620
This is a reland of commit cb9ecbc363
This reland only turns on the entry point verification flag by
default in AOT mode. After Flutter tests that use native access
in JIT mode have been appropriately updated, a followup CL will
turn this flag on by default in JIT mode as well.
Original change's description:
> [vm] Enforce that entry points must be annotated by default.
>
> Changes the default value of the --verify-entry-points flag
> to true.
>
> Changes the default value for the check_is_entrypoint argument to
> to the Invoke/InvokeGetter/InvokeSetter flags to true. The mirrors
> library implementation and calls via vm-service explicitly pass
> false for this argument now.
>
> Add annotations as needed, such as annotating classes with
> annotated generative constructors. In some cases, the annotations
> were more general than needed (e.g., annotating with a no-argument
> entry point annotation when only the setter is needed), so make
> those annotations more specific.
>
> As this pattern is already common in downstream code, allow
> Dart_Invoke on fields as long as the field is annotated for getter
> access. (That is, calling Dart_Invoke for a field is equivalent to
> retrieving the closure value via Dart_GetField and then calling
> Dart_InvokeClosure.)
>
> TEST=vm/cc/DartAPI_MissingEntryPoints
> vm/dart/entrypoints_verification_test
>
> Issue: https://github.com/dart-lang/sdk/issues/50649
> Issue: https://github.com/flutter/flutter/issues/118608
>
> Change-Id: Ibb3bf15632ab2958d8791b449af8651d47f871a5
> Cq-Include-Trybots: luci.dart.try:vm-aot-linux-product-x64-try,vm-aot-linux-debug-x64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-product-arm64-try,vm-aot-dwarf-linux-product-x64-try
> CoreLibraryReviewExempt: adding/editing vm-only pragma annotations
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/363566
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Tess Strickland <sstrickl@google.com>
TEST=vm/cc/DartAPI_MissingEntryPoints
vm/dart/entrypoints_verification_test
Change-Id: I24919c32ab4760c7c5435c378879791086256f02
Cq-Include-Trybots: luci.dart.try:vm-aot-linux-product-x64-try,vm-aot-linux-debug-x64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-product-arm64-try,vm-aot-dwarf-linux-product-x64-try,flutter-linux-try,vm-linux-debug-x64-try,vm-linux-release-x64-try,vm-appjit-linux-product-x64-try
CoreLibraryReviewExempt: adding/editing vm-only pragma annotations
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/391620
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
321 lines
11 KiB
C++
321 lines
11 KiB
C++
// Copyright (c) 2012, 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.
|
|
|
|
#include "vm/compiler/jit/compiler.h"
|
|
#include "platform/assert.h"
|
|
#include "vm/class_finalizer.h"
|
|
#include "vm/code_patcher.h"
|
|
#include "vm/dart_api_impl.h"
|
|
#include "vm/heap/safepoint.h"
|
|
#include "vm/kernel_isolate.h"
|
|
#include "vm/object.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/thread_pool.h"
|
|
#include "vm/unit_test.h"
|
|
|
|
namespace dart {
|
|
|
|
ISOLATE_UNIT_TEST_CASE(CompileFunction) {
|
|
const char* kScriptChars =
|
|
"class A {\n"
|
|
" static foo() { return 42; }\n"
|
|
" static moo() {\n"
|
|
" // A.foo();\n"
|
|
" }\n"
|
|
"}\n";
|
|
Dart_Handle library;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
library = TestCase::LoadTestScript(kScriptChars, nullptr);
|
|
}
|
|
const Library& lib =
|
|
Library::Handle(Library::RawCast(Api::UnwrapHandle(library)));
|
|
EXPECT(ClassFinalizer::ProcessPendingClasses());
|
|
Class& cls =
|
|
Class::Handle(lib.LookupClass(String::Handle(Symbols::New(thread, "A"))));
|
|
EXPECT(!cls.IsNull());
|
|
const auto& error = cls.EnsureIsFinalized(thread);
|
|
EXPECT(error == Error::null());
|
|
String& function_foo_name = String::Handle(String::New("foo"));
|
|
Function& function_foo =
|
|
Function::Handle(cls.LookupStaticFunction(function_foo_name));
|
|
EXPECT(!function_foo.IsNull());
|
|
String& function_source = String::Handle(function_foo.GetSource());
|
|
EXPECT_STREQ("static foo() { return 42; }", function_source.ToCString());
|
|
EXPECT(CompilerTest::TestCompileFunction(function_foo));
|
|
EXPECT(function_foo.HasCode());
|
|
|
|
String& function_moo_name = String::Handle(String::New("moo"));
|
|
Function& function_moo =
|
|
Function::Handle(cls.LookupStaticFunction(function_moo_name));
|
|
EXPECT(!function_moo.IsNull());
|
|
|
|
EXPECT(CompilerTest::TestCompileFunction(function_moo));
|
|
EXPECT(function_moo.HasCode());
|
|
function_source = function_moo.GetSource();
|
|
EXPECT_STREQ("static moo() {\n // A.foo();\n }",
|
|
function_source.ToCString());
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(OptimizeCompileFunctionOnHelperThread) {
|
|
// Create a simple function and compile it without optimization.
|
|
const char* kScriptChars =
|
|
"class A {\n"
|
|
" static foo() { return 42; }\n"
|
|
"}\n";
|
|
Dart_Handle library;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
library = TestCase::LoadTestScript(kScriptChars, nullptr);
|
|
}
|
|
const Library& lib =
|
|
Library::Handle(Library::RawCast(Api::UnwrapHandle(library)));
|
|
EXPECT(ClassFinalizer::ProcessPendingClasses());
|
|
Class& cls =
|
|
Class::Handle(lib.LookupClass(String::Handle(Symbols::New(thread, "A"))));
|
|
EXPECT(!cls.IsNull());
|
|
String& function_foo_name = String::Handle(String::New("foo"));
|
|
const auto& error = cls.EnsureIsFinalized(thread);
|
|
EXPECT(error == Error::null());
|
|
Function& func =
|
|
Function::Handle(cls.LookupStaticFunction(function_foo_name));
|
|
EXPECT(!func.HasCode());
|
|
CompilerTest::TestCompileFunction(func);
|
|
EXPECT(func.HasCode());
|
|
EXPECT(!func.HasOptimizedCode());
|
|
#if !defined(PRODUCT)
|
|
// Constant in product mode.
|
|
FLAG_background_compilation = true;
|
|
#endif
|
|
auto isolate_group = thread->isolate_group();
|
|
isolate_group->background_compiler()->EnqueueCompilation(func);
|
|
Monitor* m = new Monitor();
|
|
{
|
|
SafepointMonitorLocker ml(m);
|
|
while (!func.HasOptimizedCode()) {
|
|
ml.Wait(1);
|
|
}
|
|
}
|
|
delete m;
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(CompileFunctionOnHelperThread) {
|
|
// Create a simple function and compile it without optimization.
|
|
const char* kScriptChars =
|
|
"class A {\n"
|
|
" static foo() { return 42; }\n"
|
|
"}\n";
|
|
Dart_Handle library;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
library = TestCase::LoadTestScript(kScriptChars, nullptr);
|
|
}
|
|
const Library& lib =
|
|
Library::Handle(Library::RawCast(Api::UnwrapHandle(library)));
|
|
EXPECT(ClassFinalizer::ProcessPendingClasses());
|
|
Class& cls =
|
|
Class::Handle(lib.LookupClass(String::Handle(Symbols::New(thread, "A"))));
|
|
EXPECT(!cls.IsNull());
|
|
const auto& error = cls.EnsureIsFinalized(thread);
|
|
EXPECT(error == Error::null());
|
|
String& function_foo_name = String::Handle(String::New("foo"));
|
|
Function& func =
|
|
Function::Handle(cls.LookupStaticFunction(function_foo_name));
|
|
EXPECT(!func.HasCode());
|
|
CompilerTest::TestCompileFunction(func);
|
|
EXPECT(func.HasCode());
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(RegenerateAllocStubs) {
|
|
const char* kScriptChars =
|
|
"class A {\n"
|
|
"}\n"
|
|
"unOpt() => new A(); \n"
|
|
"optIt() => new A(); \n"
|
|
"A main() {\n"
|
|
" return unOpt();\n"
|
|
"}\n";
|
|
|
|
Class& cls = Class::Handle();
|
|
TransitionVMToNative transition(thread);
|
|
|
|
Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, nullptr);
|
|
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
|
EXPECT_VALID(result);
|
|
|
|
{
|
|
TransitionNativeToVM transition(thread);
|
|
Library& lib_handle =
|
|
Library::Handle(Library::RawCast(Api::UnwrapHandle(lib)));
|
|
cls = lib_handle.LookupClass(String::Handle(Symbols::New(thread, "A")));
|
|
EXPECT(!cls.IsNull());
|
|
}
|
|
|
|
{
|
|
TransitionNativeToVM transition(thread);
|
|
cls.DisableAllocationStub();
|
|
}
|
|
result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
|
EXPECT_VALID(result);
|
|
|
|
{
|
|
TransitionNativeToVM transition(thread);
|
|
cls.DisableAllocationStub();
|
|
}
|
|
result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
|
EXPECT_VALID(result);
|
|
|
|
{
|
|
TransitionNativeToVM transition(thread);
|
|
cls.DisableAllocationStub();
|
|
}
|
|
result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
|
EXPECT_VALID(result);
|
|
}
|
|
|
|
TEST_CASE(EvalExpression) {
|
|
const char* kScriptChars =
|
|
R"(
|
|
int ten = 2 * 5;
|
|
get dot => '.';
|
|
class A {
|
|
var apa = 'Herr Nilsson';
|
|
calc(x) => '${x*ten}';
|
|
}
|
|
@pragma('vm:entry-point', 'call')
|
|
makeObj() => new A();
|
|
)";
|
|
|
|
Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, nullptr);
|
|
Dart_Handle obj_handle =
|
|
Dart_Invoke(lib, Dart_NewStringFromCString("makeObj"), 0, nullptr);
|
|
EXPECT_VALID(obj_handle);
|
|
TransitionNativeToVM transition(thread);
|
|
const Object& obj = Object::Handle(Api::UnwrapHandle(obj_handle));
|
|
EXPECT(!obj.IsNull());
|
|
EXPECT(obj.IsInstance());
|
|
|
|
String& expr_text = String::Handle();
|
|
expr_text = String::New("apa + ' ${calc(10)}' + dot");
|
|
Object& val = Object::Handle();
|
|
const Class& receiver_cls = Class::Handle(obj.clazz());
|
|
|
|
if (!KernelIsolate::IsRunning()) {
|
|
UNREACHABLE();
|
|
} else {
|
|
LibraryPtr raw_library = Library::RawCast(Api::UnwrapHandle(lib));
|
|
Library& lib_handle = Library::ZoneHandle(raw_library);
|
|
|
|
Dart_KernelCompilationResult compilation_result =
|
|
KernelIsolate::CompileExpressionToKernel(
|
|
/*platform_kernel=*/nullptr, /*platform_kernel_size=*/0,
|
|
expr_text.ToCString(), Array::empty_array(), Array::empty_array(),
|
|
Array::empty_array(), Array::empty_array(), Array::empty_array(),
|
|
String::Handle(lib_handle.url()).ToCString(), "A",
|
|
/* method= */ nullptr,
|
|
/* token_pos= */ TokenPosition::kNoSource,
|
|
/* script_uri= */ String::Handle(lib_handle.url()).ToCString(),
|
|
/* is_static= */ false);
|
|
EXPECT_EQ(Dart_KernelCompilationStatus_Ok, compilation_result.status);
|
|
|
|
const ExternalTypedData& kernel_buffer =
|
|
ExternalTypedData::Handle(ExternalTypedData::NewFinalizeWithFree(
|
|
const_cast<uint8_t*>(compilation_result.kernel),
|
|
compilation_result.kernel_size));
|
|
|
|
val = Instance::Cast(obj).EvaluateCompiledExpression(
|
|
receiver_cls, kernel_buffer, Array::empty_array(), Array::empty_array(),
|
|
TypeArguments::null_type_arguments());
|
|
}
|
|
EXPECT(!val.IsNull());
|
|
EXPECT(!val.IsError());
|
|
EXPECT(val.IsString());
|
|
EXPECT_STREQ("Herr Nilsson 100.", val.ToCString());
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(EvalExpressionWithLazyCompile) {
|
|
{ // Initialize an incremental compiler in DFE mode.
|
|
TransitionVMToNative transition(thread);
|
|
TestCase::LoadTestScript("", nullptr);
|
|
}
|
|
Library& lib = Library::Handle(Library::CoreLibrary());
|
|
const String& expression = String::Handle(
|
|
String::New("(){ return (){ return (){ return 3 + 4; }(); }(); }()"));
|
|
Object& val = Object::Handle();
|
|
val = Api::UnwrapHandle(
|
|
TestCase::EvaluateExpression(lib, expression,
|
|
/* param_names= */ Array::empty_array(),
|
|
/* param_values= */ Array::empty_array()));
|
|
|
|
EXPECT(!val.IsNull());
|
|
EXPECT(!val.IsError());
|
|
EXPECT(val.IsInteger());
|
|
EXPECT_EQ(7, Integer::Cast(val).Value());
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(EvalExpressionExhaustCIDs) {
|
|
{ // Initialize an incremental compiler in DFE mode.
|
|
TransitionVMToNative transition(thread);
|
|
TestCase::LoadTestScript("", nullptr);
|
|
}
|
|
Library& lib = Library::Handle(Library::CoreLibrary());
|
|
const String& expression = String::Handle(String::New("3 + 4"));
|
|
Object& val = Object::Handle();
|
|
val = Api::UnwrapHandle(
|
|
TestCase::EvaluateExpression(lib, expression,
|
|
/* param_names= */ Array::empty_array(),
|
|
/* param_values= */ Array::empty_array()));
|
|
|
|
EXPECT(!val.IsNull());
|
|
EXPECT(!val.IsError());
|
|
EXPECT(val.IsInteger());
|
|
EXPECT_EQ(7, Integer::Cast(val).Value());
|
|
|
|
auto class_table = IsolateGroup::Current()->class_table();
|
|
|
|
intptr_t initial_class_table_size = class_table->NumCids();
|
|
|
|
val = Api::UnwrapHandle(
|
|
TestCase::EvaluateExpression(lib, expression,
|
|
/* param_names= */ Array::empty_array(),
|
|
/* param_values= */ Array::empty_array()));
|
|
EXPECT(!val.IsNull());
|
|
EXPECT(!val.IsError());
|
|
EXPECT(val.IsInteger());
|
|
EXPECT_EQ(7, Integer::Cast(val).Value());
|
|
|
|
intptr_t final_class_table_size = class_table->NumCids();
|
|
// Eval should not eat into this non-renewable resource.
|
|
EXPECT_EQ(initial_class_table_size, final_class_table_size);
|
|
}
|
|
|
|
// Too slow in debug mode.
|
|
#if !defined(DEBUG) && !defined(USING_THREAD_SANITIZER)
|
|
TEST_CASE(ManyClasses) {
|
|
// Limit is 20 bits. Check only more than 16 bits so test completes in
|
|
// reasonable time.
|
|
const intptr_t kNumClasses = (1 << 16) + 1;
|
|
|
|
TextBuffer buffer(MB);
|
|
for (intptr_t i = 0; i < kNumClasses; i++) {
|
|
buffer.Printf("class C%" Pd " { String toString() => 'C%" Pd "'; }\n", i,
|
|
i);
|
|
}
|
|
buffer.Printf("main() {\n");
|
|
for (intptr_t i = 0; i < kNumClasses; i++) {
|
|
buffer.Printf(" new C%" Pd "().toString();\n", i);
|
|
}
|
|
buffer.Printf("}\n");
|
|
|
|
Dart_Handle lib = TestCase::LoadTestScript(buffer.buffer(), nullptr);
|
|
EXPECT_VALID(lib);
|
|
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
|
EXPECT_VALID(result);
|
|
|
|
EXPECT(IsolateGroup::Current()->class_table()->NumCids() >= kNumClasses);
|
|
}
|
|
#endif // !defined(DEBUG) && !defined(USING_THREAD_SANITIZER)
|
|
|
|
} // namespace dart
|