| // 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. |
| |
| #include "vm/compiler/backend/redundancy_elimination.h" |
| #include "vm/compiler/backend/il_printer.h" |
| #include "vm/compiler/backend/il_test_helper.h" |
| #include "vm/compiler/backend/inliner.h" |
| #include "vm/compiler/backend/loops.h" |
| #include "vm/compiler/backend/type_propagator.h" |
| #include "vm/compiler/compiler_pass.h" |
| #include "vm/compiler/frontend/kernel_to_il.h" |
| #include "vm/compiler/jit/jit_call_specializer.h" |
| #include "vm/log.h" |
| #include "vm/object.h" |
| #include "vm/parser.h" |
| #include "vm/symbols.h" |
| #include "vm/unit_test.h" |
| |
| namespace dart { |
| |
| static void NoopNative(Dart_NativeArguments args) {} |
| |
| static Dart_NativeFunction NoopNativeLookup(Dart_Handle name, |
| int argument_count, |
| bool* auto_setup_scope) { |
| ASSERT(auto_setup_scope != nullptr); |
| *auto_setup_scope = false; |
| return reinterpret_cast<Dart_NativeFunction>(&NoopNative); |
| } |
| |
| // Flatten all non-captured LocalVariables from the given scope and its children |
| // and siblings into the given array based on their environment index. |
| static void FlattenScopeIntoEnvironment(FlowGraph* graph, |
| LocalScope* scope, |
| GrowableArray<LocalVariable*>* env) { |
| for (intptr_t i = 0; i < scope->num_variables(); i++) { |
| auto var = scope->VariableAt(i); |
| if (var->is_captured()) { |
| continue; |
| } |
| |
| auto index = graph->EnvIndex(var); |
| env->EnsureLength(index + 1, nullptr); |
| (*env)[index] = var; |
| } |
| |
| if (scope->sibling() != nullptr) { |
| FlattenScopeIntoEnvironment(graph, scope->sibling(), env); |
| } |
| if (scope->child() != nullptr) { |
| FlattenScopeIntoEnvironment(graph, scope->child(), env); |
| } |
| } |
| |
| // Run TryCatchAnalyzer optimization on the function foo from the given script |
| // and check that the only variables from the given list are synchronized |
| // on catch entry. |
| static void TryCatchOptimizerTest( |
| Thread* thread, |
| const char* script_chars, |
| std::initializer_list<const char*> synchronized) { |
| // Load the script and exercise the code once. |
| const auto& root_library = |
| Library::Handle(LoadTestScript(script_chars, &NoopNativeLookup)); |
| Invoke(root_library, "main"); |
| |
| // Build the flow graph. |
| std::initializer_list<CompilerPass::Id> passes = { |
| CompilerPass::kComputeSSA, CompilerPass::kTypePropagation, |
| CompilerPass::kApplyICData, CompilerPass::kSelectRepresentations, |
| CompilerPass::kTypePropagation, CompilerPass::kCanonicalize, |
| }; |
| const auto& function = Function::Handle(GetFunction(root_library, "foo")); |
| TestPipeline pipeline(function, CompilerPass::kJIT); |
| FlowGraph* graph = pipeline.RunPasses(passes); |
| |
| // Finally run TryCatchAnalyzer on the graph (in AOT mode). |
| OptimizeCatchEntryStates(graph, /*is_aot=*/true); |
| |
| EXPECT_EQ(1, graph->graph_entry()->catch_entries().length()); |
| auto scope = graph->parsed_function().node_sequence()->scope(); |
| |
| GrowableArray<LocalVariable*> env; |
| FlattenScopeIntoEnvironment(graph, scope, &env); |
| |
| for (intptr_t i = 0; i < env.length(); i++) { |
| bool found = false; |
| for (auto name : synchronized) { |
| if (env[i]->name().Equals(name)) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| env[i] = nullptr; |
| } |
| } |
| |
| CatchBlockEntryInstr* catch_entry = graph->graph_entry()->catch_entries()[0]; |
| |
| // We should only synchronize state for variables from the synchronized list. |
| for (auto defn : *catch_entry->initial_definitions()) { |
| if (ParameterInstr* param = defn->AsParameter()) { |
| EXPECT(0 <= param->index() && param->index() < env.length()); |
| EXPECT(env[param->index()] != nullptr); |
| } |
| } |
| } |
| |
| // |
| // Tests for TryCatchOptimizer. |
| // |
| |
| ISOLATE_UNIT_TEST_CASE(TryCatchOptimizer_DeadParameterElimination_Simple1) { |
| const char* script_chars = R"( |
| dynamic blackhole([dynamic val]) native 'BlackholeNative'; |
| foo(int p) { |
| var a = blackhole(), b = blackhole(); |
| try { |
| blackhole([a, b]); |
| } catch (e) { |
| // nothing is used |
| } |
| } |
| main() { |
| foo(42); |
| } |
| )"; |
| |
| TryCatchOptimizerTest(thread, script_chars, /*synchronized=*/{}); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(TryCatchOptimizer_DeadParameterElimination_Simple2) { |
| const char* script_chars = R"( |
| dynamic blackhole([dynamic val]) native 'BlackholeNative'; |
| foo(int p) { |
| var a = blackhole(), b = blackhole(); |
| try { |
| blackhole([a, b]); |
| } catch (e) { |
| // a should be synchronized |
| blackhole(a); |
| } |
| } |
| main() { |
| foo(42); |
| } |
| )"; |
| |
| TryCatchOptimizerTest(thread, script_chars, /*synchronized=*/{"a"}); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(TryCatchOptimizer_DeadParameterElimination_Cyclic1) { |
| const char* script_chars = R"( |
| dynamic blackhole([dynamic val]) native 'BlackholeNative'; |
| foo(int p) { |
| var a = blackhole(), b; |
| for (var i = 0; i < 42; i++) { |
| b = blackhole(); |
| try { |
| blackhole([a, b]); |
| } catch (e) { |
| // a and i should be synchronized |
| } |
| } |
| } |
| main() { |
| foo(42); |
| } |
| )"; |
| |
| TryCatchOptimizerTest(thread, script_chars, /*synchronized=*/{"a", "i"}); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(TryCatchOptimizer_DeadParameterElimination_Cyclic2) { |
| const char* script_chars = R"( |
| dynamic blackhole([dynamic val]) native 'BlackholeNative'; |
| foo(int p) { |
| var a = blackhole(), b = blackhole(); |
| for (var i = 0; i < 42; i++) { |
| try { |
| blackhole([a, b]); |
| } catch (e) { |
| // a, b and i should be synchronized |
| } |
| } |
| } |
| main() { |
| foo(42); |
| } |
| )"; |
| |
| TryCatchOptimizerTest(thread, script_chars, /*synchronized=*/{"a", "b", "i"}); |
| } |
| |
| } // namespace dart |