| // Copyright (c) 2017, 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. |
| |
| #ifndef RUNTIME_VM_COMPILER_CALL_SPECIALIZER_H_ |
| #define RUNTIME_VM_COMPILER_CALL_SPECIALIZER_H_ |
| |
| #if defined(DART_PRECOMPILED_RUNTIME) |
| #error "AOT runtime should not use compiler sources (including header files)" |
| #endif // defined(DART_PRECOMPILED_RUNTIME) |
| |
| #include "vm/compiler/backend/flow_graph.h" |
| #include "vm/compiler/backend/il.h" |
| |
| namespace dart { |
| |
| class SpeculativeInliningPolicy; |
| |
| // Call specialization pass is responsible for replacing instance calls by |
| // faster alternatives based on type feedback (JIT), type speculations (AOT), |
| // locally propagated type information or global type information. |
| // |
| // This pass for example can |
| // |
| // * Replace a call to a binary arithmetic operator with corresponding IL |
| // instructions and necessary checks; |
| // * Replace a dynamic call with a static call, if reciever is known |
| // to have a certain class id; |
| // * Replace type check with a range check |
| // |
| // CallSpecializer is a base class that contains logic shared between |
| // JIT and AOT compilation pipelines, see JitCallSpecializer for JIT specific |
| // optimizations and AotCallSpecializer for AOT specific optimizations. |
| class CallSpecializer : public FlowGraphVisitor { |
| public: |
| CallSpecializer(FlowGraph* flow_graph, |
| SpeculativeInliningPolicy* speculative_policy, |
| bool should_clone_fields) |
| : FlowGraphVisitor(flow_graph->reverse_postorder()), |
| speculative_policy_(speculative_policy), |
| should_clone_fields_(should_clone_fields), |
| flow_graph_(flow_graph) {} |
| |
| virtual ~CallSpecializer() {} |
| |
| FlowGraph* flow_graph() const { return flow_graph_; } |
| |
| void set_flow_graph(FlowGraph* flow_graph) { |
| flow_graph_ = flow_graph; |
| set_block_order(flow_graph->reverse_postorder()); |
| } |
| |
| // Use ICData to optimize, replace or eliminate instructions. |
| void ApplyICData(); |
| |
| // Use propagated class ids to optimize, replace or eliminate instructions. |
| void ApplyClassIds(); |
| |
| virtual void ReplaceInstanceCallsWithDispatchTableCalls(); |
| |
| void InsertBefore(Instruction* next, |
| Instruction* instr, |
| Environment* env, |
| FlowGraph::UseKind use_kind) { |
| flow_graph_->InsertBefore(next, instr, env, use_kind); |
| } |
| void InsertSpeculativeBefore(Instruction* next, |
| Instruction* instr, |
| Environment* env, |
| FlowGraph::UseKind use_kind) { |
| flow_graph_->InsertSpeculativeBefore(next, instr, env, use_kind); |
| } |
| |
| virtual void VisitStaticCall(StaticCallInstr* instr); |
| |
| // TODO(dartbug.com/30633) these methods have nothing to do with |
| // specialization of calls. They are here for historical reasons. |
| // Find a better place for them. |
| virtual void VisitLoadCodeUnits(LoadCodeUnitsInstr* instr); |
| |
| protected: |
| Thread* thread() const { return flow_graph_->thread(); } |
| IsolateGroup* isolate_group() const { return flow_graph_->isolate_group(); } |
| Zone* zone() const { return flow_graph_->zone(); } |
| const Function& function() const { return flow_graph_->function(); } |
| |
| bool TryReplaceWithIndexedOp(InstanceCallInstr* call); |
| |
| bool TryReplaceWithBinaryOp(InstanceCallInstr* call, Token::Kind op_kind); |
| bool TryReplaceWithUnaryOp(InstanceCallInstr* call, Token::Kind op_kind); |
| |
| bool TryReplaceWithEqualityOp(InstanceCallInstr* call, Token::Kind op_kind); |
| bool TryReplaceWithRelationalOp(InstanceCallInstr* call, Token::Kind op_kind); |
| |
| bool TryInlineInstanceGetter(InstanceCallInstr* call); |
| bool TryInlineInstanceSetter(InstanceCallInstr* call); |
| |
| bool TryInlineInstanceMethod(InstanceCallInstr* call); |
| void ReplaceWithInstanceOf(InstanceCallInstr* instr); |
| |
| // Replaces a call where the replacement code does not end in a |
| // value-returning instruction, so we must specify what definition should be |
| // used instead to replace uses of the call return value. |
| void ReplaceCallWithResult(Definition* call, |
| Instruction* replacement, |
| Definition* result); |
| void ReplaceCall(Definition* call, Definition* replacement); |
| |
| // Add a class check for the call's first argument (receiver). |
| void AddReceiverCheck(InstanceCallInstr* call) { |
| AddCheckClass(call->Receiver()->definition(), call->Targets(), |
| call->deopt_id(), call->env(), call); |
| } |
| |
| // Insert a null check if needed. |
| void AddCheckNull(Value* to_check, |
| const String& function_name, |
| intptr_t deopt_id, |
| Environment* deopt_environment, |
| Instruction* insert_before); |
| |
| // Attempt to build ICData for call using propagated class-ids. |
| virtual bool TryCreateICData(InstanceCallInstr* call); |
| |
| virtual bool TryReplaceInstanceOfWithRangeCheck(InstanceCallInstr* call, |
| const AbstractType& type); |
| |
| virtual bool TryOptimizeStaticCallUsingStaticTypes(StaticCallInstr* call) = 0; |
| |
| protected: |
| void InlineImplicitInstanceGetter(Definition* call, const Field& field); |
| |
| // Insert a check of 'to_check' determined by 'unary_checks'. If the |
| // check fails it will deoptimize to 'deopt_id' using the deoptimization |
| // environment 'deopt_environment'. The check is inserted immediately |
| // before 'insert_before'. |
| void AddCheckClass(Definition* to_check, |
| const Cids& cids, |
| intptr_t deopt_id, |
| Environment* deopt_environment, |
| Instruction* insert_before); |
| |
| SpeculativeInliningPolicy* speculative_policy_; |
| const bool should_clone_fields_; |
| |
| private: |
| bool TypeCheckAsClassEquality(const AbstractType& type); |
| |
| // Insert a Smi check if needed. |
| void AddCheckSmi(Definition* to_check, |
| intptr_t deopt_id, |
| Environment* deopt_environment, |
| Instruction* insert_before); |
| |
| // Add a class check for a call's nth argument immediately before the |
| // call, using the call's IC data to determine the check, and the call's |
| // deopt ID and deoptimization environment if the check fails. |
| void AddChecksForArgNr(InstanceCallInstr* call, |
| Definition* argument, |
| int argument_number); |
| |
| bool InlineSimdBinaryOp(InstanceCallInstr* call, |
| intptr_t cid, |
| Token::Kind op_kind); |
| |
| bool TryInlineImplicitInstanceGetter(InstanceCallInstr* call); |
| |
| BoolPtr InstanceOfAsBool(const ICData& ic_data, |
| const AbstractType& type, |
| ZoneGrowableArray<intptr_t>* results) const; |
| |
| bool TryOptimizeInstanceOfUsingStaticTypes(InstanceCallInstr* call, |
| const AbstractType& type); |
| |
| bool TryStringLengthOneEquality(InstanceCallInstr* call, Token::Kind op_kind); |
| |
| void SpecializePolymorphicInstanceCall(PolymorphicInstanceCallInstr* call); |
| |
| // Tries to add cid tests to 'results' so that no deoptimization is |
| // necessary for common number-related type tests. Unconditionally adds an |
| // entry for the Smi type to the start of the array. |
| static bool SpecializeTestCidsForNumericTypes( |
| ZoneGrowableArray<intptr_t>* results, |
| const AbstractType& type); |
| |
| 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_ |