diff --git a/runtime/vm/class_finalizer.cc b/runtime/vm/class_finalizer.cc index f4d63f7adc9..ce2bac2a46b 100644 --- a/runtime/vm/class_finalizer.cc +++ b/runtime/vm/class_finalizer.cc @@ -1088,19 +1088,6 @@ void ClassFinalizer::FinalizeTypesInClass(const Class& cls) { interface_class.AddDirectImplementor(cls, /* is_mixin = */ i == mixin_index); } - - if (FLAG_use_cha_deopt) { - // Invalidate all CHA code which depends on knowing the implementors of any - // of the interfaces implemented by this new class. - ClassTable* class_table = thread->isolate()->class_table(); - GrowableArray cids; - InterfaceFinder finder(zone, class_table, &cids); - finder.FindAllInterfaces(cls); - for (intptr_t j = 0; j < cids.length(); ++j) { - interface_class = class_table->At(cids[j]); - interface_class.DisableCHAImplementorUsers(); - } - } } void ClassFinalizer::FinalizeClass(const Class& cls) { @@ -1169,6 +1156,22 @@ void ClassFinalizer::FinalizeClass(const Class& cls) { RemoveCHAOptimizedCode(cls, cids); } + if (FLAG_use_cha_deopt) { + Zone* zone = thread->zone(); + ClassTable* class_table = thread->isolate()->class_table(); + auto& interface_class = Class::Handle(zone); + + // We scan every interface this [cls] implements and invalidate all CHA code + // which depends on knowing the implementors of that interface. + GrowableArray cids; + InterfaceFinder finder(zone, class_table, &cids); + finder.FindAllInterfaces(cls); + for (intptr_t j = 0; j < cids.length(); ++j) { + interface_class = class_table->At(cids[j]); + interface_class.DisableCHAImplementorUsers(); + } + } + if (cls.is_enum_class()) { AllocateEnumValues(cls); } diff --git a/runtime/vm/compiler/aot/aot_call_specializer.cc b/runtime/vm/compiler/aot/aot_call_specializer.cc index b04473bfa86..07d879fe23b 100644 --- a/runtime/vm/compiler/aot/aot_call_specializer.cc +++ b/runtime/vm/compiler/aot/aot_call_specializer.cc @@ -767,8 +767,6 @@ static void EnsureICData(Zone* zone, // TODO(dartbug.com/30635) Evaluate how much this can be shared with // JitCallSpecializer. void AotCallSpecializer::VisitInstanceCall(InstanceCallInstr* instr) { - ASSERT(FLAG_precompiled_mode); - // Type test is special as it always gets converted into inlined code. const Token::Kind op_kind = instr->token_kind(); if (Token::IsTypeTestOperator(op_kind)) { diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc index 3f182e78f9c..5a2d211ec0c 100644 --- a/runtime/vm/compiler/backend/il.cc +++ b/runtime/vm/compiler/backend/il.cc @@ -5055,13 +5055,15 @@ StoreIndexedInstr::StoreIndexedInstr(Value* array, intptr_t class_id, AlignmentType alignment, intptr_t deopt_id, - TokenPosition token_pos) + TokenPosition token_pos, + SpeculativeMode speculative_mode) : TemplateInstruction(deopt_id), emit_store_barrier_(emit_store_barrier), index_scale_(index_scale), class_id_(class_id), alignment_(StrengthenAlignment(class_id, alignment)), - token_pos_(token_pos) { + token_pos_(token_pos), + speculative_mode_(speculative_mode) { SetInputAt(kArrayPos, array); SetInputAt(kIndexPos, index); SetInputAt(kValuePos, value); diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h index fda59b3945a..6feeba16a0e 100644 --- a/runtime/vm/compiler/backend/il.h +++ b/runtime/vm/compiler/backend/il.h @@ -4723,7 +4723,8 @@ class StoreIndexedInstr : public TemplateInstruction<3, NoThrow> { intptr_t class_id, AlignmentType alignment, intptr_t deopt_id, - TokenPosition token_pos); + TokenPosition token_pos, + SpeculativeMode speculative_mode = kGuardInputs); DECLARE_INSTRUCTION(StoreIndexed) enum { kArrayPos = 0, kIndexPos = 1, kValuePos = 2 }; @@ -4747,6 +4748,8 @@ class StoreIndexedInstr : public TemplateInstruction<3, NoThrow> { (emit_store_barrier_ == kEmitStoreBarrier); } + virtual SpeculativeMode speculative_mode() const { return speculative_mode_; } + virtual bool ComputeCanDeoptimize() const { return false; } virtual Representation RequiredInputRepresentation(intptr_t idx) const; @@ -4773,6 +4776,7 @@ class StoreIndexedInstr : public TemplateInstruction<3, NoThrow> { const intptr_t class_id_; const AlignmentType alignment_; const TokenPosition token_pos_; + const SpeculativeMode speculative_mode_; DISALLOW_COPY_AND_ASSIGN(StoreIndexedInstr); }; @@ -6275,6 +6279,7 @@ class CheckedSmiOpInstr : public TemplateDefinition<2, Throws> { virtual bool ComputeCanDeoptimize() const { return false; } virtual CompileType ComputeType() const; + virtual bool RecomputeType(); virtual bool HasUnknownSideEffects() const { return true; } @@ -7022,8 +7027,10 @@ class DoubleToDoubleInstr : public TemplateDefinition<1, NoThrow, Pure> { class DoubleToFloatInstr : public TemplateDefinition<1, NoThrow, Pure> { public: - DoubleToFloatInstr(Value* value, intptr_t deopt_id) - : TemplateDefinition(deopt_id) { + DoubleToFloatInstr(Value* value, + intptr_t deopt_id, + SpeculativeMode speculative_mode = kGuardInputs) + : TemplateDefinition(deopt_id), speculative_mode_(speculative_mode) { SetInputAt(0, value); } @@ -7048,6 +7055,8 @@ class DoubleToFloatInstr : public TemplateDefinition<1, NoThrow, Pure> { return kUnboxedDouble; } + virtual SpeculativeMode speculative_mode() const { return speculative_mode_; } + virtual intptr_t DeoptimizationTarget() const { return GetDeoptId(); } virtual bool AttributesEqual(Instruction* other) const { return true; } @@ -7055,6 +7064,8 @@ class DoubleToFloatInstr : public TemplateDefinition<1, NoThrow, Pure> { virtual Definition* Canonicalize(FlowGraph* flow_graph); private: + const SpeculativeMode speculative_mode_; + DISALLOW_COPY_AND_ASSIGN(DoubleToFloatInstr); }; diff --git a/runtime/vm/compiler/backend/il_test_helper.cc b/runtime/vm/compiler/backend/il_test_helper.cc index 083a2c9db6d..2cfe72a6c95 100644 --- a/runtime/vm/compiler/backend/il_test_helper.cc +++ b/runtime/vm/compiler/backend/il_test_helper.cc @@ -9,6 +9,7 @@ #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" @@ -21,11 +22,12 @@ namespace dart { RawLibrary* LoadTestScript(const char* script, - Dart_NativeEntryResolver resolver) { + Dart_NativeEntryResolver resolver, + const char* lib_uri) { Dart_Handle api_lib; { TransitionVMToNative transition(Thread::Current()); - api_lib = TestCase::LoadTestScript(script, resolver); + api_lib = TestCase::LoadTestScript(script, resolver, lib_uri); } auto& lib = Library::Handle(); lib ^= Api::UnwrapHandle(api_lib); @@ -59,64 +61,248 @@ FlowGraph* TestPipeline::Run(bool is_aot, auto pipeline = CompilationPipeline::New(zone, function_); - auto parsed_function = new (zone) + parsed_function_ = new (zone) ParsedFunction(thread, Function::ZoneHandle(zone, function_.raw())); - pipeline->ParseFunction(parsed_function); + pipeline->ParseFunction(parsed_function_); // Extract type feedback before the graph is built, as the graph // builder uses it to attach it to nodes. - auto ic_data_array = new (zone) ZoneGrowableArray(); + ic_data_array_ = new (zone) ZoneGrowableArray(); if (!is_aot) { - function_.RestoreICDataMap(ic_data_array, /*clone_ic_data=*/false); + function_.RestoreICDataMap(ic_data_array_, /*clone_ic_data=*/false); } - FlowGraph* flow_graph = pipeline->BuildFlowGraph( - zone, parsed_function, ic_data_array, osr_id, optimized); + flow_graph_ = pipeline->BuildFlowGraph(zone, parsed_function_, ic_data_array_, + osr_id, optimized); if (is_aot) { - flow_graph->PopulateWithICData(function_); + flow_graph_->PopulateWithICData(function_); } - BlockScheduler block_scheduler(flow_graph); + BlockScheduler block_scheduler(flow_graph_); const bool reorder_blocks = FlowGraph::ShouldReorderBlocks(function_, optimized); - if (reorder_blocks) { + if (!is_aot && reorder_blocks) { block_scheduler.AssignEdgeWeights(); } SpeculativeInliningPolicy speculative_policy(/*enable_blacklist=*/false); - CompilerPassState pass_state(thread, flow_graph, &speculative_policy); - pass_state.block_scheduler = &block_scheduler; - pass_state.reorder_blocks = reorder_blocks; + pass_state_ = new CompilerPassState(thread, flow_graph_, &speculative_policy); + pass_state_->block_scheduler = &block_scheduler; + pass_state_->reorder_blocks = reorder_blocks; if (optimized) { - pass_state.inline_id_to_function.Add(&function_); + pass_state_->inline_id_to_function.Add(&function_); // We do not add the token position now because we don't know the // position of the inlined call until later. A side effect of this // is that the length of |inline_id_to_function| is always larger // than the length of |inline_id_to_token_pos| by one. // Top scope function has no caller (-1). We do this because we expect // all token positions to be at an inlined call. - pass_state.caller_inline_id.Add(-1); + pass_state_->caller_inline_id.Add(-1); - JitCallSpecializer jit_call_specializer(flow_graph, &speculative_policy); - AotCallSpecializer aot_call_specializer(/*precompiler=*/nullptr, flow_graph, - &speculative_policy); + JitCallSpecializer jit_call_specializer(flow_graph_, &speculative_policy); + AotCallSpecializer aot_call_specializer(/*precompiler=*/nullptr, + flow_graph_, &speculative_policy); if (is_aot) { - pass_state.call_specializer = &aot_call_specializer; + pass_state_->call_specializer = &aot_call_specializer; } else { - pass_state.call_specializer = &jit_call_specializer; + pass_state_->call_specializer = &jit_call_specializer; } - const auto mode = is_aot ? CompilerPass::kJIT : CompilerPass::kAOT; + const auto mode = is_aot ? CompilerPass::kAOT : CompilerPass::kJIT; if (passes.size() > 0) { - CompilerPass::RunPipelineWithPasses(&pass_state, passes); + CompilerPass::RunPipelineWithPasses(pass_state_, passes); } else { - CompilerPass::RunPipeline(mode, &pass_state); + CompilerPass::RunPipeline(mode, pass_state_); } } - return flow_graph; + return flow_graph_; +} + +void TestPipeline::CompileGraphAndAttachFunction() { + Zone* zone = thread_->zone(); + const bool optimized = true; + + SpeculativeInliningPolicy speculative_policy(/*enable_blacklist=*/false); + + ASSERT(pass_state_->inline_id_to_function.length() == + pass_state_->caller_inline_id.length()); + ObjectPoolBuilder object_pool_builder; + Assembler assembler(&object_pool_builder, /*use_far_branches=*/true); + 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; + const auto& code = Code::Handle(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); + + if (optimized) { + function_.InstallOptimizedCode(code); + } else { + function_.set_unoptimized_code(code); + function_.AttachCode(code); + } +} + +bool ILMatcher::TryMatch(std::initializer_list match_codes) { + std::vector qcodes = match_codes; + + 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 (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 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 == 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 == 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; \ + } + 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 == kMoveAny) { + return "kMoveAny"; + } + if (opcode == kMoveParallelMoves) { + return "kMoveParallelMoves"; + } + if (opcode == kMoveGlob) { + return "kMoveGlob"; + } + + switch (opcode) { +#define EMIT_CASE(Instruction, _) \ + case kMatch##Instruction: \ + return "kMatch" #Instruction; \ + case kMatchAndMove##Instruction: \ + return "kMatchAndMove" #Instruction; + FOR_EACH_INSTRUCTION(EMIT_CASE) +#undef EMIT_CASE + default: + UNREACHABLE(); + } + + UNREACHABLE(); + return nullptr; } } // namespace dart diff --git a/runtime/vm/compiler/backend/il_test_helper.h b/runtime/vm/compiler/backend/il_test_helper.h index cf96b29d82d..72ba701bfb0 100644 --- a/runtime/vm/compiler/backend/il_test_helper.h +++ b/runtime/vm/compiler/backend/il_test_helper.h @@ -5,11 +5,15 @@ #ifndef RUNTIME_VM_COMPILER_BACKEND_IL_TEST_HELPER_H_ #define RUNTIME_VM_COMPILER_BACKEND_IL_TEST_HELPER_H_ +#include + #include "include/dart_api.h" #include "platform/allocation.h" +#include "vm/compiler/backend/il.h" #include "vm/compiler/compiler_pass.h" #include "vm/compiler/compiler_state.h" +#include "vm/unit_test.h" // The helpers in this file make it easier to write C++ unit tests which assert // that Dart code gets turned into certain IR. @@ -47,18 +51,20 @@ class RawFunction; class RawLibrary; RawLibrary* LoadTestScript(const char* script, - Dart_NativeEntryResolver resolver = nullptr); + Dart_NativeEntryResolver resolver = nullptr, + const char* lib_uri = USER_TEST_URI); RawFunction* GetFunction(const Library& lib, const char* name); void Invoke(const Library& lib, const char* name); -class TestPipeline { +class TestPipeline : public ValueObject { public: explicit TestPipeline(const Function& function) : function_(function), thread_(Thread::Current()), compiler_state_(thread_) {} + ~TestPipeline() { delete pass_state_; } FlowGraph* RunJITPasses(std::initializer_list passes) { return Run(/*is_aot=*/false, passes); @@ -67,12 +73,124 @@ class TestPipeline { return Run(/*is_aot=*/true, passes); } + void CompileGraphAndAttachFunction(); + private: + // As a side-effect this will populate + // - [ic_data_array_] + // - [parsed_function_] + // - [pass_state_] + // - [flow_graph_] FlowGraph* Run(bool is_aot, std::initializer_list passes); const Function& function_; Thread* thread_; CompilerState compiler_state_; + ZoneGrowableArray* ic_data_array_ = nullptr; + ParsedFunction* parsed_function_ = nullptr; + CompilerPassState* pass_state_ = nullptr; + FlowGraph* flow_graph_ = nullptr; +}; + +// Match opcodes used for [ILMatcher], see below. +enum MatchOpCode { +// Emit a match and match-and-move code for every instruction. +#define DEFINE_MATCH_OPCODES(Instruction, _) \ + kMatch##Instruction, kMatchAndMove##Instruction, + FOR_EACH_INSTRUCTION(DEFINE_MATCH_OPCODES) +#undef DEFINE_MATCH_OPCODES + + // Matches a branch and moves left. + kMatchAndMoveBranchTrue, + + // Matches a branch and moves right. + kMatchAndMoveBranchFalse, + + // Moves forward across any instruction. + kMoveAny, + + // Moves over all parallel moves. + kMoveParallelMoves, + + // Moves forward until the next match code matches. + kMoveGlob, +}; + +// Match codes used for [ILMatcher], see below. +class MatchCode { + public: + MatchCode(MatchOpCode opcode) // NOLINT + : opcode_(opcode), capture_(nullptr) {} + + MatchCode(MatchOpCode opcode, Instruction** capture) + : opcode_(opcode), capture_(capture) {} + +#define DEFINE_TYPED_CONSTRUCTOR(Type, ignored) \ + MatchCode(MatchOpCode opcode, Type##Instr** capture) \ + : opcode_(opcode), capture_(reinterpret_cast(capture)) { \ + RELEASE_ASSERT(opcode == kMatch##Type || opcode == kMatchAndMove##Type); \ + } + FOR_EACH_INSTRUCTION(DEFINE_TYPED_CONSTRUCTOR) +#undef DEFINE_TYPED_CONSTRUCTOR + + MatchOpCode opcode() { return opcode_; } + + private: + friend class ILMatcher; + + MatchOpCode opcode_; + Instruction** capture_; +}; + +// Used for matching a sequence of IL instructions including capturing support. +// +// Example: +// +// TargetEntryInstr* entry = ....; +// BranchInstr* branch = nullptr; +// +// ILMatcher matcher(flow_graph, entry); +// if (matcher.TryMatch({ kMoveGlob, {kMatchBranch, &branch}, })) { +// EXPECT(branch->operation_cid() == kMintCid); +// ... +// } +// +// This match will start at [entry], follow any number instructions (including +// [GotoInstr]s until a [BranchInstr] is found). +// +// If the match was successful, this returns `true` and updates the current +// value for the cursor. +class ILMatcher : public ValueObject { + public: + ILMatcher(FlowGraph* flow_graph, Instruction* cursor, bool trace = true) + : flow_graph_(flow_graph), + cursor_(cursor), + // clang-format off +#if !defined(PRODUCT) + trace_(trace) {} +#else + trace_(false) {} +#endif + // clang-format on + + Instruction* value() { return cursor_; } + + // From the current [value] according to match_codes. + // + // Returns `true` if the match was successful and cursor has been updated, + // otherwise returns `false`. + bool TryMatch(std::initializer_list match_codes); + + private: + Instruction* MatchInternal(std::vector match_codes, + size_t i, + Instruction* cursor); + + const char* MatchOpCodeToCString(MatchOpCode code); + + FlowGraph* flow_graph_; + Instruction* cursor_; + bool trace_; }; } // namespace dart diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc index 604847dd842..0dec0c96690 100644 --- a/runtime/vm/compiler/backend/type_propagator.cc +++ b/runtime/vm/compiler/backend/type_propagator.cc @@ -1458,6 +1458,10 @@ CompileType CheckedSmiOpInstr::ComputeType() const { return CompileType::Dynamic(); } +bool CheckedSmiOpInstr::RecomputeType() { + return UpdateType(ComputeType()); +} + CompileType CheckedSmiComparisonInstr::ComputeType() const { if (Isolate::Current()->can_use_strong_mode_types()) { CompileType* type = call()->Type(); diff --git a/runtime/vm/compiler/backend/typed_data_aot_test.cc b/runtime/vm/compiler/backend/typed_data_aot_test.cc new file mode 100644 index 00000000000..0eab36f772d --- /dev/null +++ b/runtime/vm/compiler/backend/typed_data_aot_test.cc @@ -0,0 +1,426 @@ +// 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 + +#include "vm/compiler/backend/il_printer.h" +#include "vm/compiler/backend/il_test_helper.h" +#include "vm/compiler/call_specializer.h" +#include "vm/compiler/compiler_pass.h" +#include "vm/object.h" +#include "vm/unit_test.h" + +namespace dart { + +#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_DBC) + +// This test asserts that we are inlining accesses to typed data interfaces +// (e.g. Uint8List) if there are no instantiated 3rd party classes. +ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_Inlining) { + const char* kScript = + R"( + import 'dart:typed_data'; + + void foo(Uint8List list, int from) { + if (from >= list.length) { + list[from]; + } + } + )"; + + const auto& root_library = Library::Handle(LoadTestScript(kScript)); + const auto& function = Function::Handle(GetFunction(root_library, "foo")); + + TestPipeline pipeline(function); + FlowGraph* flow_graph = pipeline.RunAOTPasses({}); + + auto entry = flow_graph->graph_entry()->normal_entry(); + EXPECT(entry != nullptr); + + CheckNullInstr* check_null = nullptr; + LoadFieldInstr* load_field = nullptr; + GenericCheckBoundInstr* bounds_check = nullptr; + Instruction* load_untagged = nullptr; + LoadIndexedInstr* load_indexed = nullptr; + + ILMatcher cursor(flow_graph, entry); + RELEASE_ASSERT(cursor.TryMatch({ + kMoveGlob, + {kMatchAndMoveCheckNull, &check_null}, + {kMatchAndMoveLoadField, &load_field}, + kMoveGlob, + kMatchAndMoveBranchTrue, + kMoveGlob, + {kMatchAndMoveGenericCheckBound, &bounds_check}, + {kMatchAndMoveLoadUntagged, &load_untagged}, + kMoveParallelMoves, + {kMatchAndMoveLoadIndexed, &load_indexed}, + kMoveGlob, + kMatchReturn, + })); + + EXPECT(load_field->InputAt(0)->definition()->IsParameter()); + EXPECT(bounds_check->InputAt(0)->definition() == load_field); + EXPECT(load_untagged->InputAt(0)->definition()->IsParameter()); + EXPECT(load_indexed->InputAt(0)->definition() == load_untagged); +} + +// This test asserts that we are not inlining accesses to typed data interfaces +// (e.g. Uint8List) if there are instantiated 3rd party classes (e.g. +// UnmodifiableUint8ListView). +ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_NotInlining) { + const char* kScript = + R"( + import 'dart:typed_data'; + + createThirdPartyUint8List() => UnmodifiableUint8ListView(Uint8List(10)); + + void foo(Uint8List list, int from) { + if (from >= list.length) { + list[from]; + } + } + )"; + + const auto& root_library = Library::Handle(LoadTestScript(kScript)); + + // Firstly we ensure a non internal/external/view Uint8List is allocated. + Invoke(root_library, "createThirdPartyUint8List"); + + // Now we ensure that we don't perform the inlining of the `list[from]` + // access. + const auto& function = Function::Handle(GetFunction(root_library, "foo")); + TestPipeline pipeline(function); + FlowGraph* flow_graph = pipeline.RunAOTPasses({}); + + auto entry = flow_graph->graph_entry()->normal_entry(); + EXPECT(entry != nullptr); + + InstanceCallInstr* length_call = nullptr; + PushArgumentInstr* pusharg1 = nullptr; + PushArgumentInstr* pusharg2 = nullptr; + InstanceCallInstr* index_get_call = nullptr; + + ILMatcher cursor(flow_graph, entry); + RELEASE_ASSERT(cursor.TryMatch({ + kMoveGlob, + {kMatchAndMoveInstanceCall, &length_call}, + kMoveGlob, + kMatchAndMoveBranchTrue, + kMoveGlob, + {kMatchAndMovePushArgument, &pusharg1}, + {kMatchAndMovePushArgument, &pusharg2}, + {kMatchAndMoveInstanceCall, &index_get_call}, + kMoveGlob, + kMatchReturn, + })); + + EXPECT(length_call->Selector() == Symbols::GetLength().raw()); + EXPECT(pusharg1->InputAt(0)->definition()->IsParameter()); + EXPECT(pusharg2->InputAt(0)->definition()->IsParameter()); + EXPECT(index_get_call->Selector() == Symbols::IndexToken().raw()); +} + +// This test asserts that we are inlining get:length, [] and []= for all typed +// data interfaces. It also ensures that the asserted IR actually works by +// exercising it. +ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_FunctionalGetSet) { + const char* kTemplate = + R"( + import 'dart:typed_data'; + + void reverseList(%s list) { + final length = list.length; + final halfLength = length ~/ 2; + for (int i = 0; i < halfLength; ++i) { + final tmp = list[length-i-1]; + list[length-i-1] = list[i]; + list[i] = tmp; + } + } + )"; + + std::initializer_list expected_il = { + // Before loop + kMoveGlob, + kMatchAndMoveCheckNull, + kMatchAndMoveLoadField, + kMoveGlob, + kMatchAndMoveBranchTrue, + + // Loop + kMoveGlob, + // Load 1 + kMatchAndMoveGenericCheckBound, + kMoveGlob, + kMatchAndMoveLoadUntagged, + kMoveParallelMoves, + kMatchAndMoveLoadIndexed, + kMoveGlob, + // Load 2 + kMatchAndMoveGenericCheckBound, + kMoveGlob, + kMatchAndMoveLoadUntagged, + kMoveParallelMoves, + kMatchAndMoveLoadIndexed, + kMoveGlob, + // Store 1 + kMatchAndMoveGenericCheckBound, + kMoveGlob, + kMoveParallelMoves, + kMatchAndMoveLoadUntagged, + kMoveParallelMoves, + kMatchAndMoveStoreIndexed, + kMoveGlob, + // Store 2 + kMoveParallelMoves, + kMatchAndMoveLoadUntagged, + kMoveParallelMoves, + kMatchAndMoveStoreIndexed, + kMoveGlob, + + // Exit the loop. + kMatchAndMoveBranchFalse, + kMoveGlob, + kMatchReturn, + }; + + char script_buffer[1024]; + auto& lib = Library::Handle(); + auto& function = Function::Handle(); + auto& view = TypedDataView::Handle(); + auto& arguments = Array::Handle(); + auto& result = Object::Handle(); + + auto run_reverse_list = [&](const char* name, const TypedDataBase& data) { + // Fill in the template with the [name]. + Utils::SNPrint(script_buffer, sizeof(script_buffer), kTemplate, name); + + // Create a new library, load the function and compile it using our AOT + // pipeline. + lib = LoadTestScript(script_buffer, nullptr, name); + function = GetFunction(lib, "reverseList"); + TestPipeline pipeline(function); + FlowGraph* flow_graph = pipeline.RunAOTPasses({}); + auto entry = flow_graph->graph_entry()->normal_entry(); + + // Ensure the IL matches what we expect. + ILMatcher cursor(flow_graph, entry); + EXPECT(cursor.TryMatch(expected_il)); + + // Class ids are numbered from internal/view/external. + const classid_t view_cid = data.GetClassId() + 1; + ASSERT(RawObject::IsTypedDataViewClassId(view_cid)); + + // First and last element are not in the view, i.e. + // view[0:view.length()-1] = data[1:data.length()-2] + const intptr_t length_in_bytes = + (data.LengthInBytes() - 2 * data.ElementSizeInBytes()); + view = TypedDataView::New(view_cid, data, data.ElementSizeInBytes(), + length_in_bytes / data.ElementSizeInBytes()); + ASSERT(data.ElementType() == view.ElementType()); + + arguments = Array::New(1); + arguments.SetAt(0, view); + result = DartEntry::InvokeFunction(function, arguments); + EXPECT(result.IsNull()); + }; + + const auto& uint8_list = + TypedData::Handle(TypedData::New(kTypedDataUint8ArrayCid, 16)); + const auto& uint8c_list = + TypedData::Handle(TypedData::New(kTypedDataUint8ClampedArrayCid, 16)); + const auto& int16_list = + TypedData::Handle(TypedData::New(kTypedDataInt16ArrayCid, 16)); + const auto& uint16_list = + TypedData::Handle(TypedData::New(kTypedDataUint16ArrayCid, 16)); + const auto& int32_list = + TypedData::Handle(TypedData::New(kTypedDataInt32ArrayCid, 16)); + const auto& uint32_list = + TypedData::Handle(TypedData::New(kTypedDataUint32ArrayCid, 16)); + const auto& int64_list = + TypedData::Handle(TypedData::New(kTypedDataInt64ArrayCid, 16)); + const auto& uint64_list = + TypedData::Handle(TypedData::New(kTypedDataUint64ArrayCid, 16)); + const auto& float32_list = + TypedData::Handle(TypedData::New(kTypedDataFloat32ArrayCid, 16)); + const auto& float64_list = + TypedData::Handle(TypedData::New(kTypedDataFloat64ArrayCid, 16)); + const auto& int8_list = + TypedData::Handle(TypedData::New(kTypedDataInt8ArrayCid, 16)); + for (intptr_t i = 0; i < 16; ++i) { + int8_list.SetInt8(i, i); + uint8_list.SetUint8(i, i); + uint8c_list.SetUint8(i, i); + int16_list.SetInt16(2 * i, i); + uint16_list.SetUint16(2 * i, i); + int32_list.SetInt32(4 * i, i); + uint32_list.SetUint32(4 * i, i); + int64_list.SetInt64(8 * i, i); + uint64_list.SetUint64(8 * i, i); + float32_list.SetFloat32(4 * i, i + 0.5); + float64_list.SetFloat64(8 * i, i + 0.7); + } + run_reverse_list("Uint8List", int8_list); + run_reverse_list("Int8List", uint8_list); + run_reverse_list("Uint8ClampedList", uint8c_list); + run_reverse_list("Int16List", int16_list); + run_reverse_list("Uint16List", uint16_list); + run_reverse_list("Int32List", int32_list); + run_reverse_list("Uint32List", uint32_list); + run_reverse_list("Int64List", int64_list); + run_reverse_list("Uint64List", uint64_list); + run_reverse_list("Float32List", float32_list); + run_reverse_list("Float64List", float64_list); + for (intptr_t i = 0; i < 16; ++i) { + // Only the values in the view are reversed. + const bool in_view = i >= 1 && i < 15; + + const int64_t expected_value = in_view ? (16 - i - 1) : i; + const uint64_t expected_uvalue = in_view ? (16 - i - 1) : i; + const float expected_fvalue = (in_view ? (16 - i - 1) : i) + 0.5; + const double expected_dvalue = (in_view ? (16 - i - 1) : i) + 0.7; + + EXPECT(int8_list.GetInt8(i) == expected_value); + EXPECT(uint8_list.GetUint8(i) == expected_uvalue); + EXPECT(uint8c_list.GetUint8(i) == expected_uvalue); + EXPECT(int16_list.GetInt16(2 * i) == expected_value); + EXPECT(uint16_list.GetUint16(2 * i) == expected_uvalue); + EXPECT(int32_list.GetInt32(4 * i) == expected_value); + EXPECT(uint32_list.GetUint32(4 * i) == expected_uvalue); + EXPECT(int64_list.GetInt64(8 * i) == expected_value); + EXPECT(uint64_list.GetUint64(8 * i) == expected_uvalue); + EXPECT(float32_list.GetFloat32(4 * i) == expected_fvalue); + EXPECT(float64_list.GetFloat64(8 * i) == expected_dvalue); + } +} + +// This test asserts that we get errors if receiver, index or value are null. +ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_FunctionalIndexError) { + const char* kTemplate = + R"( + import 'dart:typed_data'; + void getList(%s list, int index, %s value) { + list[index] = value; + } + )"; + + std::initializer_list expected_il = { + // Receiver null check + kMoveGlob, + kMatchAndMoveCheckNull, + + // Index null check + kMoveGlob, + kMatchAndMoveCheckNull, + + // Value null check + kMoveGlob, + kMatchAndMoveCheckNull, + + // LoadField length + kMoveGlob, + kMatchAndMoveLoadField, + + // Bounds check + kMoveGlob, + kMatchAndMoveGenericCheckBound, + + // Store value. + kMoveGlob, + kMatchAndMoveLoadUntagged, + kMoveParallelMoves, + kMatchAndMoveStoreIndexed, + + // Return + kMoveGlob, + kMatchReturn, + }; + + char script_buffer[1024]; + auto& lib = Library::Handle(); + auto& function = Function::Handle(); + auto& arguments = Array::Handle(); + auto& result = Object::Handle(); + + const intptr_t kIndex = 1; + const intptr_t kLastStage = 3; + + auto run_test = [&](const char* name, const char* type, + const TypedDataBase& data, const Object& value, + int stage) { + // Fill in the template with the [name]. + Utils::SNPrint(script_buffer, sizeof(script_buffer), kTemplate, name, type); + + // Create a new library, load the function and compile it using our AOT + // pipeline. + lib = LoadTestScript(script_buffer, nullptr, name); + function = GetFunction(lib, "getList"); + TestPipeline pipeline(function); + FlowGraph* flow_graph = pipeline.RunAOTPasses({}); + auto entry = flow_graph->graph_entry()->normal_entry(); + + // Ensure the IL matches what we expect. + ILMatcher cursor(flow_graph, entry, /*trace=*/false); + EXPECT(cursor.TryMatch(expected_il)); + + arguments = Array::New(3); + arguments.SetAt(0, stage == 0 ? Object::null_object() : data); + arguments.SetAt( + 1, stage == 1 ? Object::null_object() : Smi::Handle(Smi::New(kIndex))); + arguments.SetAt(2, stage == 2 ? Object::null_object() : value); + result = DartEntry::InvokeFunction(function, arguments); + + if (stage == kLastStage) { + // The last stage must be successful + EXPECT(result.IsNull()); + } else { + // Ensure we get an error. + EXPECT(result.IsUnhandledException()); + result = UnhandledException::Cast(result).exception(); + } + }; + + const auto& uint8_list = + TypedData::Handle(TypedData::New(kTypedDataUint8ArrayCid, 16)); + const auto& uint8c_list = + TypedData::Handle(TypedData::New(kTypedDataUint8ClampedArrayCid, 16)); + const auto& int16_list = + TypedData::Handle(TypedData::New(kTypedDataInt16ArrayCid, 16)); + const auto& uint16_list = + TypedData::Handle(TypedData::New(kTypedDataUint16ArrayCid, 16)); + const auto& int32_list = + TypedData::Handle(TypedData::New(kTypedDataInt32ArrayCid, 16)); + const auto& uint32_list = + TypedData::Handle(TypedData::New(kTypedDataUint32ArrayCid, 16)); + const auto& int64_list = + TypedData::Handle(TypedData::New(kTypedDataInt64ArrayCid, 16)); + const auto& uint64_list = + TypedData::Handle(TypedData::New(kTypedDataUint64ArrayCid, 16)); + const auto& float32_list = + TypedData::Handle(TypedData::New(kTypedDataFloat32ArrayCid, 16)); + const auto& float64_list = + TypedData::Handle(TypedData::New(kTypedDataFloat64ArrayCid, 16)); + const auto& int8_list = + TypedData::Handle(TypedData::New(kTypedDataInt8ArrayCid, 16)); + const auto& int_value = Integer::Handle(Integer::New(42)); + const auto& float_value = Double::Handle(Double::New(4.2)); + for (intptr_t stage = 0; stage <= kLastStage; ++stage) { + run_test("Uint8List", "int", int8_list, int_value, stage); + run_test("Int8List", "int", uint8_list, int_value, stage); + run_test("Uint8ClampedList", "int", uint8c_list, int_value, stage); + run_test("Int16List", "int", int16_list, int_value, stage); + run_test("Uint16List", "int", uint16_list, int_value, stage); + run_test("Int32List", "int", int32_list, int_value, stage); + run_test("Uint32List", "int", uint32_list, int_value, stage); + run_test("Int64List", "int", int64_list, int_value, stage); + run_test("Uint64List", "int", uint64_list, int_value, stage); + run_test("Float32List", "double", float32_list, float_value, stage); + run_test("Float64List", "double", float64_list, float_value, stage); + } +} + +#endif // defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_DBC) + +} // namespace dart diff --git a/runtime/vm/compiler/call_specializer.cc b/runtime/vm/compiler/call_specializer.cc index 266d8db9c98..2148b751b02 100644 --- a/runtime/vm/compiler/call_specializer.cc +++ b/runtime/vm/compiler/call_specializer.cc @@ -17,6 +17,13 @@ namespace dart { #define I (isolate()) #define Z (zone()) +static void RefineUseTypes(Definition* instr) { + CompileType* new_type = instr->Type(); + for (Value::Iterator it(instr->input_use_list()); !it.Done(); it.Advance()) { + it.Current()->RefineReachingType(new_type); + } +} + static bool ShouldInlineSimd() { return FlowGraphCompiler::SupportsUnboxedSimd128(); } @@ -1626,5 +1633,259 @@ bool CallSpecializer::SpecializeTestCidsForNumericTypes( return true; // May deoptimize since we have not identified all 'true' tests. } +void TypedDataSpecializer::Optimize(FlowGraph* flow_graph) { + TypedDataSpecializer optimizer(flow_graph); + optimizer.VisitBlocks(); +} + +void TypedDataSpecializer::EnsureIsInitialized() { + if (initialized_) return; + + initialized_ = true; + + int_type_ = Type::IntType(); + double_type_ = Type::Double(); + + const auto& typed_data = Library::Handle( + Z, Library::LookupLibrary(thread_, Symbols::DartTypedData())); + + auto& td_class = Class::Handle(Z); + auto& direct_implementors = GrowableObjectArray::Handle(Z); + +#define INIT_HANDLE(iface, member_name, type, cid) \ + td_class = typed_data.LookupClass(Symbols::iface()); \ + ASSERT(!td_class.IsNull()); \ + direct_implementors = td_class.direct_implementors(); \ + if (!HasThirdPartyImplementor(direct_implementors)) { \ + member_name = td_class.RareType(); \ + } + + PUBLIC_TYPED_DATA_CLASS_LIST(INIT_HANDLE) +#undef INIT_HANDLE +} + +bool TypedDataSpecializer::HasThirdPartyImplementor( + const GrowableObjectArray& direct_implementors) { + // Check if there are non internal/external/view implementors. + for (intptr_t i = 0; i < direct_implementors.Length(); ++i) { + implementor_ ^= direct_implementors.At(i); + + // We only consider [implementor_] a 3rd party implementor if it was + // finalized by the class finalizer, since only then can we have concrete + // instances of the [implementor_]. + if (implementor_.is_finalized()) { + const classid_t cid = implementor_.id(); + if (!RawObject::IsTypedDataClassId(cid) && + !RawObject::IsTypedDataViewClassId(cid) && + !RawObject::IsExternalTypedDataClassId(cid)) { + return true; + } + } + } + return false; +} + +void TypedDataSpecializer::VisitInstanceCall(InstanceCallInstr* call) { + TryInlineCall(call); +} + +void TypedDataSpecializer::VisitStaticCall(StaticCallInstr* call) { + TryInlineCall(call); +} + +void TypedDataSpecializer::TryInlineCall(TemplateDartCall<0>* call) { + const bool is_length_getter = call->Selector() == Symbols::GetLength().raw(); + const bool is_index_get = call->Selector() == Symbols::IndexToken().raw(); + const bool is_index_set = + call->Selector() == Symbols::AssignIndexToken().raw(); + + if (is_length_getter || is_index_get || is_index_set) { + EnsureIsInitialized(); + + const intptr_t receiver_index = call->FirstArgIndex(); + + CompileType* receiver_type = call->ArgumentAt(receiver_index + 0)->Type(); + + CompileType* index_type = nullptr; + if (is_index_get || is_index_set) { + index_type = call->ArgumentAt(receiver_index + 1)->Type(); + } + + CompileType* value_type = nullptr; + if (is_index_set) { + value_type = call->ArgumentAt(receiver_index + 2)->Type(); + } + + auto& type_class = Class::Handle(zone_); +#define TRY_INLINE(iface, member_name, type, cid) \ + if (!member_name.IsNull()) { \ + if (receiver_type->IsAssignableTo(member_name)) { \ + if (is_length_getter) { \ + type_class = member_name.type_class(); \ + ReplaceWithLengthGetter(call); \ + } else if (is_index_get) { \ + if (!index_type->IsNullableInt()) return; \ + type_class = member_name.type_class(); \ + ReplaceWithIndexGet(call, cid); \ + } else { \ + if (!index_type->IsNullableInt()) return; \ + if (!value_type->IsAssignableTo(type)) return; \ + type_class = member_name.type_class(); \ + ReplaceWithIndexSet(call, cid); \ + } \ + return; \ + } \ + } + PUBLIC_TYPED_DATA_CLASS_LIST(TRY_INLINE) +#undef INIT_HANDLE + } +} + +void TypedDataSpecializer::ReplaceWithLengthGetter(TemplateDartCall<0>* call) { + const intptr_t receiver_idx = call->FirstArgIndex(); + auto array = call->PushArgumentAt(receiver_idx + 0)->value()->definition(); + + if (array->Type()->is_nullable()) { + AppendNullCheck(call, &array); + } + Definition* length = AppendLoadLength(call, array); + flow_graph_->ReplaceCurrentInstruction(current_iterator(), call, length); + RefineUseTypes(length); +} + +void TypedDataSpecializer::ReplaceWithIndexGet(TemplateDartCall<0>* call, + classid_t cid) { + const intptr_t receiver_idx = call->FirstArgIndex(); + auto array = call->PushArgumentAt(receiver_idx + 0)->value()->definition(); + auto index = call->PushArgumentAt(receiver_idx + 1)->value()->definition(); + + if (array->Type()->is_nullable()) { + AppendNullCheck(call, &array); + } + if (index->Type()->is_nullable()) { + AppendNullCheck(call, &index); + } + AppendBoundsCheck(call, array, &index); + Definition* value = AppendLoadIndexed(call, array, index, cid); + flow_graph_->ReplaceCurrentInstruction(current_iterator(), call, value); + RefineUseTypes(value); +} + +void TypedDataSpecializer::ReplaceWithIndexSet(TemplateDartCall<0>* call, + classid_t cid) { + const intptr_t receiver_idx = call->FirstArgIndex(); + auto array = call->PushArgumentAt(receiver_idx + 0)->value()->definition(); + auto index = call->PushArgumentAt(receiver_idx + 1)->value()->definition(); + auto value = call->PushArgumentAt(receiver_idx + 2)->value()->definition(); + + if (array->Type()->is_nullable()) { + AppendNullCheck(call, &array); + } + if (index->Type()->is_nullable()) { + AppendNullCheck(call, &index); + } + if (value->Type()->is_nullable()) { + AppendNullCheck(call, &value); + } + AppendBoundsCheck(call, array, &index); + AppendStoreIndexed(call, array, index, value, cid); + + RELEASE_ASSERT(!call->HasUses()); + flow_graph_->ReplaceCurrentInstruction(current_iterator(), call, nullptr); +} + +void TypedDataSpecializer::AppendNullCheck(TemplateDartCall<0>* call, + Definition** value) { + auto check = + new (Z) CheckNullInstr(new (Z) Value(*value), Symbols::OptimizedOut(), + call->deopt_id(), call->token_pos()); + flow_graph_->InsertBefore(call, check, call->env(), FlowGraph::kValue); + + // Use data dependency as control dependency. + *value = check; +} + +void TypedDataSpecializer::AppendBoundsCheck(TemplateDartCall<0>* call, + Definition* array, + Definition** index) { + auto length = new (Z) LoadFieldInstr( + new (Z) Value(array), Slot::TypedDataBase_length(), call->token_pos()); + flow_graph_->InsertBefore(call, length, call->env(), FlowGraph::kValue); + + auto check = new (Z) GenericCheckBoundInstr( + new (Z) Value(length), new (Z) Value(*index), DeoptId::kNone); + flow_graph_->InsertBefore(call, check, call->env(), FlowGraph::kValue); + + // Use data dependency as control dependency. + *index = check; +} + +Definition* TypedDataSpecializer::AppendLoadLength(TemplateDartCall<0>* call, + Definition* array) { + auto length = new (Z) LoadFieldInstr( + new (Z) Value(array), Slot::TypedDataBase_length(), call->token_pos()); + flow_graph_->InsertBefore(call, length, call->env(), FlowGraph::kValue); + return length; +} + +Definition* TypedDataSpecializer::AppendLoadIndexed(TemplateDartCall<0>* call, + Definition* array, + Definition* index, + classid_t cid) { + const intptr_t element_size = TypedDataBase::ElementSizeFor(cid); + const intptr_t index_scale = element_size; + + auto data = new (Z) LoadUntaggedInstr(new (Z) Value(array), + TypedDataBase::data_field_offset()); + flow_graph_->InsertBefore(call, data, call->env(), FlowGraph::kValue); + + Definition* load = new (Z) + LoadIndexedInstr(new (Z) Value(data), new (Z) Value(index), index_scale, + cid, kAlignedAccess, DeoptId::kNone, call->token_pos()); + flow_graph_->InsertBefore(call, load, call->env(), FlowGraph::kValue); + + if (cid == kTypedDataFloat32ArrayCid) { + load = new (Z) FloatToDoubleInstr(new (Z) Value(load), call->deopt_id()); + flow_graph_->InsertBefore(call, load, call->env(), FlowGraph::kValue); + } + + return load; +} + +void TypedDataSpecializer::AppendStoreIndexed(TemplateDartCall<0>* call, + Definition* array, + Definition* index, + Definition* value, + classid_t cid) { + const intptr_t element_size = TypedDataBase::ElementSizeFor(cid); + const intptr_t index_scale = element_size; + + const auto deopt_id = call->deopt_id(); + + if (cid == kTypedDataFloat32ArrayCid) { + value = new (Z) DoubleToFloatInstr(new (Z) Value(value), deopt_id, + Instruction::kNotSpeculative); + flow_graph_->InsertBefore(call, value, call->env(), FlowGraph::kValue); + } else if (cid == kTypedDataInt32ArrayCid) { + value = new (Z) UnboxInt32Instr(UnboxInt32Instr::kTruncate, + new (Z) Value(value), deopt_id); + flow_graph_->InsertBefore(call, value, call->env(), FlowGraph::kValue); + } else if (cid == kTypedDataUint32ArrayCid) { + value = new (Z) UnboxUint32Instr(new (Z) Value(value), deopt_id); + ASSERT(value->AsUnboxInteger()->is_truncating()); + flow_graph_->InsertBefore(call, value, call->env(), FlowGraph::kValue); + } + + auto data = new (Z) LoadUntaggedInstr(new (Z) Value(array), + TypedDataBase::data_field_offset()); + flow_graph_->InsertBefore(call, data, call->env(), FlowGraph::kValue); + + auto store = new (Z) StoreIndexedInstr( + new (Z) Value(data), new (Z) Value(index), new (Z) Value(value), + kNoStoreBarrier, index_scale, cid, kAlignedAccess, DeoptId::kNone, + call->token_pos(), Instruction::kNotSpeculative); + flow_graph_->InsertBefore(call, store, call->env(), FlowGraph::kEffect); +} + } // namespace dart #endif // DART_PRECOMPILED_RUNTIME diff --git a/runtime/vm/compiler/call_specializer.h b/runtime/vm/compiler/call_specializer.h index cb9ca8ccde0..e9a0a6d580a 100644 --- a/runtime/vm/compiler/call_specializer.h +++ b/runtime/vm/compiler/call_specializer.h @@ -178,6 +178,106 @@ class CallSpecializer : public FlowGraphVisitor { FlowGraph* flow_graph_; }; +#define PUBLIC_TYPED_DATA_CLASS_LIST(V) \ + V(Int8List, int8_list_type_, int_type_, kTypedDataInt8ArrayCid) \ + V(Uint8List, uint8_list_type_, int_type_, kTypedDataUint8ArrayCid) \ + V(Uint8ClampedList, uint8_clamped_type_, int_type_, \ + kTypedDataUint8ClampedArrayCid) \ + V(Int16List, int16_list_type_, int_type_, kTypedDataInt16ArrayCid) \ + V(Uint16List, uint16_list_type_, int_type_, kTypedDataUint16ArrayCid) \ + V(Int32List, int32_list_type_, int_type_, kTypedDataInt32ArrayCid) \ + V(Uint32List, uint32_list_type_, int_type_, kTypedDataUint32ArrayCid) \ + V(Int64List, int64_list_type_, int_type_, kTypedDataInt64ArrayCid) \ + V(Uint64List, uint64_list_type_, int_type_, kTypedDataUint64ArrayCid) \ + V(Float32List, float32_list_type_, double_type_, kTypedDataFloat32ArrayCid) \ + V(Float64List, float64_list_type_, double_type_, kTypedDataFloat64ArrayCid) + +// Specializes instance/static calls with receiver type being a typed data +// interface (if that interface is only implemented by internal/external/view +// typed data classes). +// +// For example: +// +// foo(Uint8List bytes) => bytes[0]; +// +// Would be translated to something like this: +// +// v0 <- Constant(0) +// +// // Ensures the list is non-null. +// v1 <- ParameterInstr(0) +// v2 <- CheckNull(v1) +// +// // Load the length & perform bounds checks +// v3 <- LoadField(v2, "TypedDataBase.length"); +// v4 <- GenericCheckBounds(v3, v0); +// +// // Directly access the byte, independent of whether `bytes` is +// // _Uint8List, _Uint8ArrayView or _ExternalUint8Array. +// v5 <- LoadUntagged(v1, "TypedDataBase.data"); +// v5 <- LoadIndexed(v5, v4) +// +class TypedDataSpecializer : public FlowGraphVisitor { + public: + static void Optimize(FlowGraph* flow_graph); + + virtual void VisitInstanceCall(InstanceCallInstr* instr); + virtual void VisitStaticCall(StaticCallInstr* instr); + + private: + // clang-format off + explicit TypedDataSpecializer(FlowGraph* flow_graph) + : FlowGraphVisitor(flow_graph->reverse_postorder()), + thread_(Thread::Current()), + zone_(thread_->zone()), + flow_graph_(flow_graph), +#define ALLOCATE_HANDLE(iface, member_name, type, cid) \ + member_name(AbstractType::Handle(zone_)), + PUBLIC_TYPED_DATA_CLASS_LIST(ALLOCATE_HANDLE) +#undef INIT_HANDLE + int_type_(AbstractType::Handle()), + double_type_(AbstractType::Handle()), + implementor_(Class::Handle()) { + } + // clang-format on + + void EnsureIsInitialized(); + bool HasThirdPartyImplementor(const GrowableObjectArray& direct_implementors); + void TryInlineCall(TemplateDartCall<0>* call); + void ReplaceWithLengthGetter(TemplateDartCall<0>* call); + void ReplaceWithIndexGet(TemplateDartCall<0>* call, classid_t cid); + void ReplaceWithIndexSet(TemplateDartCall<0>* call, classid_t cid); + void AppendNullCheck(TemplateDartCall<0>* call, Definition** array); + void AppendBoundsCheck(TemplateDartCall<0>* call, + Definition* array, + Definition** index); + Definition* AppendLoadLength(TemplateDartCall<0>* call, Definition* array); + Definition* AppendLoadIndexed(TemplateDartCall<0>* call, + Definition* array, + Definition* index, + classid_t cid); + void AppendStoreIndexed(TemplateDartCall<0>* call, + Definition* array, + Definition* index, + Definition* value, + classid_t cid); + + Zone* zone() const { return zone_; } + + Thread* thread_; + Zone* zone_; + FlowGraph* flow_graph_; + bool initialized_ = false; + +#define DEF_HANDLE(iface, member_name, type, cid) AbstractType& member_name; + PUBLIC_TYPED_DATA_CLASS_LIST(DEF_HANDLE) +#undef DEF_HANDLE + + AbstractType& int_type_; + AbstractType& double_type_; + Class& implementor_; +}; + } // namespace dart #endif // RUNTIME_VM_COMPILER_CALL_SPECIALIZER_H_ diff --git a/runtime/vm/compiler/compiler_pass.cc b/runtime/vm/compiler/compiler_pass.cc index a6ceb605b9c..3e08c06a439 100644 --- a/runtime/vm/compiler/compiler_pass.cc +++ b/runtime/vm/compiler/compiler_pass.cc @@ -241,6 +241,9 @@ void CompilerPass::RunPipeline(PipelineMode mode, // unreachable code. INVOKE_PASS(ApplyICData); } + if (mode == kAOT) { + INVOKE_PASS(OptimizeTypedDataAccesses); + } #endif INVOKE_PASS(WidenSmiToInt32); INVOKE_PASS(SelectRepresentations); @@ -371,6 +374,9 @@ COMPILER_PASS(OptimizeBranches, { ConstantPropagator::OptimizeBranches(flow_graph); }); +COMPILER_PASS(OptimizeTypedDataAccesses, + { TypedDataSpecializer::Optimize(flow_graph); }); + COMPILER_PASS(TryCatchOptimization, { OptimizeCatchEntryStates(flow_graph, /*is_aot=*/FLAG_precompiled_mode); }); diff --git a/runtime/vm/compiler/compiler_pass.h b/runtime/vm/compiler/compiler_pass.h index 3c16df835b1..e5aa8230eff 100644 --- a/runtime/vm/compiler/compiler_pass.h +++ b/runtime/vm/compiler/compiler_pass.h @@ -36,6 +36,7 @@ namespace dart { V(LICM) \ V(OptimisticallySpecializeSmiPhis) \ V(OptimizeBranches) \ + V(OptimizeTypedDataAccesses) \ V(RangeAnalysis) \ V(ReorderBlocks) \ V(SelectRepresentations) \ diff --git a/runtime/vm/compiler/compiler_sources.gni b/runtime/vm/compiler/compiler_sources.gni index a0f521b7ddd..f3882958988 100644 --- a/runtime/vm/compiler/compiler_sources.gni +++ b/runtime/vm/compiler/compiler_sources.gni @@ -163,5 +163,6 @@ compiler_sources_tests = [ "backend/redundancy_elimination_test.cc", "backend/slot_test.cc", "backend/type_propagator_test.cc", + "backend/typed_data_aot_test.cc", "cha_test.cc", ] diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index 771116b79b4..845d780acdd 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -209,6 +209,7 @@ class ObjectPointerVisitor; V(LanguageError, "LanguageError") \ V(LeftShiftOperator, "<<") \ V(Length, "length") \ + V(GetLength, "get:length") \ V(LessEqualOperator, "<=") \ V(LibraryClass, "Library") \ V(LibraryPrefix, "LibraryPrefix") \