4a93b79ca6
Front-end marks certain calls (currently in collection literals) as not requiring covariant parameter checks (invariant). VM can use that flag to generate calls through unchecked entry point. TEST=vm/cc/StreamingFlowGraphBuilder_InvariantFlagInListLiterals Change-Id: I8d0dc09ea4867f41d20f6d7b6ba9e0940ff555dd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175880 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
292 lines
8.9 KiB
C++
292 lines
8.9 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"(
|
|
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);
|
|
|
|
ReturnInstr* ret = nullptr;
|
|
|
|
ILMatcher cursor(flow_graph, entry);
|
|
// clang-format off
|
|
RELEASE_ASSERT(cursor.TryMatch({
|
|
kMatchAndMoveFunctionEntry,
|
|
kMatchAndMoveCheckStackOverflow,
|
|
kMoveDebugStepChecks,
|
|
{kMatchReturn, &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},
|
|
kMatchAndMoveStringInterpolate,
|
|
kMoveDebugStepChecks,
|
|
kMatchReturn,
|
|
}));
|
|
// clang-format on
|
|
|
|
// StoreIndexed(tmp_array, 0, s)
|
|
EXPECT(store1->index()->BindsToConstant());
|
|
EXPECT(store1->index()->BoundConstant().IsInteger());
|
|
EXPECT(Integer::Cast(store1->index()->BoundConstant()).AsInt64Value() == 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()).AsInt64Value() == 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},
|
|
kMatchAndMoveStringInterpolate,
|
|
kMoveDebugStepChecks,
|
|
kMatchReturn,
|
|
}));
|
|
// clang-format on
|
|
|
|
// StoreIndexed(tmp_array, 0, "ab")
|
|
EXPECT(store1->index()->BindsToConstant());
|
|
EXPECT(store1->index()->BoundConstant().IsInteger());
|
|
EXPECT(Integer::Cast(store1->index()->BoundConstant()).AsInt64Value() == 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()).AsInt64Value() == 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()).AsInt64Value() == 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},
|
|
kMatchAndMoveStringInterpolate,
|
|
kMoveDebugStepChecks,
|
|
kMatchReturn,
|
|
}));
|
|
// clang-format on
|
|
|
|
// StoreIndexed(tmp_array, 0, "ab")
|
|
EXPECT(store1->index()->BindsToConstant());
|
|
EXPECT(store1->index()->BoundConstant().IsInteger());
|
|
EXPECT(Integer::Cast(store1->index()->BoundConstant()).AsInt64Value() == 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()).AsInt64Value() == 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()).AsInt64Value() == 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"(
|
|
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,
|
|
kMatchAndMoveStaticCall,
|
|
kMatchAndMoveStaticCall,
|
|
{kMatchAndMoveInstanceCall, &call_add},
|
|
kMoveDebugStepChecks,
|
|
kMatchReturn,
|
|
}));
|
|
// clang-format on
|
|
|
|
EXPECT(call_add != nullptr);
|
|
EXPECT(call_add->function_name().Equals("add"));
|
|
EXPECT(call_add->entry_kind() == Code::EntryKind::kUnchecked);
|
|
}
|
|
|
|
} // namespace dart
|