df5cae97dc
This reverts commit61f45d66b2. This reverts commita0ab33ac52. This reverts commit68e2512ace. This reverts commitfb732a570d. TEST=ci Bug: b/501539846 Cq-Include-Trybots: luci.dart.try:vm-fuchsia-release-x64-try,vm-fuchsia-release-arm64-try,vm-ffi-mac-debug-simarm64_arm64-try,vm-ffi-mac-release-simarm64_arm64-try Change-Id: I7f27bb15bf1fcb26fe8793a043b7530ed99a02a7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/495480 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
1298 lines
48 KiB
C++
1298 lines
48 KiB
C++
// Copyright (c) 2012, 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_BACKEND_FLOW_GRAPH_COMPILER_H_
|
|
#define RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_COMPILER_H_
|
|
|
|
#include "vm/compiler/runtime_api.h"
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
#error "AOT runtime should not use compiler sources (including header files)"
|
|
#endif // defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
#include <functional>
|
|
|
|
#include "vm/allocation.h"
|
|
#include "vm/code_descriptors.h"
|
|
#include "vm/compiler/assembler/assembler.h"
|
|
#include "vm/compiler/assembler/object_pool_builder.h"
|
|
#include "vm/compiler/backend/code_statistics.h"
|
|
#include "vm/compiler/backend/il.h"
|
|
#include "vm/compiler/backend/locations.h"
|
|
#include "vm/runtime_entry.h"
|
|
|
|
namespace dart {
|
|
|
|
// Forward declarations.
|
|
class CatchEntryMovesMapBuilder;
|
|
class Code;
|
|
class DeoptInfoBuilder;
|
|
class FlowGraph;
|
|
class FlowGraphCompiler;
|
|
class Function;
|
|
template <typename T>
|
|
class GrowableArray;
|
|
class ParsedFunction;
|
|
|
|
namespace compiler {
|
|
struct TableSelector;
|
|
}
|
|
|
|
// Used in methods which need conditional access to a temporary register.
|
|
// May only be used to allocate a single temporary register.
|
|
class TemporaryRegisterAllocator : public ValueObject {
|
|
public:
|
|
virtual ~TemporaryRegisterAllocator() {}
|
|
virtual Register AllocateTemporary() = 0;
|
|
virtual void ReleaseTemporary() = 0;
|
|
};
|
|
|
|
class ConstantTemporaryAllocator : public TemporaryRegisterAllocator {
|
|
public:
|
|
explicit ConstantTemporaryAllocator(Register tmp) : tmp_(tmp) {}
|
|
|
|
Register AllocateTemporary() override { return tmp_; }
|
|
void ReleaseTemporary() override {}
|
|
|
|
private:
|
|
Register const tmp_;
|
|
};
|
|
|
|
class NoTemporaryAllocator : public TemporaryRegisterAllocator {
|
|
public:
|
|
Register AllocateTemporary() override { UNREACHABLE(); }
|
|
void ReleaseTemporary() override { UNREACHABLE(); }
|
|
};
|
|
|
|
// Used for describing a deoptimization point after call (lazy deoptimization).
|
|
// For deoptimization before instruction use class CompilerDeoptInfoWithStub.
|
|
class CompilerDeoptInfo : public ZoneObject {
|
|
public:
|
|
CompilerDeoptInfo(intptr_t deopt_id,
|
|
ICData::DeoptReasonId reason,
|
|
uint32_t flags,
|
|
Environment* deopt_env)
|
|
: pc_offset_(-1),
|
|
deopt_id_(deopt_id),
|
|
reason_(reason),
|
|
flags_(flags),
|
|
deopt_env_(deopt_env) {
|
|
ASSERT(deopt_env != nullptr);
|
|
}
|
|
virtual ~CompilerDeoptInfo() {}
|
|
|
|
TypedDataPtr CreateDeoptInfo(FlowGraphCompiler* compiler,
|
|
DeoptInfoBuilder* builder,
|
|
const Array& deopt_table);
|
|
|
|
// No code needs to be generated.
|
|
virtual void GenerateCode(FlowGraphCompiler* compiler, intptr_t stub_ix) {}
|
|
|
|
intptr_t pc_offset() const { return pc_offset_; }
|
|
void set_pc_offset(intptr_t offset) { pc_offset_ = offset; }
|
|
|
|
intptr_t deopt_id() const { return deopt_id_; }
|
|
ICData::DeoptReasonId reason() const { return reason_; }
|
|
uint32_t flags() const { return flags_; }
|
|
const Environment* deopt_env() const { return deopt_env_; }
|
|
|
|
private:
|
|
void EmitMaterializations(Environment* env, DeoptInfoBuilder* builder);
|
|
|
|
void AllocateOutgoingArguments(Environment* env);
|
|
|
|
intptr_t pc_offset_;
|
|
const intptr_t deopt_id_;
|
|
const ICData::DeoptReasonId reason_;
|
|
const uint32_t flags_;
|
|
Environment* deopt_env_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(CompilerDeoptInfo);
|
|
};
|
|
|
|
class CompilerDeoptInfoWithStub : public CompilerDeoptInfo {
|
|
public:
|
|
CompilerDeoptInfoWithStub(intptr_t deopt_id,
|
|
ICData::DeoptReasonId reason,
|
|
uint32_t flags,
|
|
Environment* deopt_env)
|
|
: CompilerDeoptInfo(deopt_id, reason, flags, deopt_env), entry_label_() {
|
|
ASSERT(reason != ICData::kDeoptAtCall);
|
|
}
|
|
|
|
compiler::Label* entry_label() { return &entry_label_; }
|
|
|
|
// Implementation is in architecture specific file.
|
|
virtual void GenerateCode(FlowGraphCompiler* compiler, intptr_t stub_ix);
|
|
|
|
const char* Name() const {
|
|
const char* kFormat = "Deopt stub for id %d, reason: %s";
|
|
const intptr_t len = Utils::SNPrint(nullptr, 0, kFormat, deopt_id(),
|
|
DeoptReasonToCString(reason())) +
|
|
1;
|
|
char* chars = Thread::Current()->zone()->Alloc<char>(len);
|
|
Utils::SNPrint(chars, len, kFormat, deopt_id(),
|
|
DeoptReasonToCString(reason()));
|
|
return chars;
|
|
}
|
|
|
|
private:
|
|
compiler::Label entry_label_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(CompilerDeoptInfoWithStub);
|
|
};
|
|
|
|
class SlowPathCode : public ZoneObject {
|
|
public:
|
|
explicit SlowPathCode(Instruction* instruction)
|
|
: instruction_(instruction), entry_label_(), exit_label_() {}
|
|
virtual ~SlowPathCode() {}
|
|
|
|
Instruction* instruction() const { return instruction_; }
|
|
compiler::Label* entry_label() { return &entry_label_; }
|
|
compiler::Label* exit_label() { return &exit_label_; }
|
|
|
|
void GenerateCode(FlowGraphCompiler* compiler) {
|
|
EmitNativeCode(compiler);
|
|
ASSERT(entry_label_.IsBound());
|
|
}
|
|
|
|
private:
|
|
virtual void EmitNativeCode(FlowGraphCompiler* compiler) = 0;
|
|
|
|
Instruction* instruction_;
|
|
compiler::Label entry_label_;
|
|
compiler::Label exit_label_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(SlowPathCode);
|
|
};
|
|
|
|
template <typename T>
|
|
class TemplateSlowPathCode : public SlowPathCode {
|
|
public:
|
|
explicit TemplateSlowPathCode(T* instruction) : SlowPathCode(instruction) {}
|
|
|
|
T* instruction() const {
|
|
return static_cast<T*>(SlowPathCode::instruction());
|
|
}
|
|
};
|
|
|
|
class BoxAllocationSlowPath : public TemplateSlowPathCode<Instruction> {
|
|
public:
|
|
BoxAllocationSlowPath(Instruction* instruction,
|
|
const Class& cls,
|
|
Register result)
|
|
: TemplateSlowPathCode(instruction), cls_(cls), result_(result) {}
|
|
|
|
virtual void EmitNativeCode(FlowGraphCompiler* compiler);
|
|
|
|
static void Allocate(FlowGraphCompiler* compiler,
|
|
Instruction* instruction,
|
|
const Class& cls,
|
|
Register result,
|
|
Register temp);
|
|
|
|
private:
|
|
const Class& cls_;
|
|
const Register result_;
|
|
};
|
|
|
|
class DoubleToIntegerSlowPath
|
|
: public TemplateSlowPathCode<DoubleToIntegerInstr> {
|
|
public:
|
|
DoubleToIntegerSlowPath(DoubleToIntegerInstr* instruction,
|
|
FpuRegister value_reg)
|
|
: TemplateSlowPathCode(instruction), value_reg_(value_reg) {}
|
|
|
|
virtual void EmitNativeCode(FlowGraphCompiler* compiler);
|
|
|
|
private:
|
|
FpuRegister value_reg_;
|
|
};
|
|
|
|
// Slow path code which calls runtime entry to throw an exception.
|
|
class ThrowErrorSlowPathCode : public TemplateSlowPathCode<Instruction> {
|
|
public:
|
|
ThrowErrorSlowPathCode(Instruction* instruction,
|
|
const RuntimeEntry& runtime_entry)
|
|
: TemplateSlowPathCode(instruction), runtime_entry_(runtime_entry) {}
|
|
|
|
// This name appears in disassembly.
|
|
virtual const char* name() = 0;
|
|
|
|
// Subclasses can override these methods to customize slow path code.
|
|
virtual void EmitCodeAtSlowPathEntry(FlowGraphCompiler* compiler) {}
|
|
virtual void AddMetadataForRuntimeCall(FlowGraphCompiler* compiler) {}
|
|
virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler) {}
|
|
|
|
// Returns number of arguments for runtime call (if shared stub is not used).
|
|
virtual intptr_t GetNumberOfArgumentsForRuntimeCall() { return 0; }
|
|
|
|
virtual void EmitSharedStubCall(FlowGraphCompiler* compiler,
|
|
bool save_fpu_registers) {
|
|
UNREACHABLE();
|
|
}
|
|
|
|
virtual void EmitNativeCode(FlowGraphCompiler* compiler);
|
|
|
|
private:
|
|
const RuntimeEntry& runtime_entry_;
|
|
};
|
|
|
|
class NullErrorSlowPath : public ThrowErrorSlowPathCode {
|
|
public:
|
|
explicit NullErrorSlowPath(CheckNullInstr* instruction)
|
|
: ThrowErrorSlowPathCode(instruction,
|
|
GetRuntimeEntry(instruction->exception_type())) {
|
|
}
|
|
|
|
CheckNullInstr::ExceptionType exception_type() const {
|
|
return instruction()->AsCheckNull()->exception_type();
|
|
}
|
|
|
|
const char* name() override;
|
|
|
|
void EmitSharedStubCall(FlowGraphCompiler* compiler,
|
|
bool save_fpu_registers) override;
|
|
|
|
void AddMetadataForRuntimeCall(FlowGraphCompiler* compiler) override {
|
|
CheckNullInstr::AddMetadataForRuntimeCall(instruction()->AsCheckNull(),
|
|
compiler);
|
|
}
|
|
|
|
static CodePtr GetStub(FlowGraphCompiler* compiler,
|
|
CheckNullInstr::ExceptionType exception_type,
|
|
bool save_fpu_registers);
|
|
|
|
private:
|
|
static const RuntimeEntry& GetRuntimeEntry(
|
|
CheckNullInstr::ExceptionType exception_type);
|
|
};
|
|
|
|
class RangeErrorSlowPath : public ThrowErrorSlowPathCode {
|
|
public:
|
|
explicit RangeErrorSlowPath(GenericCheckBoundInstr* instruction)
|
|
: ThrowErrorSlowPathCode(
|
|
instruction,
|
|
GenericCheckBoundInstr::UseUnboxedRepresentation()
|
|
? kRangeErrorUnboxedInt64RuntimeEntry
|
|
: kRangeErrorRuntimeEntry) {}
|
|
virtual const char* name() { return "check bound"; }
|
|
|
|
virtual intptr_t GetNumberOfArgumentsForRuntimeCall() {
|
|
if (GenericCheckBoundInstr::UseUnboxedRepresentation()) {
|
|
return 0; // Unboxed arguments are passed through Thread.
|
|
}
|
|
return 2; // length and index
|
|
}
|
|
|
|
virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler);
|
|
|
|
virtual void EmitSharedStubCall(FlowGraphCompiler* compiler,
|
|
bool save_fpu_registers);
|
|
};
|
|
|
|
class WriteErrorSlowPath : public ThrowErrorSlowPathCode {
|
|
public:
|
|
explicit WriteErrorSlowPath(CheckWritableInstr* instruction)
|
|
: ThrowErrorSlowPathCode(instruction, kWriteErrorRuntimeEntry) {}
|
|
virtual const char* name() { return "check writable"; }
|
|
|
|
virtual void EmitSharedStubCall(FlowGraphCompiler* compiler,
|
|
bool save_fpu_registers);
|
|
|
|
virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler);
|
|
|
|
virtual intptr_t GetNumberOfArgumentsForRuntimeCall() {
|
|
return 2; // receiver, kind
|
|
}
|
|
};
|
|
|
|
class LateInitializationErrorSlowPath : public ThrowErrorSlowPathCode {
|
|
public:
|
|
explicit LateInitializationErrorSlowPath(Instruction* instruction)
|
|
: ThrowErrorSlowPathCode(instruction,
|
|
kLateFieldNotInitializedErrorRuntimeEntry) {
|
|
ASSERT(instruction->IsLoadField() || instruction->IsLoadStaticField());
|
|
}
|
|
virtual const char* name() { return "late initialization error"; }
|
|
|
|
virtual intptr_t GetNumberOfArgumentsForRuntimeCall() {
|
|
return 1; // field
|
|
}
|
|
|
|
virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler);
|
|
|
|
virtual void EmitSharedStubCall(FlowGraphCompiler* compiler,
|
|
bool save_fpu_registers);
|
|
|
|
private:
|
|
FieldPtr OriginalField() const {
|
|
return instruction()->IsLoadField()
|
|
? instruction()->AsLoadField()->slot().field().Original()
|
|
: instruction()->AsLoadStaticField()->field().Original();
|
|
}
|
|
};
|
|
|
|
class FieldAccessErrorSlowPath : public ThrowErrorSlowPathCode {
|
|
public:
|
|
explicit FieldAccessErrorSlowPath(Instruction* instruction)
|
|
: ThrowErrorSlowPathCode(
|
|
instruction,
|
|
kStaticFieldAccessedWithoutIsolateErrorRuntimeEntry) {
|
|
ASSERT(instruction->IsStoreStaticField());
|
|
}
|
|
virtual const char* name() { return "field access error"; }
|
|
|
|
virtual intptr_t GetNumberOfArgumentsForRuntimeCall() {
|
|
return 1; // field
|
|
}
|
|
|
|
virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler);
|
|
|
|
private:
|
|
FieldPtr OriginalField() const {
|
|
return instruction()->AsStoreStaticField()->field().Original();
|
|
}
|
|
};
|
|
|
|
class CheckedStoreIntoSharedSlowPath
|
|
: public TemplateSlowPathCode<StoreStaticFieldInstr> {
|
|
public:
|
|
CheckedStoreIntoSharedSlowPath(StoreStaticFieldInstr* instruction,
|
|
Register value)
|
|
: TemplateSlowPathCode(instruction), value_(value) {}
|
|
|
|
virtual void EmitNativeCode(FlowGraphCompiler* compiler);
|
|
|
|
private:
|
|
Register value_;
|
|
|
|
FieldPtr OriginalField() const {
|
|
return instruction()->AsStoreStaticField()->field().Original();
|
|
}
|
|
|
|
Register value() const { return value_; }
|
|
};
|
|
|
|
class EnsureDeeplyImmutableSlowPath : public TemplateSlowPathCode<Instruction> {
|
|
public:
|
|
EnsureDeeplyImmutableSlowPath(CheckFieldImmutabilityInstr* instruction,
|
|
Register value)
|
|
: TemplateSlowPathCode(instruction), value_(value) {}
|
|
|
|
virtual void EmitNativeCode(FlowGraphCompiler* compiler);
|
|
|
|
private:
|
|
Register value_;
|
|
|
|
Register value() const { return value_; }
|
|
};
|
|
|
|
enum class TypeTestOutcome { kConclusive, kNotConclusive };
|
|
|
|
class FlowGraphCompiler : public ValueObject {
|
|
private:
|
|
class BlockInfo : public ZoneObject {
|
|
public:
|
|
BlockInfo()
|
|
: block_label_(),
|
|
jump_label_(&block_label_),
|
|
next_nonempty_label_(nullptr),
|
|
is_marked_(false) {}
|
|
|
|
// The label to jump to when control is transferred to this block. For
|
|
// nonempty blocks it is the label of the block itself. For empty
|
|
// blocks it is the label of the first nonempty successor block.
|
|
compiler::Label* jump_label() const { return jump_label_; }
|
|
void set_jump_label(compiler::Label* label) { jump_label_ = label; }
|
|
|
|
// The label of the first nonempty block after this one in the block
|
|
// order, or nullptr if there is no nonempty block following this one.
|
|
compiler::Label* next_nonempty_label() const {
|
|
return next_nonempty_label_;
|
|
}
|
|
void set_next_nonempty_label(compiler::Label* label) {
|
|
next_nonempty_label_ = label;
|
|
}
|
|
|
|
bool WasCompacted() const { return jump_label_ != &block_label_; }
|
|
|
|
// Block compaction is recursive. Block info for already-compacted
|
|
// blocks is marked so as to avoid cycles in the graph.
|
|
bool is_marked() const { return is_marked_; }
|
|
void mark() { is_marked_ = true; }
|
|
|
|
private:
|
|
compiler::Label block_label_;
|
|
|
|
compiler::Label* jump_label_;
|
|
compiler::Label* next_nonempty_label_;
|
|
|
|
bool is_marked_;
|
|
};
|
|
|
|
public:
|
|
FlowGraphCompiler(compiler::Assembler* assembler,
|
|
FlowGraph* flow_graph,
|
|
const ParsedFunction& parsed_function,
|
|
bool is_optimizing,
|
|
ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data,
|
|
CodeStatistics* stats = nullptr);
|
|
|
|
void ArchSpecificInitialization();
|
|
|
|
~FlowGraphCompiler();
|
|
|
|
static bool SupportsUnboxedSimd128();
|
|
static bool CanConvertInt64ToDouble();
|
|
|
|
// Accessors.
|
|
compiler::Assembler* assembler() const { return assembler_; }
|
|
const ParsedFunction& parsed_function() const { return parsed_function_; }
|
|
const Function& function() const { return parsed_function_.function(); }
|
|
const GrowableArray<BlockEntryInstr*>& block_order() const {
|
|
return block_order_;
|
|
}
|
|
const GrowableArray<const compiler::TableSelector*>&
|
|
dispatch_table_call_targets() const {
|
|
return dispatch_table_call_targets_;
|
|
}
|
|
|
|
// If 'ForcedOptimization()' returns 'true', we are compiling in optimized
|
|
// mode for a function which cannot deoptimize. Certain optimizations, e.g.
|
|
// speculative optimizations and call patching are disabled.
|
|
bool ForcedOptimization() const { return function().ForceOptimize(); }
|
|
|
|
const FlowGraph& flow_graph() const {
|
|
return intrinsic_mode() ? *intrinsic_flow_graph_ : flow_graph_;
|
|
}
|
|
|
|
BlockEntryInstr* current_block() const { return current_block_; }
|
|
void set_current_block(BlockEntryInstr* value) { current_block_ = value; }
|
|
|
|
Instruction* current_instruction() const { return current_instruction_; }
|
|
|
|
bool CanOptimize() const;
|
|
bool CanOptimizeFunction() const;
|
|
bool CanOSRFunction() const;
|
|
bool is_optimizing() const { return is_optimizing_; }
|
|
|
|
void InsertBSSRelocation(BSS::Relocation reloc);
|
|
void LoadBSSEntry(BSS::Relocation relocation, Register dst, Register tmp);
|
|
|
|
// The function was fully intrinsified, so the body is unreachable.
|
|
//
|
|
// We still need to compile the body in unoptimized mode because the
|
|
// 'ICData's are added to the function's 'ic_data_array_' when instance
|
|
// calls are compiled.
|
|
bool skip_body_compilation() const {
|
|
return fully_intrinsified_ && is_optimizing();
|
|
}
|
|
|
|
void EnterIntrinsicMode();
|
|
void ExitIntrinsicMode();
|
|
bool intrinsic_mode() const { return intrinsic_mode_; }
|
|
|
|
void set_intrinsic_flow_graph(const FlowGraph& flow_graph) {
|
|
intrinsic_flow_graph_ = &flow_graph;
|
|
}
|
|
|
|
void set_intrinsic_slow_path_label(compiler::Label* label) {
|
|
ASSERT(intrinsic_slow_path_label_ == nullptr || label == nullptr);
|
|
intrinsic_slow_path_label_ = label;
|
|
}
|
|
compiler::Label* intrinsic_slow_path_label() const {
|
|
ASSERT(intrinsic_slow_path_label_ != nullptr);
|
|
return intrinsic_slow_path_label_;
|
|
}
|
|
|
|
bool ForceSlowPathForStackOverflow() const;
|
|
|
|
const GrowableArray<BlockInfo*>& block_info() const { return block_info_; }
|
|
|
|
void StatsBegin(Instruction* instr) {
|
|
if (stats_ != nullptr) stats_->Begin(instr);
|
|
}
|
|
|
|
void StatsEnd(Instruction* instr) {
|
|
if (stats_ != nullptr) stats_->End(instr);
|
|
}
|
|
|
|
void SpecialStatsBegin(intptr_t tag) {
|
|
if (stats_ != nullptr) stats_->SpecialBegin(tag);
|
|
}
|
|
|
|
void SpecialStatsEnd(intptr_t tag) {
|
|
if (stats_ != nullptr) stats_->SpecialEnd(tag);
|
|
}
|
|
|
|
GrowableArray<const Field*>& used_static_fields() {
|
|
return used_static_fields_;
|
|
}
|
|
|
|
// Constructor is lightweight, major initialization work should occur here.
|
|
// This makes it easier to measure time spent in the compiler.
|
|
void InitCompiler();
|
|
|
|
void CompileGraph();
|
|
|
|
void EmitPrologue();
|
|
|
|
void VisitBlocks();
|
|
|
|
void EmitFunctionEntrySourcePositionDescriptorIfNeeded();
|
|
|
|
// Bail out of the flow graph compiler. Does not return to the caller.
|
|
void Bailout(const char* reason);
|
|
|
|
// Returns 'true' if regular code generation should be skipped.
|
|
bool TryIntrinsify();
|
|
|
|
// Emits code for a generic move from a location 'src' to a location 'dst'.
|
|
//
|
|
// Note that Location does not include a size (that can only be deduced from
|
|
// a Representation), so these moves might overapproximate the size needed
|
|
// to move. The maximal overapproximation is moving 8 bytes instead of 4 on
|
|
// 64 bit architectures. This overapproximation is not a problem, because
|
|
// the Dart calling convention only uses word-sized stack slots.
|
|
//
|
|
// TODO(dartbug.com/40400): Express this in terms of EmitMove(NativeLocation
|
|
// NativeLocation) to remove code duplication.
|
|
void EmitMove(Location dst, Location src, TemporaryRegisterAllocator* temp);
|
|
|
|
// Emits code for a move from a location `src` to a location `dst`.
|
|
//
|
|
// Takes into account the payload and container representations of `dst` and
|
|
// `src` to do the smallest move possible, and sign (or zero) extend or
|
|
// truncate if needed.
|
|
//
|
|
// Makes use of TMP, FpuTMP, and `temp`.
|
|
void EmitNativeMove(const compiler::ffi::NativeLocation& dst,
|
|
const compiler::ffi::NativeLocation& src,
|
|
TemporaryRegisterAllocator* temp);
|
|
|
|
// Helper method to move from a Location to a NativeLocation.
|
|
void EmitMoveToNative(const compiler::ffi::NativeLocation& dst,
|
|
Location src_loc,
|
|
Representation src_type,
|
|
TemporaryRegisterAllocator* temp);
|
|
|
|
// Helper method to move from a NativeLocation to a Location.
|
|
void EmitMoveFromNative(Location dst_loc,
|
|
Representation dst_type,
|
|
const compiler::ffi::NativeLocation& src,
|
|
TemporaryRegisterAllocator* temp);
|
|
|
|
// Helper method to move a Dart const to a native location.
|
|
void EmitMoveConst(const compiler::ffi::NativeLocation& dst,
|
|
Location src,
|
|
Representation src_type,
|
|
TemporaryRegisterAllocator* temp);
|
|
|
|
bool CheckAssertAssignableTypeTestingABILocations(
|
|
const LocationSummary& locs);
|
|
|
|
void GenerateAssertAssignable(CompileType* receiver_type,
|
|
const InstructionSource& source,
|
|
intptr_t deopt_id,
|
|
Environment* env,
|
|
const String& dst_name,
|
|
LocationSummary* locs);
|
|
|
|
#if !defined(TARGET_ARCH_IA32)
|
|
void GenerateCallerChecksForAssertAssignable(CompileType* receiver_type,
|
|
const AbstractType& dst_type,
|
|
compiler::Label* done);
|
|
|
|
void GenerateTTSCall(const InstructionSource& source,
|
|
intptr_t deopt_id,
|
|
Environment* env,
|
|
Register reg_with_type,
|
|
const AbstractType& dst_type,
|
|
const String& dst_name,
|
|
LocationSummary* locs);
|
|
|
|
static void GenerateIndirectTTSCall(compiler::Assembler* assembler,
|
|
Register reg_with_type,
|
|
intptr_t sub_type_cache_index);
|
|
#endif
|
|
|
|
void GenerateStubCall(const InstructionSource& source,
|
|
const Code& stub,
|
|
UntaggedPcDescriptors::Kind kind,
|
|
LocationSummary* locs,
|
|
intptr_t deopt_id,
|
|
Environment* env);
|
|
|
|
void GenerateNonLazyDeoptableStubCall(
|
|
const InstructionSource& source,
|
|
const Code& stub,
|
|
UntaggedPcDescriptors::Kind kind,
|
|
LocationSummary* locs,
|
|
ObjectPool::SnapshotBehavior snapshot_behavior =
|
|
compiler::ObjectPoolBuilderEntry::kSnapshotable);
|
|
|
|
void GeneratePatchableCall(
|
|
const InstructionSource& source,
|
|
const Code& stub,
|
|
UntaggedPcDescriptors::Kind kind,
|
|
LocationSummary* locs,
|
|
ObjectPool::SnapshotBehavior snapshot_behavior =
|
|
compiler::ObjectPoolBuilderEntry::kSnapshotable);
|
|
|
|
void GenerateDartCall(intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
const Code& stub,
|
|
UntaggedPcDescriptors::Kind kind,
|
|
LocationSummary* locs,
|
|
Code::EntryKind entry_kind = Code::EntryKind::kNormal);
|
|
|
|
void GenerateStaticDartCall(
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
UntaggedPcDescriptors::Kind kind,
|
|
LocationSummary* locs,
|
|
const Function& target,
|
|
Code::EntryKind entry_kind = Code::EntryKind::kNormal);
|
|
|
|
void GenerateInstanceOf(const InstructionSource& source,
|
|
intptr_t deopt_id,
|
|
Environment* env,
|
|
const AbstractType& type,
|
|
LocationSummary* locs);
|
|
|
|
void GenerateInstanceCall(intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
LocationSummary* locs,
|
|
const ICData& ic_data,
|
|
Code::EntryKind entry_kind,
|
|
bool receiver_can_be_smi);
|
|
|
|
void GenerateStaticCall(
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
const Function& function,
|
|
ArgumentsInfo args_info,
|
|
LocationSummary* locs,
|
|
const ICData& ic_data_in,
|
|
ICData::RebindRule rebind_rule,
|
|
Code::EntryKind entry_kind = Code::EntryKind::kNormal);
|
|
|
|
void GenerateNumberTypeCheck(Register kClassIdReg,
|
|
const AbstractType& type,
|
|
compiler::Label* is_instance_lbl,
|
|
compiler::Label* is_not_instance_lbl);
|
|
void GenerateStringTypeCheck(Register kClassIdReg,
|
|
compiler::Label* is_instance_lbl,
|
|
compiler::Label* is_not_instance_lbl);
|
|
void GenerateListTypeCheck(Register kClassIdReg,
|
|
compiler::Label* is_instance_lbl);
|
|
|
|
// We test up to 4 different cid ranges, if we would need to test more in
|
|
// order to get a definite answer we fall back to the old mechanism (namely
|
|
// of going into the subtyping cache)
|
|
static constexpr intptr_t kMaxNumberOfCidRangesToTest = 4;
|
|
|
|
// If [fall_through_if_inside] is `true`, then [outside_range_lbl] must be
|
|
// supplied, since it will be jumped to in the last case if the cid is outside
|
|
// the range.
|
|
//
|
|
// Returns whether [class_id_reg] is clobbered by the check.
|
|
static bool GenerateCidRangesCheck(
|
|
compiler::Assembler* assembler,
|
|
Register class_id_reg,
|
|
const CidRangeVector& cid_ranges,
|
|
compiler::Label* inside_range_lbl,
|
|
compiler::Label* outside_range_lbl = nullptr,
|
|
bool fall_through_if_inside = false);
|
|
|
|
void EmitOptimizedInstanceCall(
|
|
const Code& stub,
|
|
const ICData& ic_data,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
LocationSummary* locs,
|
|
Code::EntryKind entry_kind = Code::EntryKind::kNormal);
|
|
|
|
void EmitInstanceCallJIT(const Code& stub,
|
|
const ICData& ic_data,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
LocationSummary* locs,
|
|
Code::EntryKind entry_kind);
|
|
|
|
void EmitPolymorphicInstanceCall(const PolymorphicInstanceCallInstr* call,
|
|
const CallTargets& targets,
|
|
ArgumentsInfo args_info,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
LocationSummary* locs,
|
|
bool complete,
|
|
intptr_t total_call_count,
|
|
bool receiver_can_be_smi = true);
|
|
|
|
void EmitMegamorphicInstanceCall(const ICData& icdata,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
LocationSummary* locs) {
|
|
const String& name = String::Handle(icdata.target_name());
|
|
const Array& arguments_descriptor =
|
|
Array::Handle(icdata.arguments_descriptor());
|
|
EmitMegamorphicInstanceCall(name, arguments_descriptor, deopt_id, source,
|
|
locs);
|
|
}
|
|
|
|
void EmitMegamorphicInstanceCall(const String& function_name,
|
|
const Array& arguments_descriptor,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
LocationSummary* locs);
|
|
|
|
void EmitInstanceCallAOT(
|
|
const ICData& ic_data,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
LocationSummary* locs,
|
|
Code::EntryKind entry_kind = Code::EntryKind::kNormal,
|
|
bool receiver_can_be_smi = true);
|
|
|
|
void EmitTestAndCall(const CallTargets& targets,
|
|
const String& function_name,
|
|
ArgumentsInfo args_info,
|
|
compiler::Label* failed,
|
|
compiler::Label* match_found,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source_index,
|
|
LocationSummary* locs,
|
|
bool complete,
|
|
intptr_t total_ic_calls,
|
|
Code::EntryKind entry_kind = Code::EntryKind::kNormal);
|
|
|
|
void EmitDispatchTableCall(int32_t selector_offset,
|
|
const Array& arguments_descriptor);
|
|
|
|
Condition EmitEqualityRegConstCompare(Register reg,
|
|
const Object& obj,
|
|
bool needs_number_check,
|
|
const InstructionSource& source,
|
|
intptr_t deopt_id);
|
|
Condition EmitEqualityRegRegCompare(Register left,
|
|
Register right,
|
|
bool needs_number_check,
|
|
const InstructionSource& source,
|
|
intptr_t deopt_id);
|
|
Condition EmitBoolTest(Register value, BranchLabels labels, bool invert);
|
|
|
|
bool NeedsEdgeCounter(BlockEntryInstr* block);
|
|
|
|
void EmitEdgeCounter(intptr_t edge_id);
|
|
|
|
void RecordCatchEntryMoves(Environment* env);
|
|
|
|
void EmitCallToStub(const Code& stub,
|
|
ObjectPool::SnapshotBehavior snapshot_behavior =
|
|
compiler::ObjectPoolBuilderEntry::kSnapshotable);
|
|
void EmitJumpToStub(const Code& stub);
|
|
void EmitTailCallToStub(const Code& stub);
|
|
|
|
void EmitDropArguments(intptr_t count);
|
|
|
|
// Emits the following metadata for the current PC:
|
|
//
|
|
// * Attaches current try index
|
|
// * Attaches stackmaps
|
|
// * Attaches catch entry moves (in AOT)
|
|
// * Deoptimization information (in JIT)
|
|
//
|
|
// If [env] is not `nullptr` it will be used instead of the
|
|
// `pending_deoptimization_env`.
|
|
void EmitCallsiteMetadata(const InstructionSource& source,
|
|
intptr_t deopt_id,
|
|
UntaggedPcDescriptors::Kind kind,
|
|
LocationSummary* locs,
|
|
Environment* env);
|
|
|
|
void EmitYieldPositionMetadata(const InstructionSource& source,
|
|
intptr_t yield_index);
|
|
|
|
void EmitComment(Instruction* instr);
|
|
|
|
// Returns stack size (number of variables on stack for unoptimized
|
|
// code, or number of spill slots for optimized code).
|
|
intptr_t StackSize() const;
|
|
|
|
// Returns the number of extra stack slots used during an Osr entry
|
|
// (values for all [ParameterInstr]s, representing local variables
|
|
// and expression stack values, are already on the stack).
|
|
intptr_t ExtraStackSlotsOnOsrEntry() const;
|
|
|
|
#if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)
|
|
// Changes the base register of this Location if this allows us to utilize
|
|
// a better addressing mode. For RISC-V, this is the wider range of compressed
|
|
// instructions available for SP-relative load compared to FP-relative loads.
|
|
// Assumes `StackSize` accounts for everything at the point of use.
|
|
Location RebaseIfImprovesAddressing(Location loc) const;
|
|
#endif // defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)
|
|
|
|
// Returns assembler label associated with the given block entry.
|
|
compiler::Label* GetJumpLabel(BlockEntryInstr* block_entry) const;
|
|
bool WasCompacted(BlockEntryInstr* block_entry) const;
|
|
|
|
// Returns the label of the fall-through of the current block.
|
|
compiler::Label* NextNonEmptyLabel() const;
|
|
|
|
// Returns true if there is a next block after the current one in
|
|
// the block order and if it is the given block.
|
|
bool CanFallThroughTo(BlockEntryInstr* block_entry) const;
|
|
|
|
// Return true-, false- and fall-through label for a branch instruction.
|
|
BranchLabels CreateBranchLabels(BranchInstr* branch) const;
|
|
|
|
void AddExceptionHandler(CatchBlockEntryInstr* entry);
|
|
void SetNeedsStackTrace(intptr_t try_index);
|
|
void AddCurrentDescriptor(UntaggedPcDescriptors::Kind kind,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source);
|
|
void AddDescriptor(
|
|
UntaggedPcDescriptors::Kind kind,
|
|
intptr_t pc_offset,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
intptr_t try_index,
|
|
intptr_t yield_index = UntaggedPcDescriptors::kInvalidYieldIndex);
|
|
|
|
// Add NullCheck information for the current PC.
|
|
void AddNullCheck(const InstructionSource& source, const String& name);
|
|
|
|
void RecordSafepoint(LocationSummary* locs,
|
|
intptr_t slow_path_argument_count = 0);
|
|
|
|
compiler::Label* AddDeoptStub(intptr_t deopt_id,
|
|
ICData::DeoptReasonId reason,
|
|
uint32_t flags = 0);
|
|
|
|
CompilerDeoptInfo* AddDeoptIndexAtCall(intptr_t deopt_id, Environment* env);
|
|
CompilerDeoptInfo* AddSlowPathDeoptInfo(intptr_t deopt_id, Environment* env);
|
|
|
|
void AddSlowPathCode(SlowPathCode* slow_path);
|
|
|
|
void FinalizeExceptionHandlers(const Code& code);
|
|
void FinalizePcDescriptors(const Code& code);
|
|
ArrayPtr CreateDeoptInfo(compiler::Assembler* assembler);
|
|
void FinalizeStackMaps(const Code& code);
|
|
void FinalizeVarDescriptors(const Code& code);
|
|
void FinalizeCatchEntryMovesMap(const Code& code);
|
|
void FinalizeStaticCallTargetsTable(const Code& code);
|
|
void FinalizeCodeSourceMap(const Code& code);
|
|
|
|
const Class& double_class() const { return double_class_; }
|
|
const Class& mint_class() const { return mint_class_; }
|
|
const Class& float32x4_class() const { return float32x4_class_; }
|
|
const Class& float64x2_class() const { return float64x2_class_; }
|
|
const Class& int32x4_class() const { return int32x4_class_; }
|
|
|
|
const Class& BoxClassFor(Representation rep);
|
|
|
|
void SaveLiveRegisters(LocationSummary* locs);
|
|
void RestoreLiveRegisters(LocationSummary* locs);
|
|
#if defined(DEBUG)
|
|
void ClobberDeadTempRegisters(LocationSummary* locs);
|
|
#endif
|
|
|
|
// Returns a new environment based on [env] which accounts for the new
|
|
// locations of values in the slow path call.
|
|
Environment* SlowPathEnvironmentFor(Instruction* inst,
|
|
intptr_t num_slow_path_args) {
|
|
if (inst->env() == nullptr && is_optimizing()) {
|
|
if (pending_deoptimization_env_ == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return SlowPathEnvironmentFor(pending_deoptimization_env_, inst->locs(),
|
|
num_slow_path_args);
|
|
}
|
|
return SlowPathEnvironmentFor(inst->env(), inst->locs(),
|
|
num_slow_path_args);
|
|
}
|
|
|
|
Environment* SlowPathEnvironmentFor(Environment* env,
|
|
LocationSummary* locs,
|
|
intptr_t num_slow_path_args);
|
|
|
|
intptr_t CurrentTryIndex() const {
|
|
if (current_block_ == nullptr) {
|
|
return kInvalidTryIndex;
|
|
}
|
|
return current_block_->try_index();
|
|
}
|
|
|
|
bool may_reoptimize() const { return may_reoptimize_; }
|
|
|
|
// Use in unoptimized compilation to preserve/reuse ICData.
|
|
//
|
|
// If [binary_smi_target] is non-null and we have to create the ICData, the
|
|
// ICData will get an (kSmiCid, kSmiCid, binary_smi_target) entry.
|
|
const ICData* GetOrAddInstanceCallICData(intptr_t deopt_id,
|
|
const String& target_name,
|
|
const Array& arguments_descriptor,
|
|
intptr_t num_args_tested,
|
|
const AbstractType& receiver_type,
|
|
const Function& binary_smi_target);
|
|
|
|
const ICData* GetOrAddStaticCallICData(intptr_t deopt_id,
|
|
const Function& target,
|
|
const Array& arguments_descriptor,
|
|
intptr_t num_args_tested,
|
|
ICData::RebindRule rebind_rule);
|
|
|
|
static const CallTargets* ResolveCallTargetsForReceiverCid(
|
|
intptr_t cid,
|
|
const String& selector,
|
|
const Array& args_desc_array);
|
|
|
|
const ZoneGrowableArray<const ICData*>& deopt_id_to_ic_data() const {
|
|
return *deopt_id_to_ic_data_;
|
|
}
|
|
|
|
Thread* thread() const { return thread_; }
|
|
IsolateGroup* isolate_group() const { return thread_->isolate_group(); }
|
|
Zone* zone() const { return zone_; }
|
|
|
|
void AddStubCallTarget(const Code& code);
|
|
void AddDispatchTableCallTarget(const compiler::TableSelector* selector);
|
|
|
|
ArrayPtr edge_counters_array() const { return edge_counters_array_.ptr(); }
|
|
|
|
ArrayPtr InliningIdToFunction() const;
|
|
|
|
void BeginCodeSourceRange(const InstructionSource& source);
|
|
void EndCodeSourceRange(const InstructionSource& source);
|
|
|
|
static bool LookupMethodFor(int class_id,
|
|
const String& name,
|
|
const ArgumentsDescriptor& args_desc,
|
|
Function* fn_return,
|
|
bool* class_is_abstract_return = nullptr);
|
|
|
|
// Returns new class-id bias.
|
|
//
|
|
// TODO(kustermann): We should move this code out of the [FlowGraphCompiler]!
|
|
static int EmitTestAndCallCheckCid(compiler::Assembler* assembler,
|
|
compiler::Label* label,
|
|
Register class_id_reg,
|
|
const CidRangeValue& range,
|
|
int bias,
|
|
bool jump_on_miss = true);
|
|
|
|
bool IsEmptyBlock(BlockEntryInstr* block) const;
|
|
|
|
void EmitOptimizedStaticCall(
|
|
const Function& function,
|
|
const Array& arguments_descriptor,
|
|
intptr_t size_with_type_args,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
LocationSummary* locs,
|
|
Code::EntryKind entry_kind = Code::EntryKind::kNormal);
|
|
|
|
private:
|
|
friend class BoxInt64Instr; // For AddPcRelativeCallStubTarget().
|
|
friend class CheckNullInstr; // For AddPcRelativeCallStubTarget().
|
|
friend class NullErrorSlowPath; // For AddPcRelativeCallStubTarget().
|
|
friend class CheckStackOverflowInstr; // For AddPcRelativeCallStubTarget().
|
|
friend class StoreIndexedInstr; // For AddPcRelativeCallStubTarget().
|
|
friend class StoreFieldInstr; // For AddPcRelativeCallStubTarget().
|
|
friend class CheckStackOverflowSlowPath; // For pending_deoptimization_env_.
|
|
friend class GraphIntrinsicCodeGenScope; // For optimizing_.
|
|
|
|
// Architecture specific implementation of simple native moves.
|
|
void EmitNativeMoveArchitecture(const compiler::ffi::NativeLocation& dst,
|
|
const compiler::ffi::NativeLocation& src);
|
|
void EmitNativeLoad(Register dst,
|
|
Register base,
|
|
intptr_t offset,
|
|
compiler::ffi::PrimitiveType type);
|
|
|
|
void EmitFrameEntry();
|
|
|
|
bool TryIntrinsifyHelper();
|
|
void AddPcRelativeCallTarget(const Function& function,
|
|
Code::EntryKind entry_kind);
|
|
void AddPcRelativeCallStubTarget(const Code& stub_code);
|
|
void AddPcRelativeTailCallStubTarget(const Code& stub_code);
|
|
void AddPcRelativeTTSCallTypeTarget(const AbstractType& type);
|
|
void AddStaticCallTarget(const Function& function,
|
|
Code::EntryKind entry_kind);
|
|
|
|
void GenerateDeferredCode();
|
|
|
|
void EmitInstructionPrologue(Instruction* instr);
|
|
void EmitInstructionEpilogue(Instruction* instr);
|
|
|
|
// Emit code to load a Value into register 'dst'.
|
|
void LoadValue(Register dst, Value* value);
|
|
|
|
void EmitUnoptimizedStaticCall(
|
|
intptr_t size_with_type_args,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source,
|
|
LocationSummary* locs,
|
|
const ICData& ic_data,
|
|
Code::EntryKind entry_kind = Code::EntryKind::kNormal);
|
|
|
|
// Helper for TestAndCall that calculates a good bias that
|
|
// allows more compact instructions to be emitted.
|
|
intptr_t ComputeGoodBiasForCidComparison(const CallTargets& sorted,
|
|
intptr_t max_immediate);
|
|
|
|
// More helpers for EmitTestAndCall.
|
|
|
|
static Register EmitTestCidRegister();
|
|
|
|
void EmitTestAndCallLoadReceiver(intptr_t count_without_type_args,
|
|
const Array& arguments_descriptor);
|
|
|
|
void EmitTestAndCallSmiBranch(compiler::Label* label, bool jump_if_smi);
|
|
|
|
void EmitTestAndCallLoadCid(Register class_id_reg);
|
|
|
|
// Type checking helper methods.
|
|
void CheckClassIds(Register class_id_reg,
|
|
const GrowableArray<intptr_t>& class_ids,
|
|
compiler::Label* is_instance_lbl,
|
|
compiler::Label* is_not_instance_lbl);
|
|
|
|
SubtypeTestCachePtr GenerateInlineInstanceof(
|
|
const InstructionSource& source,
|
|
const AbstractType& type,
|
|
compiler::Label* is_instance_lbl,
|
|
compiler::Label* is_not_instance_lbl);
|
|
|
|
SubtypeTestCachePtr GenerateInstantiatedTypeWithArgumentsTest(
|
|
const InstructionSource& source,
|
|
const AbstractType& dst_type,
|
|
compiler::Label* is_instance_lbl,
|
|
compiler::Label* is_not_instance_lbl);
|
|
|
|
TypeTestOutcome GenerateInstantiatedTypeNoArgumentsTest(
|
|
const InstructionSource& source,
|
|
const AbstractType& dst_type,
|
|
compiler::Label* is_instance_lbl,
|
|
compiler::Label* is_not_instance_lbl);
|
|
|
|
SubtypeTestCachePtr GenerateUninstantiatedTypeTest(
|
|
const InstructionSource& source,
|
|
const AbstractType& dst_type,
|
|
compiler::Label* is_instance_lbl,
|
|
compiler::Label* is_not_instance_label);
|
|
|
|
SubtypeTestCachePtr GenerateFunctionTypeTest(
|
|
const InstructionSource& source,
|
|
const AbstractType& dst_type,
|
|
compiler::Label* is_instance_lbl,
|
|
compiler::Label* is_not_instance_label);
|
|
|
|
SubtypeTestCachePtr GenerateSubtype1TestCacheLookup(
|
|
const InstructionSource& source,
|
|
const Class& type_class,
|
|
compiler::Label* is_instance_lbl,
|
|
compiler::Label* is_not_instance_lbl);
|
|
|
|
enum class TypeTestStubKind {
|
|
// Just check the instance cid (no closures).
|
|
kTestTypeOneArg = 1,
|
|
// Also check the instance type arguments.
|
|
kTestTypeTwoArgs = 2,
|
|
// Also check the instantiator type arguments for the destination type.
|
|
kTestTypeThreeArgs = 3,
|
|
// Also check the function type arguments for the destination type.
|
|
kTestTypeFourArgs = 4,
|
|
// Also check the parent function and delayed type arguments for a closure.
|
|
kTestTypeSixArgs = 6,
|
|
// Also check the destination type, as it is not known at compile time.
|
|
kTestTypeSevenArgs = 7,
|
|
};
|
|
|
|
static_assert(static_cast<intptr_t>(TypeTestStubKind::kTestTypeSevenArgs) ==
|
|
SubtypeTestCache::kMaxInputs,
|
|
"Need to adjust kTestTypeMaxArgs");
|
|
static constexpr TypeTestStubKind kTestTypeMaxArgs =
|
|
TypeTestStubKind::kTestTypeSevenArgs;
|
|
|
|
// Returns the number of used inputs for a given type test stub kind.
|
|
intptr_t UsedInputsForTTSKind(TypeTestStubKind kind) {
|
|
return static_cast<intptr_t>(kind);
|
|
}
|
|
|
|
// Returns type test stub kind for a type test against type parameter type.
|
|
TypeTestStubKind GetTypeTestStubKindForTypeParameter(
|
|
const TypeParameter& type_param);
|
|
|
|
// Takes input from TypeTestABI registers (or stack on IA32), see
|
|
// StubCodeCompiler::GenerateSubtypeNTestCacheStub for caller-save registers.
|
|
SubtypeTestCachePtr GenerateCallSubtypeTestStub(
|
|
TypeTestStubKind test_kind,
|
|
compiler::Label* is_instance_lbl,
|
|
compiler::Label* is_not_instance_lbl);
|
|
|
|
void GenerateBoolToJump(Register bool_reg,
|
|
compiler::Label* is_true,
|
|
compiler::Label* is_false);
|
|
|
|
// Perform a greedy local register allocation. Consider all registers free.
|
|
void AllocateRegistersLocally(Instruction* instr);
|
|
|
|
// Map a block number in a forward iteration into the block number in the
|
|
// corresponding reverse iteration. Used to obtain an index into
|
|
// block_order for reverse iterations.
|
|
intptr_t reverse_index(intptr_t index) const {
|
|
return block_order_.length() - index - 1;
|
|
}
|
|
|
|
void set_current_instruction(Instruction* current_instruction) {
|
|
current_instruction_ = current_instruction;
|
|
}
|
|
|
|
void CompactBlock(BlockEntryInstr* block);
|
|
void CompactBlocks();
|
|
|
|
bool IsListClass(const Class& cls) const {
|
|
return cls.ptr() == list_class_.ptr();
|
|
}
|
|
|
|
void EmitSourceLine(Instruction* instr);
|
|
|
|
intptr_t GetOptimizationThreshold() const;
|
|
|
|
#if defined(DEBUG)
|
|
void FrameStateUpdateWith(Instruction* instr);
|
|
void FrameStatePush(Definition* defn);
|
|
void FrameStatePop(intptr_t count);
|
|
bool FrameStateIsSafeToCall();
|
|
void FrameStateClear();
|
|
#endif
|
|
|
|
// Returns true if instruction lookahead (window size one)
|
|
// is amenable to a peephole optimization.
|
|
bool IsPeephole(Instruction* instr) const;
|
|
|
|
#if defined(DEBUG)
|
|
bool CanCallDart() const {
|
|
return current_instruction_ == nullptr ||
|
|
current_instruction_->CanCallDart();
|
|
}
|
|
#else
|
|
bool CanCallDart() const { return true; }
|
|
#endif
|
|
|
|
bool CanPcRelativeCall(const Function& target) const;
|
|
bool CanPcRelativeCall(const Code& target) const;
|
|
bool CanPcRelativeCall(const AbstractType& target) const;
|
|
|
|
// This struct contains either function or code, the other one being nullptr.
|
|
class StaticCallsStruct : public ZoneObject {
|
|
public:
|
|
Code::CallKind call_kind;
|
|
Code::CallEntryPoint entry_point;
|
|
const intptr_t offset;
|
|
const Function* function; // Can be nullptr.
|
|
const Code* code; // Can be nullptr.
|
|
const AbstractType* dst_type; // Can be nullptr.
|
|
StaticCallsStruct(Code::CallKind call_kind,
|
|
Code::CallEntryPoint entry_point,
|
|
intptr_t offset_arg,
|
|
const Function* function_arg,
|
|
const Code* code_arg,
|
|
const AbstractType* dst_type)
|
|
: call_kind(call_kind),
|
|
entry_point(entry_point),
|
|
offset(offset_arg),
|
|
function(function_arg),
|
|
code(code_arg),
|
|
dst_type(dst_type) {
|
|
DEBUG_ASSERT(function == nullptr ||
|
|
function->IsNotTemporaryScopedHandle());
|
|
DEBUG_ASSERT(code == nullptr || code->IsNotTemporaryScopedHandle());
|
|
DEBUG_ASSERT(dst_type == nullptr ||
|
|
dst_type->IsNotTemporaryScopedHandle());
|
|
ASSERT(code == nullptr || dst_type == nullptr);
|
|
}
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(StaticCallsStruct);
|
|
};
|
|
|
|
Thread* thread_;
|
|
Zone* zone_;
|
|
compiler::Assembler* assembler_;
|
|
const ParsedFunction& parsed_function_;
|
|
const FlowGraph& flow_graph_;
|
|
const FlowGraph* intrinsic_flow_graph_ = nullptr;
|
|
const GrowableArray<BlockEntryInstr*>& block_order_;
|
|
|
|
#if defined(DEBUG)
|
|
GrowableArray<Representation> frame_state_;
|
|
#endif
|
|
|
|
// Compiler specific per-block state. Indexed by postorder block number
|
|
// for convenience. This is not the block's index in the block order,
|
|
// which is reverse postorder.
|
|
BlockEntryInstr* current_block_;
|
|
ExceptionHandlerList* exception_handlers_list_;
|
|
DescriptorList* pc_descriptors_list_;
|
|
CompressedStackMapsBuilder* compressed_stackmaps_builder_;
|
|
CodeSourceMapBuilder* code_source_map_builder_;
|
|
CatchEntryMovesMapBuilder* catch_entry_moves_maps_builder_;
|
|
GrowableArray<BlockInfo*> block_info_;
|
|
GrowableArray<CompilerDeoptInfo*> deopt_infos_;
|
|
GrowableArray<SlowPathCode*> slow_path_code_;
|
|
// Fields that were referenced by generated code.
|
|
// This list is needed by precompiler to ensure they are retained.
|
|
GrowableArray<const Field*> used_static_fields_;
|
|
// Stores static call targets as well as stub targets.
|
|
// TODO(srdjan): Evaluate if we should store allocation stub targets into a
|
|
// separate table?
|
|
GrowableArray<StaticCallsStruct*> static_calls_target_table_;
|
|
// The table selectors of all dispatch table calls in the current function.
|
|
GrowableArray<const compiler::TableSelector*> dispatch_table_call_targets_;
|
|
GrowableArray<IndirectGotoInstr*> indirect_gotos_;
|
|
bool is_optimizing_;
|
|
// Set to true if optimized code has IC calls.
|
|
bool may_reoptimize_;
|
|
// True while emitting intrinsic code.
|
|
bool intrinsic_mode_;
|
|
compiler::Label* intrinsic_slow_path_label_ = nullptr;
|
|
bool fully_intrinsified_ = false;
|
|
CodeStatistics* stats_;
|
|
|
|
// The definition whose value is supposed to be at the top of the
|
|
// expression stack. Used by peephole optimization (window size one)
|
|
// to eliminate redundant push/pop pairs.
|
|
Definition* top_of_stack_ = nullptr;
|
|
|
|
const Class& double_class_;
|
|
const Class& mint_class_;
|
|
const Class& float32x4_class_;
|
|
const Class& float64x2_class_;
|
|
const Class& int32x4_class_;
|
|
const Class& list_class_;
|
|
|
|
// Currently instructions generate deopt stubs internally by
|
|
// calling AddDeoptStub. To communicate deoptimization environment
|
|
// that should be used when deoptimizing we store it in this variable.
|
|
// In future AddDeoptStub should be moved out of the instruction template.
|
|
Environment* pending_deoptimization_env_;
|
|
|
|
ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data_;
|
|
Array& edge_counters_array_;
|
|
|
|
// Instruction currently running EmitNativeCode().
|
|
Instruction* current_instruction_ = nullptr;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(FlowGraphCompiler);
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_COMPILER_H_
|