Files
sdk/runtime/vm/compiler/frontend/kernel_binary_flowgraph_test.cc
T
Ivan Inozemtsev 735a73943d Reland "[vm] Turn on entry point checking in JIT mode."
This is a reland of commit 982b9fad44

Original change's description:
> [vm] Turn on entry point checking in JIT mode.
>
> Now that Flutter tests that access entry points from native code
> have been annotated[1], we can turn on entry point checking in JIT
> mode.
>
> This CL also removes the A flag category from flag_list.h and the
> AOT_FLAG_MACRO definitions and uses from flags.[cc,h], as they were
> created as a temporary measure until this flag could be unconditionally
> defaulted to true.
>
> [1] See the following PRs:
> * https://github.com/flutter/engine/pull/57158
> * https://github.com/flutter/flutter/pull/160158
> * https://github.com/flutter/flutter/pull/160421
>
> TEST=vm/dart/entrypoints_verification_test vm/cc/IRTest
>      vm/cc/StreamingFlowGraphBuilder vm/cc/STC vm/cc/TTS
>
> Issue: https://github.com/dart-lang/sdk/issues/50649
> Issue: https://github.com/flutter/flutter/issues/118608
>
> 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,vm-linux-debug-x64-try,vm-linux-release-x64-try,vm-appjit-linux-product-x64-try
> Change-Id: Ibe5b21bb74f1a6fb88824b71ff87b9e555216dbf
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/400301
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Tess Strickland <sstrickl@google.com>

TEST=vm/dart/entrypoints_verification_test vm/cc/IRTest
     vm/cc/StreamingFlowGraphBuilder vm/cc/STC vm/cc/TTS

Change-Id: Ibd5f362f908b4aaa68cda870a387c081537bbc16
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,vm-linux-debug-x64-try,vm-linux-release-x64-try,vm-appjit-linux-product-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/403360
Auto-Submit: Ivan Inozemtsev <iinozemtsev@google.com>
Commit-Queue: Ivan Inozemtsev <iinozemtsev@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-01-08 00:26:40 -08:00

398 lines
12 KiB
C++

// 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.
#include "vm/compiler/frontend/kernel_binary_flowgraph.h"
#include "vm/compiler/backend/il_test_helper.h"
#include "vm/object.h"
#include "vm/unit_test.h"
namespace dart {
ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_ConstFoldStringConcats) {
// According to the Dart spec:
// "Adjacent strings are implicitly concatenated to form a single string
// literal."
const char* kScript = R"(
@pragma("vm:entry-point", "call")
test() {
var s = 'aaaa'
'bbbb'
'cccc';
return s;
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& function = Function::Handle(GetFunction(root_library, "test"));
Invoke(root_library, "test");
TestPipeline pipeline(function, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses({
CompilerPass::kComputeSSA,
});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
DartReturnInstr* ret = nullptr;
ILMatcher cursor(flow_graph, entry);
// clang-format off
RELEASE_ASSERT(cursor.TryMatch({
kMatchAndMoveFunctionEntry,
kMatchAndMoveCheckStackOverflow,
kMoveDebugStepChecks,
{kMatchDartReturn, &ret},
}));
// clang-format on
EXPECT(ret->value()->BindsToConstant());
EXPECT(ret->value()->BoundConstant().IsString());
const String& ret_str = String::Cast(ret->value()->BoundConstant());
EXPECT(ret_str.Equals("aaaabbbbcccc"));
}
ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_FlattenNestedStringInterp) {
// We should collapse nested StringInterpolates:
const char* kScript = R"(
test(String s) {
return '$s' '${'d' 'e'}';
}
main() => test('u');
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& function = Function::Handle(GetFunction(root_library, "test"));
Invoke(root_library, "main");
TestPipeline pipeline(function, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses({
CompilerPass::kComputeSSA,
});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
StoreIndexedInstr* store1 = nullptr;
StoreIndexedInstr* store2 = nullptr;
ILMatcher cursor(flow_graph, entry);
// clang-format off
RELEASE_ASSERT(cursor.TryMatch({
kMatchAndMoveFunctionEntry,
kMatchAndMoveCheckStackOverflow,
kMoveDebugStepChecks,
kMatchAndMoveCreateArray,
{kMatchAndMoveStoreIndexed, &store1},
{kMatchAndMoveStoreIndexed, &store2},
kMatchAndMoveRecordCoverage,
kMatchAndMoveStaticCall,
kMoveDebugStepChecks,
kMatchDartReturn,
}));
// clang-format on
// StoreIndexed(tmp_array, 0, s)
EXPECT(store1->index()->BindsToConstant());
EXPECT(store1->index()->BoundConstant().IsInteger());
EXPECT(Integer::Cast(store1->index()->BoundConstant()).Value() == 0);
EXPECT(!store1->value()->BindsToConstant());
// StoreIndexed(tmp_array, 1, "de")
EXPECT(store2->index()->BindsToConstant());
EXPECT(store2->index()->BoundConstant().IsInteger());
EXPECT(Integer::Cast(store2->index()->BoundConstant()).Value() == 1);
EXPECT(store2->value()->BindsToConstant());
EXPECT(store2->value()->BoundConstant().IsString());
EXPECT(String::Cast(store2->value()->BoundConstant()).Equals("de"));
}
ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_DropEmptyStringInterp) {
// We should drop empty strings from StringInterpolates:
const char* kScript = R"(
test(s) {
return '' 'a' '$s' '' 'b' '';
}
main() => test('u');
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& function = Function::Handle(GetFunction(root_library, "test"));
Invoke(root_library, "main");
TestPipeline pipeline(function, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses({
CompilerPass::kComputeSSA,
});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
StoreIndexedInstr* store1 = nullptr;
StoreIndexedInstr* store2 = nullptr;
StoreIndexedInstr* store3 = nullptr;
ILMatcher cursor(flow_graph, entry);
// clang-format off
RELEASE_ASSERT(cursor.TryMatch({
kMatchAndMoveFunctionEntry,
kMatchAndMoveCheckStackOverflow,
kMoveDebugStepChecks,
kMatchAndMoveCreateArray,
{kMatchAndMoveStoreIndexed, &store1},
{kMatchAndMoveStoreIndexed, &store2},
{kMatchAndMoveStoreIndexed, &store3},
kMatchAndMoveRecordCoverage,
kMatchAndMoveStaticCall,
kMoveDebugStepChecks,
kMatchDartReturn,
}));
// clang-format on
// StoreIndexed(tmp_array, 0, "ab")
EXPECT(store1->index()->BindsToConstant());
EXPECT(store1->index()->BoundConstant().IsInteger());
EXPECT(Integer::Cast(store1->index()->BoundConstant()).Value() == 0);
EXPECT(store1->value()->BindsToConstant());
EXPECT(store1->value()->BoundConstant().IsString());
EXPECT(String::Cast(store1->value()->BoundConstant()).Equals("a"));
// StoreIndexed(tmp_array, 1, s)
EXPECT(store2->index()->BindsToConstant());
EXPECT(store2->index()->BoundConstant().IsInteger());
EXPECT(Integer::Cast(store2->index()->BoundConstant()).Value() == 1);
EXPECT(!store2->value()->BindsToConstant());
// StoreIndexed(tmp_array, 2, "b")
EXPECT(store3->index()->BindsToConstant());
EXPECT(store3->index()->BoundConstant().IsInteger());
EXPECT(Integer::Cast(store3->index()->BoundConstant()).Value() == 2);
EXPECT(store3->value()->BindsToConstant());
EXPECT(store3->value()->BoundConstant().IsString());
EXPECT(String::Cast(store3->value()->BoundConstant()).Equals("b"));
}
ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_ConcatStringLits) {
// We should drop empty strings from StringInterpolates:
const char* kScript = R"(
test(s) {
return '' 'a' '' 'b' '$s' '' 'c' '' 'd' '';
}
main() => test('u');
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& function = Function::Handle(GetFunction(root_library, "test"));
Invoke(root_library, "main");
TestPipeline pipeline(function, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses({
CompilerPass::kComputeSSA,
});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
StoreIndexedInstr* store1 = nullptr;
StoreIndexedInstr* store2 = nullptr;
StoreIndexedInstr* store3 = nullptr;
ILMatcher cursor(flow_graph, entry);
// clang-format off
RELEASE_ASSERT(cursor.TryMatch({
kMatchAndMoveFunctionEntry,
kMatchAndMoveCheckStackOverflow,
kMoveDebugStepChecks,
kMatchAndMoveCreateArray,
{kMatchAndMoveStoreIndexed, &store1},
{kMatchAndMoveStoreIndexed, &store2},
{kMatchAndMoveStoreIndexed, &store3},
kMatchAndMoveRecordCoverage,
kMatchAndMoveStaticCall,
kMoveDebugStepChecks,
kMatchDartReturn,
}));
// clang-format on
// StoreIndexed(tmp_array, 0, "ab")
EXPECT(store1->index()->BindsToConstant());
EXPECT(store1->index()->BoundConstant().IsInteger());
EXPECT(Integer::Cast(store1->index()->BoundConstant()).Value() == 0);
EXPECT(store1->value()->BindsToConstant());
EXPECT(store1->value()->BoundConstant().IsString());
EXPECT(String::Cast(store1->value()->BoundConstant()).Equals("ab"));
// StoreIndexed(tmp_array, 1, s)
EXPECT(store2->index()->BindsToConstant());
EXPECT(store2->index()->BoundConstant().IsInteger());
EXPECT(Integer::Cast(store2->index()->BoundConstant()).Value() == 1);
EXPECT(!store2->value()->BindsToConstant());
// StoreIndexed(tmp_array, 2, "cd")
EXPECT(store3->index()->BindsToConstant());
EXPECT(store3->index()->BoundConstant().IsInteger());
EXPECT(Integer::Cast(store3->index()->BoundConstant()).Value() == 2);
EXPECT(store3->value()->BindsToConstant());
EXPECT(store3->value()->BoundConstant().IsString());
EXPECT(String::Cast(store3->value()->BoundConstant()).Equals("cd"));
}
ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_InvariantFlagInListLiterals) {
const char* kScript = R"(
@pragma("vm:entry-point", "call")
test() {
return [...[], 42];
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& function = Function::Handle(GetFunction(root_library, "test"));
Invoke(root_library, "test");
TestPipeline pipeline(function, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses({
CompilerPass::kComputeSSA,
});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
InstanceCallInstr* call_add = nullptr;
ILMatcher cursor(flow_graph, entry);
// clang-format off
RELEASE_ASSERT(cursor.TryMatch({
kMatchAndMoveFunctionEntry,
kMatchAndMoveCheckStackOverflow,
kMoveDebugStepChecks,
kMatchAndMoveRecordCoverage,
kMatchAndMoveStaticCall,
kMatchAndMoveRecordCoverage,
kMatchAndMoveStaticCall,
kMatchAndMoveRecordCoverage,
{kMatchAndMoveInstanceCall, &call_add},
kMoveDebugStepChecks,
kMatchDartReturn,
}));
// clang-format on
EXPECT(call_add != nullptr);
EXPECT(call_add->function_name().Equals("add"));
EXPECT(call_add->entry_kind() == Code::EntryKind::kUnchecked);
}
ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_TypedClosureCall) {
// This test ensures that a typed closure call (i.e. the closure being called
// has a real function type as static type) ends up compiling to
//
// CheckNull()+ClosureCall()
//
// instead of
//
// InstanceCall(dyn:call)
//
// This is a regression test for a case where JIT support uses dynamic calls
// instead of typed closure calls if call-site attribute metadata is missing
// which is the case if an incremental kernel compiler is used. The
// [LoadTestScript] below uses IKG compiler, so this is a regression test for:
//
// - https://github.com/dart-lang/sdk/issues/46320
// - https://github.com/dart-lang/sdk/issues/45421
//
const char* kScript = R"(
int callClosure(int Function(int) fun, int value) => fun(value);
@pragma("vm:entry-point", "call")
test() => callClosure((int a) => a + 1, 10);
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
Invoke(root_library, "test");
const auto& callClosureFunction =
Function::Handle(GetFunction(root_library, "callClosure"));
TestPipeline pipeline(callClosureFunction, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses({
CompilerPass::kComputeSSA,
});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
ILMatcher cursor(flow_graph, entry, true);
// clang-format off
std::initializer_list<MatchCode> expected = {
kMatchAndMoveFunctionEntry,
kMatchAndMoveCheckStackOverflow,
kMoveDebugStepChecks,
kMatchAndMoveCheckNull,
kMatchAndMoveLoadField,
kMoveDebugStepChecks,
#if !defined(PRODUCT)
kMatchAndMoveRecordCoverage,
#endif
kMatchAndMoveClosureCall,
kMoveDebugStepChecks,
kMatchDartReturn,
};
RELEASE_ASSERT(cursor.TryMatch(expected));
// clang-format on
}
ISOLATE_UNIT_TEST_CASE(
StreamingFlowGraphBuilder_StaticGetFinalFieldWithTrivialInitializer) {
const char* kScript = R"(
final int x = 0xFEEDFEED;
@pragma("vm:entry-point", "call")
test() {
return x;
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& function = Function::Handle(GetFunction(root_library, "test"));
Invoke(root_library, "test");
TestPipeline pipeline(function, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses({
CompilerPass::kComputeSSA,
});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
DartReturnInstr* return_instr = nullptr;
ILMatcher cursor(flow_graph, entry);
RELEASE_ASSERT(cursor.TryMatch({
kMatchAndMoveFunctionEntry,
kMatchAndMoveCheckStackOverflow,
kMoveDebugStepChecks,
{kMatchDartReturn, &return_instr},
}));
EXPECT(return_instr != nullptr);
ConstantInstr* const_value =
return_instr->value()->definition()->AsConstant();
EXPECT(const_value != nullptr);
EXPECT(const_value->value().IsInteger());
EXPECT_EQ(0xFEEDFEED, Integer::Cast(const_value->value()).Value());
}
} // namespace dart