| // Copyright (c) 2019, 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/il_test_helper.h" |
| |
| #include "vm/compiler/aot/aot_call_specializer.h" |
| #include "vm/compiler/backend/block_scheduler.h" |
| #include "vm/compiler/backend/flow_graph.h" |
| #include "vm/compiler/backend/flow_graph_compiler.h" |
| #include "vm/compiler/backend/il.h" |
| #include "vm/compiler/backend/il_printer.h" |
| #include "vm/compiler/backend/inliner.h" |
| #include "vm/compiler/call_specializer.h" |
| #include "vm/compiler/compiler_pass.h" |
| #include "vm/compiler/jit/compiler.h" |
| #include "vm/compiler/jit/jit_call_specializer.h" |
| #include "vm/dart_api_impl.h" |
| #include "vm/parser.h" |
| #include "vm/unit_test.h" |
| |
| namespace dart { |
| |
| LibraryPtr LoadTestScript(const char* script, |
| Dart_NativeEntryResolver resolver, |
| const char* lib_uri) { |
| Dart_Handle api_lib; |
| { |
| TransitionVMToNative transition(Thread::Current()); |
| api_lib = TestCase::LoadTestScript(script, resolver, lib_uri); |
| EXPECT_VALID(api_lib); |
| } |
| auto& lib = Library::Handle(); |
| lib ^= Api::UnwrapHandle(api_lib); |
| EXPECT(!lib.IsNull()); |
| return lib.ptr(); |
| } |
| |
| FunctionPtr GetFunction(const Library& lib, const char* name) { |
| Thread* thread = Thread::Current(); |
| const auto& func = Function::Handle(lib.LookupFunctionAllowPrivate( |
| String::Handle(Symbols::New(thread, name)))); |
| EXPECT(!func.IsNull()); |
| return func.ptr(); |
| } |
| |
| ClassPtr GetClass(const Library& lib, const char* name) { |
| Thread* thread = Thread::Current(); |
| const auto& cls = Class::Handle( |
| lib.LookupClassAllowPrivate(String::Handle(Symbols::New(thread, name)))); |
| EXPECT(!cls.IsNull()); |
| return cls.ptr(); |
| } |
| |
| TypeParameterPtr GetClassTypeParameter(const Class& klass, intptr_t index) { |
| const auto& param = TypeParameter::Handle(klass.TypeParameterAt(index)); |
| EXPECT(!param.IsNull()); |
| return param.ptr(); |
| } |
| |
| TypeParameterPtr GetFunctionTypeParameter(const Function& fun, intptr_t index) { |
| const auto& param = TypeParameter::Handle(fun.TypeParameterAt(index)); |
| EXPECT(!param.IsNull()); |
| return param.ptr(); |
| } |
| |
| ObjectPtr Invoke(const Library& lib, const char* name) { |
| Thread* thread = Thread::Current(); |
| Dart_Handle api_lib = Api::NewHandle(thread, lib.ptr()); |
| Dart_Handle result; |
| { |
| TransitionVMToNative transition(thread); |
| result = |
| Dart_Invoke(api_lib, NewString(name), /*argc=*/0, /*argv=*/nullptr); |
| EXPECT_VALID(result); |
| } |
| return Api::UnwrapHandle(result); |
| } |
| |
| FlowGraph* TestPipeline::RunPasses( |
| std::initializer_list<CompilerPass::Id> passes) { |
| // The table dispatch transformation needs a precompiler, which is not |
| // available in the test pipeline. |
| SetFlagScope<bool> sfs(&FLAG_use_table_dispatch, false); |
| |
| auto thread = Thread::Current(); |
| auto zone = thread->zone(); |
| const bool optimized = true; |
| const intptr_t osr_id = Compiler::kNoOSRDeoptId; |
| |
| auto pipeline = CompilationPipeline::New(zone, function_); |
| |
| parsed_function_ = new (zone) |
| ParsedFunction(thread, Function::ZoneHandle(zone, function_.ptr())); |
| pipeline->ParseFunction(parsed_function_); |
| |
| // Extract type feedback before the graph is built, as the graph |
| // builder uses it to attach it to nodes. |
| ic_data_array_ = new (zone) ZoneGrowableArray<const ICData*>(); |
| if (mode_ == CompilerPass::kJIT) { |
| function_.RestoreICDataMap(ic_data_array_, /*clone_ic_data=*/false); |
| } |
| |
| flow_graph_ = pipeline->BuildFlowGraph(zone, parsed_function_, ic_data_array_, |
| osr_id, optimized); |
| |
| if (mode_ == CompilerPass::kAOT) { |
| flow_graph_->PopulateWithICData(function_); |
| } |
| |
| const bool reorder_blocks = |
| FlowGraph::ShouldReorderBlocks(function_, optimized); |
| if (mode_ == CompilerPass::kJIT && reorder_blocks) { |
| BlockScheduler::AssignEdgeWeights(flow_graph_); |
| } |
| |
| SpeculativeInliningPolicy speculative_policy(/*enable_suppression=*/false); |
| pass_state_ = new CompilerPassState(thread, flow_graph_, &speculative_policy); |
| pass_state_->reorder_blocks = reorder_blocks; |
| |
| if (optimized) { |
| JitCallSpecializer jit_call_specializer(flow_graph_, &speculative_policy); |
| AotCallSpecializer aot_call_specializer(/*precompiler=*/nullptr, |
| flow_graph_, &speculative_policy); |
| if (mode_ == CompilerPass::kAOT) { |
| pass_state_->call_specializer = &aot_call_specializer; |
| } else { |
| pass_state_->call_specializer = &jit_call_specializer; |
| } |
| |
| if (passes.size() > 0) { |
| flow_graph_ = CompilerPass::RunPipelineWithPasses(pass_state_, passes); |
| } else { |
| flow_graph_ = CompilerPass::RunPipeline(mode_, pass_state_); |
| } |
| } |
| |
| return flow_graph_; |
| } |
| |
| void TestPipeline::CompileGraphAndAttachFunction() { |
| Zone* zone = thread_->zone(); |
| const bool optimized = true; |
| |
| SpeculativeInliningPolicy speculative_policy(/*enable_suppression=*/false); |
| |
| #if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_IA32) |
| const bool use_far_branches = false; |
| #else |
| const bool use_far_branches = true; |
| #endif |
| |
| ASSERT(pass_state_->inline_id_to_function.length() == |
| pass_state_->caller_inline_id.length()); |
| compiler::ObjectPoolBuilder object_pool_builder; |
| compiler::Assembler assembler(&object_pool_builder, use_far_branches); |
| FlowGraphCompiler graph_compiler( |
| &assembler, flow_graph_, *parsed_function_, optimized, |
| &speculative_policy, pass_state_->inline_id_to_function, |
| pass_state_->inline_id_to_token_pos, pass_state_->caller_inline_id, |
| ic_data_array_); |
| |
| graph_compiler.CompileGraph(); |
| |
| const auto& deopt_info_array = |
| Array::Handle(zone, graph_compiler.CreateDeoptInfo(&assembler)); |
| const auto pool_attachment = Code::PoolAttachment::kAttachPool; |
| Code& code = Code::Handle(); |
| { |
| SafepointWriteRwLocker ml(thread_, |
| thread_->isolate_group()->program_lock()); |
| code ^= Code::FinalizeCode(&graph_compiler, &assembler, pool_attachment, |
| optimized, nullptr); |
| } |
| code.set_is_optimized(optimized); |
| code.set_owner(function_); |
| |
| graph_compiler.FinalizePcDescriptors(code); |
| code.set_deopt_info_array(deopt_info_array); |
| |
| graph_compiler.FinalizeStackMaps(code); |
| graph_compiler.FinalizeVarDescriptors(code); |
| graph_compiler.FinalizeExceptionHandlers(code); |
| graph_compiler.FinalizeCatchEntryMovesMap(code); |
| graph_compiler.FinalizeStaticCallTargetsTable(code); |
| graph_compiler.FinalizeCodeSourceMap(code); |
| |
| { |
| SafepointWriteRwLocker ml(thread_, |
| thread_->isolate_group()->program_lock()); |
| if (optimized) { |
| function_.InstallOptimizedCode(code); |
| } else { |
| function_.set_unoptimized_code(code); |
| function_.AttachCode(code); |
| } |
| } |
| |
| // We expect there to be no deoptimizations. |
| if (mode_ == CompilerPass::kAOT) { |
| EXPECT(deopt_info_array.IsNull() || deopt_info_array.Length() == 0); |
| } |
| } |
| |
| bool ILMatcher::TryMatch(std::initializer_list<MatchCode> match_codes, |
| MatchOpCode insert_before) { |
| std::vector<MatchCode> qcodes = match_codes; |
| |
| if (insert_before != kInvalidMatchOpCode) { |
| for (auto pos = qcodes.begin(); pos < qcodes.end(); pos++) { |
| pos = qcodes.insert(pos, insert_before) + 1; |
| } |
| } |
| |
| if (trace_) { |
| OS::PrintErr("ILMatcher: Matching the following graph\n"); |
| FlowGraphPrinter::PrintGraph("ILMatcher", flow_graph_); |
| OS::PrintErr("ILMatcher: Starting match at %s:\n", cursor_->ToCString()); |
| } |
| |
| Instruction* cursor = cursor_; |
| for (size_t i = 0; i < qcodes.size(); ++i) { |
| Instruction** capture = qcodes[i].capture_; |
| if (parallel_moves_handling_ == ParallelMovesHandling::kSkip) { |
| while (cursor->IsParallelMove()) { |
| cursor = cursor->next(); |
| } |
| } |
| if (trace_) { |
| OS::PrintErr(" matching %30s @ %s\n", |
| MatchOpCodeToCString(qcodes[i].opcode()), |
| cursor->ToCString()); |
| } |
| |
| auto next = MatchInternal(qcodes, i, cursor); |
| if (next == nullptr) { |
| if (trace_) { |
| OS::PrintErr(" -> Match failed\n"); |
| } |
| cursor = next; |
| break; |
| } |
| if (capture != nullptr) { |
| *capture = cursor; |
| } |
| cursor = next; |
| } |
| if (cursor != nullptr) { |
| cursor_ = cursor; |
| return true; |
| } |
| return false; |
| } |
| |
| Instruction* ILMatcher::MatchInternal(std::vector<MatchCode> match_codes, |
| size_t i, |
| Instruction* cursor) { |
| const MatchOpCode opcode = match_codes[i].opcode(); |
| if (opcode == kMatchAndMoveBranchTrue) { |
| auto branch = cursor->AsBranch(); |
| if (branch == nullptr) return nullptr; |
| return branch->true_successor(); |
| } |
| if (opcode == kMatchAndMoveBranchFalse) { |
| auto branch = cursor->AsBranch(); |
| if (branch == nullptr) return nullptr; |
| return branch->false_successor(); |
| } |
| if (opcode == kNop) { |
| return cursor; |
| } |
| if (opcode == kMoveAny) { |
| return cursor->next(); |
| } |
| if (opcode == kMoveParallelMoves) { |
| while (cursor != nullptr && cursor->IsParallelMove()) { |
| cursor = cursor->next(); |
| } |
| return cursor; |
| } |
| |
| if (opcode == kMoveGlob) { |
| ASSERT((i + 1) < match_codes.size()); |
| while (true) { |
| if (cursor == nullptr) return nullptr; |
| if (MatchInternal(match_codes, i + 1, cursor) != nullptr) { |
| return cursor; |
| } |
| if (auto as_goto = cursor->AsGoto()) { |
| cursor = as_goto->successor(); |
| } else { |
| cursor = cursor->next(); |
| } |
| } |
| } |
| |
| if (opcode == kMoveDebugStepChecks) { |
| while (cursor != nullptr && cursor->IsDebugStepCheck()) { |
| cursor = cursor->next(); |
| } |
| return cursor; |
| } |
| |
| if (opcode == kMatchAndMoveGoto) { |
| if (auto goto_instr = cursor->AsGoto()) { |
| return goto_instr->successor(); |
| } |
| } |
| |
| switch (opcode) { |
| #define EMIT_CASE(Instruction, _) \ |
| case kMatch##Instruction: { \ |
| if (cursor->Is##Instruction()) { \ |
| return cursor; \ |
| } \ |
| return nullptr; \ |
| } \ |
| case kMatchAndMove##Instruction: { \ |
| if (cursor->Is##Instruction()) { \ |
| return cursor->next(); \ |
| } \ |
| return nullptr; \ |
| } \ |
| case kMatchAndMoveOptional##Instruction: { \ |
| if (cursor->Is##Instruction()) { \ |
| return cursor->next(); \ |
| } \ |
| return cursor; \ |
| } |
| FOR_EACH_INSTRUCTION(EMIT_CASE) |
| #undef EMIT_CASE |
| default: |
| UNREACHABLE(); |
| } |
| |
| UNREACHABLE(); |
| return nullptr; |
| } |
| |
| const char* ILMatcher::MatchOpCodeToCString(MatchOpCode opcode) { |
| if (opcode == kMatchAndMoveBranchTrue) { |
| return "kMatchAndMoveBranchTrue"; |
| } |
| if (opcode == kMatchAndMoveBranchFalse) { |
| return "kMatchAndMoveBranchFalse"; |
| } |
| if (opcode == kNop) { |
| return "kNop"; |
| } |
| if (opcode == kMoveAny) { |
| return "kMoveAny"; |
| } |
| if (opcode == kMoveParallelMoves) { |
| return "kMoveParallelMoves"; |
| } |
| if (opcode == kMoveGlob) { |
| return "kMoveGlob"; |
| } |
| if (opcode == kMoveDebugStepChecks) { |
| return "kMoveDebugStepChecks"; |
| } |
| |
| switch (opcode) { |
| #define EMIT_CASE(Instruction, _) \ |
| case kMatch##Instruction: \ |
| return "kMatch" #Instruction; \ |
| case kMatchAndMove##Instruction: \ |
| return "kMatchAndMove" #Instruction; \ |
| case kMatchAndMoveOptional##Instruction: \ |
| return "kMatchAndMoveOptional" #Instruction; |
| FOR_EACH_INSTRUCTION(EMIT_CASE) |
| #undef EMIT_CASE |
| default: |
| UNREACHABLE(); |
| } |
| |
| UNREACHABLE(); |
| return nullptr; |
| } |
| |
| } // namespace dart |