Files
sdk/runtime/vm/compiler/call_specializer.h
T
Tess Strickland 4d1bdaaca9 Reland "[vm/compiler] Change MemoryCopy to also take untagged addresses."
This is a reland of commit 06d7a2352e

This version fixes an issue when a phi node has multiple inputs with
different unboxed integer representations. The original CL made a
change where only the representations were considered, not the range
of values for the Phi calculated by range analysis. The reland goes
back to the old behavior for this case.

Also fixes the new tests on 32-bit architectures.

Original change's description:
> [vm/compiler] Change MemoryCopy to also take untagged addresses.
>
> This CL adds the ability to pass the payload address of the source
> and destination directly to the MemoryCopy instruction as an untagged
> value.
>
> The new translation of the _TypedListBase._memMoveN methods use the new
> MemoryCopy constructor, retrieving the untagged value of the data field
> of both the source and destination. This way, if inlining exposes the
> allocation of the object from which the data field is being retrieved,
> then allocation sinking can remove the intermediate allocation if there
> are no escaping uses of the object.
>
> Since Pointer.asTypedList allocates such ExternalTypedData objects,
> this CL makes that method inlined if at all possible, which removes
> the intermediate allocation if the only use of the TypedData object
> is to call setRange for memory copying purposes.
>
> This CL also separates unboxed native slots into two groups: those
> that contain untagged addresses and those that do not. The former
> group now have the kUntagged representation, which mimics the old
> use of LoadUntagged for the PointerBase data field and also ensures
> that any arithmetic operations on untagged addresses must first be
> explicitly converted to an unboxed integer and then explicitly converted
> back to untagged before being stored in a slot that contains untagged
> addresses.
>
> When a unboxed native slot that contains untagged addresses is defined,
> the definition also includes a boolean which represents whether
> addresses that may be moved by the GC can be stored in this slot or not.
> The redundancy eliminator uses this to decide whether it is safe to
> eliminate a duplicate load, replace a load with the value originally
> stored in the slot, or lift a load out of a loop.
>
> In particular, the PointerBase data field may contain GC-moveable
> addresses, but only for internal TypedData objects and views, not
> for external TypedData objects or Pointers. To allow load optimizations
> involving the latter, the LoadField and StoreField instructions now
> take boolean flags for whether loads or stores from the slot are
> guaranteed to not be GC-moveable, to override the information from
> the slot argument.
>
> Notable benchmark changes on x64 (similar for other archs unless noted):
>
> JIT:
> * FfiMemory.PointerPointer: 250.7%
> * FfiStructCopy.Copy1Bytes: -26.73% (only x64)
> * FfiStructCopy.Copy32Bytes: -25.18% (only x64)
> * MemoryCopy.64.setRange.Pointer.Uint8: 19.36%
> * MemoryCopy.64.setRange.Pointer.Double: 18.96%
> * MemoryCopy.8.setRange.Pointer.Double: 17.59%
> * MemoryCopy.8.setRange.Pointer.Uint8: 19.46%
>
> AOT:
> * FfiMemory.PointerPointer: 323.5%
> * FfiStruct.FieldLoadStore: 483.3%
> * FileIO_readwrite_64kb: 15.39%
> * FileIO_readwrite_512kb (Intel Xeon): 46.22%
> * MemoryCopy.512.setRange.Pointer.Uint8: 35.20%
> * MemoryCopy.64.setRange.Pointer.Uint8: 55.40%
> * MemoryCopy.512.setRange.Pointer.Double: 29.45%
> * MemoryCopy.64.setRange.Pointer.Double: 60.37%
> * MemoryCopy.8.setRange.Pointer.Double: 59.54%
> * MemoryCopy.8.setRange.Pointer.Uint8: 55.40%
> * FfiStructCopy.Copy32Bytes: 398.3%
> * FfiStructCopy.Copy1Bytes: 1233%
>
> TEST=vm/dart/address_local_pointer, vm/dart/pointer_as_typed_list
>
> Issue: https://github.com/dart-lang/sdk/issues/42072
> Fixes: https://github.com/dart-lang/sdk/issues/53124
>
> Cq-Include-Trybots: luci.dart.try:vm-ffi-qemu-linux-release-arm-try,vm-eager-optimization-linux-release-x64-try,vm-linux-release-x64-try,vm-linux-debug-x64-try,vm-aot-linux-release-x64-try,vm-aot-linux-debug-x64-try
> Change-Id: I563e0bfac5b1ac6cf1111649934067c12891b631
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/324820
> Reviewed-by: Alexander Markov <alexmarkov@google.com>
> Commit-Queue: Tess Strickland <sstrickl@google.com>
> Reviewed-by: Martin Kustermann <kustermann@google.com>

TEST=vm/dart/address_local_pointer, vm/dart/pointer_as_typed_list

Issue: https://github.com/dart-lang/sdk/issues/42072
Fixes: https://github.com/dart-lang/sdk/issues/53124

Change-Id: Iabb0e910f12636d0ff51e711c8c9c98ad40e5811
Cq-Include-Trybots: luci.dart.try:vm-ffi-qemu-linux-release-arm-try,vm-eager-optimization-linux-release-x64-try,vm-linux-release-x64-try,vm-linux-debug-x64-try,vm-aot-linux-release-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-release-simarm_x64-try,vm-aot-linux-debug-simarm_x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/330600
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2023-10-17 07:41:49 +00:00

292 lines
12 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_
#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 receiver 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, intptr_t* type_cid);
// 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 <- LoadField(Slot::PointerBase_data(), v1);
// 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();
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 AppendMutableCheck(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_