Files
sdk/runtime/vm/compiler/call_specializer.h
T
Martin Kustermann dbfd00f44d [vm/compiler] Add new optimization pass which inlines typed data accesses
Based on the unified typed data layout, we can now inline accesses to
typed data interface classes if there are no 3rd party implementations
of those interfaces.

Example: If a receiver is of type Uint8List and we call `[]` or `[]=` we
will inline the byte access.

Instead of changing the existing inliner / call specializer we add this
as an extra pass: If the inliner / call specializer infer that the
receiver type is e.g. internal typed data then it will perform the
inlining itself using more optimized LoadIndexed instruction.

=> Only if those existing optimization passes have not been able to inline
   the access will we, later on in the compilation pipeline, run a
   specialized pass which will inline the accesses using LoadUntagged +
   LoadIndexed (which is slightly less efficient than using only LoadIndexed
   for internal typed data).

As a first step this is only done for AOT.

For ease of writing tests matching certain IR graphs this CL also adds a
IR pattern matcher.

Issue https://github.com/dart-lang/sdk/issues/35154

Cq-Include-Trybots: luci.dart.try:vm-canary-linux-debug-try, vm-dartkb-linux-debug-x64-try, vm-dartkb-linux-release-x64-try, vm-kernel-asan-linux-release-x64-try, vm-kernel-checked-linux-release-x64-try, vm-kernel-linux-debug-ia32-try, vm-kernel-linux-debug-simdbc64-try, vm-kernel-linux-debug-x64-try, vm-kernel-linux-product-x64-try, vm-kernel-linux-release-ia32-try, vm-kernel-linux-release-simarm-try, vm-kernel-linux-release-simarm64-try, vm-kernel-linux-release-simdbc64-try, vm-kernel-linux-release-x64-try, vm-kernel-optcounter-threshold-linux-release-ia32-try, vm-kernel-optcounter-threshold-linux-release-x64-try, vm-kernel-precomp-android-release-arm-try, vm-kernel-precomp-bare-linux-release-simarm-try, vm-kernel-precomp-bare-linux-release-simarm64-try, vm-kernel-precomp-bare-linux-release-x64-try, vm-kernel-precomp-linux-debug-x64-try, vm-kernel-precomp-linux-product-x64-try, vm-kernel-precomp-linux-release-simarm-try, vm-kernel-precomp-linux-release-simarm64-try, vm-kernel-precomp-linux-release-x64-try, vm-kernel-precomp-obfuscate-linux-release-x64-try, vm-kernel-precomp-win-release-simarm64-try, vm-kernel-precomp-win-release-x64-try, vm-kernel-reload-linux-debug-x64-try, vm-kernel-reload-linux-release-x64-try, vm-kernel-reload-rollback-linux-debug-x64-try, vm-kernel-reload-rollback-linux-release-x64-try, vm-kernel-win-debug-ia32-try, vm-kernel-win-debug-x64-try, vm-kernel-win-product-x64-try, vm-kernel-win-release-ia32-try, vm-kernel-win-release-x64-try

Change-Id: I5f2e01a55f46b473f64478b05679f65b9fd7c4c8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/98662
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
2019-04-05 15:51:34 +00:00

284 lines
11 KiB
C++

// 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_
#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_; }
// Use ICData to optimize, replace or eliminate instructions.
void ApplyICData();
// Use propagated class ids to optimize, replace or eliminate instructions.
void ApplyClassIds();
void InsertBefore(Instruction* next,
Instruction* instr,
Environment* env,
FlowGraph::UseKind use_kind) {
flow_graph_->InsertBefore(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(); }
Isolate* isolate() const { return flow_graph_->isolate(); }
Zone* zone() const { return flow_graph_->zone(); }
const Function& function() const { return flow_graph_->function(); }
bool TryReplaceWithIndexedOp(InstanceCallInstr* call,
const ICData* unary_checks);
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,
const ICData& unary_ic_data);
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) {
AddChecksForArgNr(call, call->Receiver()->definition(),
/* argument_number = */ 0);
}
// 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);
static bool HasOnlyTwoOf(const ICData& ic_data, intptr_t cid);
virtual bool TryReplaceInstanceOfWithRangeCheck(InstanceCallInstr* call,
const AbstractType& type);
virtual bool TryOptimizeStaticCallUsingStaticTypes(StaticCallInstr* call) = 0;
protected:
void InlineImplicitInstanceGetter(Definition* call, const Field& field);
SpeculativeInliningPolicy* speculative_policy_;
const bool should_clone_fields_;
private:
bool TypeCheckAsClassEquality(const AbstractType& type);
// 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);
// 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* instr,
int argument_number);
bool InlineSimdBinaryOp(InstanceCallInstr* call,
intptr_t cid,
Token::Kind op_kind);
bool TryInlineImplicitInstanceGetter(InstanceCallInstr* call);
RawBool* InstanceOfAsBool(const ICData& ic_data,
const AbstractType& type,
ZoneGrowableArray<intptr_t>* results) const;
bool TryOptimizeInstanceOfUsingStaticTypes(InstanceCallInstr* call,
const AbstractType& type);
void ReplaceWithMathCFunction(InstanceCallInstr* call,
MethodRecognizer::Kind recognized_kind);
bool TryStringLengthOneEquality(InstanceCallInstr* call, Token::Kind op_kind);
RawField* GetField(intptr_t class_id, const String& field_name);
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_