7cd8fda37e
Also load/store canonical hashes in the heap for non-empty TypedData instances in the same manner as canonical hashes for Arrays. TEST=ci (refactoring only) Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try Change-Id: I54274b558fa9f0c8e304198b18cb3f0e9c3e0dfb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/504600 Commit-Queue: Tess Strickland <sstrickl@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
1441 lines
51 KiB
C++
1441 lines
51 KiB
C++
// Copyright (c) 2018, 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 "vm/compiler/frontend/base_flow_graph_builder.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "vm/compiler/backend/range_analysis.h" // For Range.
|
|
#include "vm/compiler/frontend/flow_graph_builder.h" // For InlineExitCollector.
|
|
#include "vm/compiler/frontend/kernel_to_il.h" // For FlowGraphBuilder.
|
|
#include "vm/compiler/frontend/kernel_translation_helper.h"
|
|
#include "vm/compiler/jit/compiler.h" // For Compiler::IsBackgroundCompilation().
|
|
#include "vm/compiler/runtime_api.h"
|
|
#include "vm/growable_array.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/resolver.h"
|
|
|
|
namespace dart {
|
|
namespace kernel {
|
|
|
|
#define Z (zone_)
|
|
#define IG (thread_->isolate_group())
|
|
|
|
static bool SupportsCoverage() {
|
|
#if defined(PRODUCT)
|
|
return false;
|
|
#else
|
|
return !CompilerState::Current().is_aot();
|
|
#endif
|
|
}
|
|
|
|
Fragment& Fragment::operator+=(const Fragment& other) {
|
|
ASSERT(is_valid());
|
|
ASSERT(other.is_valid());
|
|
if (entry == nullptr) {
|
|
entry = other.entry;
|
|
current = other.current;
|
|
} else if (other.entry != nullptr) {
|
|
if (current != nullptr) {
|
|
current->LinkTo(other.entry);
|
|
}
|
|
// Although [other.entry] could be unreachable (if this fragment is
|
|
// closed), there could be a yield continuation point in the middle of
|
|
// [other] fragment so [other.current] is still reachable.
|
|
current = other.current;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
Fragment& Fragment::operator<<=(Instruction* next) {
|
|
ASSERT(is_valid());
|
|
if (entry == nullptr) {
|
|
entry = current = next;
|
|
} else if (current != nullptr) {
|
|
current->LinkTo(next);
|
|
current = next;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void Fragment::Prepend(Instruction* start) {
|
|
ASSERT(is_valid());
|
|
if (entry == nullptr) {
|
|
entry = current = start;
|
|
} else {
|
|
start->LinkTo(entry);
|
|
entry = start;
|
|
}
|
|
}
|
|
|
|
Fragment Fragment::closed() {
|
|
ASSERT(entry != nullptr);
|
|
return Fragment(entry, nullptr);
|
|
}
|
|
|
|
Fragment operator+(const Fragment& first, const Fragment& second) {
|
|
Fragment result = first;
|
|
result += second;
|
|
return result;
|
|
}
|
|
|
|
Fragment operator<<(const Fragment& fragment, Instruction* next) {
|
|
Fragment result = fragment;
|
|
result <<= next;
|
|
return result;
|
|
}
|
|
|
|
TestFragment::TestFragment(Instruction* entry, BranchInstr* branch)
|
|
: entry(entry),
|
|
true_successor_addresses(new SuccessorAddressArray(1)),
|
|
false_successor_addresses(new SuccessorAddressArray(1)) {
|
|
true_successor_addresses->Add(branch->true_successor_address());
|
|
false_successor_addresses->Add(branch->false_successor_address());
|
|
}
|
|
|
|
void TestFragment::ConnectBranchesTo(
|
|
BaseFlowGraphBuilder* builder,
|
|
const TestFragment::SuccessorAddressArray& branches,
|
|
JoinEntryInstr* join) {
|
|
ASSERT(!branches.is_empty());
|
|
for (auto branch : branches) {
|
|
*branch = builder->BuildTargetEntry();
|
|
(*branch)->Goto(join);
|
|
}
|
|
}
|
|
|
|
BlockEntryInstr* TestFragment::CreateSuccessorFor(
|
|
BaseFlowGraphBuilder* builder,
|
|
const TestFragment::SuccessorAddressArray& branches) {
|
|
ASSERT(!branches.is_empty());
|
|
|
|
if (branches.length() == 1) {
|
|
TargetEntryInstr* target = builder->BuildTargetEntry();
|
|
*(branches[0]) = target;
|
|
return target;
|
|
}
|
|
|
|
JoinEntryInstr* join = builder->BuildJoinEntry();
|
|
ConnectBranchesTo(builder, branches, join);
|
|
return join;
|
|
}
|
|
|
|
BlockEntryInstr* TestFragment::CreateTrueSuccessor(
|
|
BaseFlowGraphBuilder* builder) {
|
|
ASSERT(true_successor_addresses != nullptr);
|
|
return CreateSuccessorFor(builder, *true_successor_addresses);
|
|
}
|
|
|
|
BlockEntryInstr* TestFragment::CreateFalseSuccessor(
|
|
BaseFlowGraphBuilder* builder) {
|
|
ASSERT(false_successor_addresses != nullptr);
|
|
return CreateSuccessorFor(builder, *false_successor_addresses);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadContextAt(int depth) {
|
|
intptr_t delta = context_depth_ - depth;
|
|
ASSERT(delta >= 0);
|
|
Fragment instructions = LoadLocal(parsed_function_->current_context_var());
|
|
while (delta-- > 0) {
|
|
instructions += LoadNativeField(Slot::Context_parent());
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StrictCompare(TokenPosition position,
|
|
Token::Kind kind,
|
|
bool number_check /* = false */) {
|
|
Value* right = Pop();
|
|
Value* left = Pop();
|
|
StrictCompareInstr* compare =
|
|
new (Z) StrictCompareInstr(InstructionSource(position), kind, left, right,
|
|
number_check, GetNextDeoptId());
|
|
Push(compare);
|
|
return Fragment(compare);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StrictCompare(Token::Kind kind,
|
|
bool number_check /* = false */) {
|
|
Value* right = Pop();
|
|
Value* left = Pop();
|
|
StrictCompareInstr* compare = new (Z) StrictCompareInstr(
|
|
InstructionSource(), kind, left, right, number_check, GetNextDeoptId());
|
|
Push(compare);
|
|
return Fragment(compare);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::BranchIfTrue(TargetEntryInstr** then_entry,
|
|
TargetEntryInstr** otherwise_entry,
|
|
bool negate) {
|
|
Fragment instructions = Constant(Bool::True());
|
|
return instructions + BranchIfEqual(then_entry, otherwise_entry, negate);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::BranchIfNull(TargetEntryInstr** then_entry,
|
|
TargetEntryInstr** otherwise_entry,
|
|
bool negate) {
|
|
Fragment instructions = NullConstant();
|
|
return instructions + BranchIfEqual(then_entry, otherwise_entry, negate);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::BranchIfEqual(TargetEntryInstr** then_entry,
|
|
TargetEntryInstr** otherwise_entry,
|
|
bool negate) {
|
|
Value* right_value = Pop();
|
|
Value* left_value = Pop();
|
|
StrictCompareInstr* compare = new (Z) StrictCompareInstr(
|
|
InstructionSource(), negate ? Token::kNE_STRICT : Token::kEQ_STRICT,
|
|
left_value, right_value, false, GetNextDeoptId());
|
|
BranchInstr* branch = new (Z) BranchInstr(compare, GetNextDeoptId());
|
|
*then_entry = *branch->true_successor_address() = BuildTargetEntry();
|
|
*otherwise_entry = *branch->false_successor_address() = BuildTargetEntry();
|
|
return Fragment(branch).closed();
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::BranchIfStrictEqual(
|
|
TargetEntryInstr** then_entry,
|
|
TargetEntryInstr** otherwise_entry) {
|
|
Value* rhs = Pop();
|
|
Value* lhs = Pop();
|
|
StrictCompareInstr* compare =
|
|
new (Z) StrictCompareInstr(InstructionSource(), Token::kEQ_STRICT, lhs,
|
|
rhs, false, GetNextDeoptId());
|
|
BranchInstr* branch = new (Z) BranchInstr(compare, GetNextDeoptId());
|
|
*then_entry = *branch->true_successor_address() = BuildTargetEntry();
|
|
*otherwise_entry = *branch->false_successor_address() = BuildTargetEntry();
|
|
return Fragment(branch).closed();
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::Return(TokenPosition position) {
|
|
Fragment instructions;
|
|
|
|
Value* value = Pop();
|
|
ASSERT(stack_ == nullptr);
|
|
const Function& function = parsed_function_->function();
|
|
const Representation representation =
|
|
FlowGraph::ReturnRepresentationOf(function);
|
|
DartReturnInstr* return_instr = new (Z) DartReturnInstr(
|
|
InstructionSource(position), value, GetNextDeoptId(), representation);
|
|
if (exit_collector_ != nullptr) exit_collector_->AddExit(return_instr);
|
|
|
|
instructions <<= return_instr;
|
|
|
|
return instructions.closed();
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::Stop(const char* message) {
|
|
return Fragment(new (Z) StopInstr(message)).closed();
|
|
}
|
|
|
|
bool BaseFlowGraphBuilder::ShouldOmitCheckBoundsIn(const Function& function,
|
|
const Function* caller) {
|
|
auto& state = CompilerState::Current();
|
|
if (!state.is_aot()) {
|
|
return false;
|
|
}
|
|
|
|
// The function itself is annotated with pragma.
|
|
if (state.PragmasOf(function).unsafe_no_bounds_checks) {
|
|
return true;
|
|
}
|
|
|
|
// We are inlining recognized method and the caller
|
|
// is annotated with pragma.
|
|
if (caller != nullptr &&
|
|
FlowGraphBuilder::IsRecognizedMethodForFlowGraph(function) &&
|
|
state.PragmasOf(*caller).unsafe_no_bounds_checks) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool BaseFlowGraphBuilder::ShouldOmitStackOverflowChecks(
|
|
bool optimizing,
|
|
const Function& function) {
|
|
if (!optimizing) {
|
|
return false; // Retain all checks.
|
|
}
|
|
|
|
// Omit CheckStackOverflow if vm:unsafe:no-interrupts is present.
|
|
Object& options = Object::Handle();
|
|
return Library::FindPragma(dart::Thread::Current(),
|
|
/*only_core=*/false, function,
|
|
Symbols::vm_unsafe_no_interrupts(),
|
|
/*multiple=*/false, &options);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::CheckStackOverflow(TokenPosition position,
|
|
intptr_t stack_depth,
|
|
intptr_t loop_depth) {
|
|
const auto deopt_id = GetNextDeoptId();
|
|
// Consume deopt_id and check if we should omit this overflow check.
|
|
// When performing an OSR we need to keep the OSR target in the graph
|
|
// for |BlockEntryInstr::FindOsrEntryAndRelink|.
|
|
if (should_omit_stack_overflow_checks() && (deopt_id != osr_id_)) {
|
|
return Fragment();
|
|
}
|
|
return Fragment(new (Z) CheckStackOverflowInstr(
|
|
InstructionSource(position), stack_depth, loop_depth, deopt_id,
|
|
CheckStackOverflowInstr::kOsrAndPreemption));
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::CheckStackOverflowInPrologue(
|
|
TokenPosition position) {
|
|
auto check = CheckStackOverflow(position, 0, 0);
|
|
if (IsInlining()) {
|
|
// If we are inlining don't actually attach the stack check. We must still
|
|
// create the stack check in order to allocate a deopt id.
|
|
return Fragment();
|
|
}
|
|
return check;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::Constant(const Object& value) {
|
|
DEBUG_ASSERT(value.IsNotTemporaryScopedHandle());
|
|
ConstantInstr* constant = new (Z) ConstantInstr(value);
|
|
Push(constant);
|
|
return Fragment(constant);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::Goto(JoinEntryInstr* destination) {
|
|
return Fragment(new (Z) GotoInstr(destination, GetNextDeoptId())).closed();
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::IntConstant(int64_t value) {
|
|
return Fragment(
|
|
Constant(Integer::ZoneHandle(Z, Integer::New(value, Heap::kOld))));
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::UnboxedIntConstant(
|
|
int64_t value,
|
|
Representation representation) {
|
|
const auto& obj = Integer::ZoneHandle(Z, Integer::NewCanonical(value));
|
|
auto const constant = new (Z) UnboxedConstantInstr(obj, representation);
|
|
Push(constant);
|
|
return Fragment(constant);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::MemoryCopy(classid_t src_cid,
|
|
classid_t dest_cid,
|
|
bool unboxed_inputs,
|
|
bool can_overlap) {
|
|
Value* length = Pop();
|
|
Value* dest_start = Pop();
|
|
Value* src_start = Pop();
|
|
Value* dest = Pop();
|
|
Value* src = Pop();
|
|
auto copy =
|
|
new (Z) MemoryCopyInstr(src, src_cid, dest, dest_cid, src_start,
|
|
dest_start, length, unboxed_inputs, can_overlap);
|
|
return Fragment(copy);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::TailCall(const Code& code) {
|
|
Value* arg_desc = Pop();
|
|
return Fragment(new (Z) TailCallInstr(code, arg_desc)).closed();
|
|
}
|
|
|
|
void BaseFlowGraphBuilder::InlineBailout(const char* reason) {
|
|
if (IsInlining()) {
|
|
parsed_function_->function().set_is_inlinable(false);
|
|
parsed_function_->Bailout("kernel::BaseFlowGraphBuilder", reason);
|
|
}
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadArgDescriptor() {
|
|
if (has_saved_args_desc_array()) {
|
|
const ArgumentsDescriptor descriptor(saved_args_desc_array());
|
|
// Double-check that compile-time Size() matches runtime size on target.
|
|
ASSERT_EQUAL(descriptor.Size(), FlowGraph::ComputeArgumentsSizeInWords(
|
|
function_, descriptor.Count()));
|
|
return Constant(saved_args_desc_array());
|
|
}
|
|
ASSERT(parsed_function_->has_arg_desc_var());
|
|
return LoadLocal(parsed_function_->arg_desc_var());
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::TestTypeArgsLen(Fragment eq_branch,
|
|
Fragment neq_branch,
|
|
intptr_t num_type_args) {
|
|
Fragment test;
|
|
|
|
// Compile-time arguments descriptor case.
|
|
if (has_saved_args_desc_array()) {
|
|
const ArgumentsDescriptor descriptor(saved_args_desc_array_);
|
|
return descriptor.TypeArgsLen() == num_type_args ? eq_branch : neq_branch;
|
|
}
|
|
|
|
// Runtime arguments descriptor case.
|
|
TargetEntryInstr* eq_entry;
|
|
TargetEntryInstr* neq_entry;
|
|
|
|
test += LoadArgDescriptor();
|
|
test += LoadNativeField(Slot::ArgumentsDescriptor_type_args_len());
|
|
test += IntConstant(num_type_args);
|
|
test += BranchIfEqual(&eq_entry, &neq_entry);
|
|
|
|
eq_branch.Prepend(eq_entry);
|
|
neq_branch.Prepend(neq_entry);
|
|
|
|
JoinEntryInstr* join = BuildJoinEntry();
|
|
eq_branch += Goto(join);
|
|
neq_branch += Goto(join);
|
|
|
|
return Fragment(test.entry, join);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::TestDelayedTypeArgs(LocalVariable* closure,
|
|
Fragment present,
|
|
Fragment absent) {
|
|
const auto& function = parsed_function_->function();
|
|
ASSERT(function.IsClosureFunction());
|
|
|
|
if (!function.IsGeneric()) {
|
|
return absent;
|
|
}
|
|
|
|
Fragment test;
|
|
TargetEntryInstr* absent_entry;
|
|
TargetEntryInstr* present_entry;
|
|
test += LoadLocal(closure);
|
|
test += LoadNativeField(Slot::GetClosureElementSlot(
|
|
thread_, compiler::target::Closure::element_offset(
|
|
UntaggedClosure::kDelayedTypeArgumentsIndex)));
|
|
test += Constant(Object::empty_type_arguments());
|
|
test += BranchIfEqual(&absent_entry, &present_entry);
|
|
|
|
present.Prepend(present_entry);
|
|
absent.Prepend(absent_entry);
|
|
|
|
JoinEntryInstr* join = BuildJoinEntry();
|
|
absent += Goto(join);
|
|
present += Goto(join);
|
|
|
|
return Fragment(test.entry, join);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::TestAnyTypeArgs(Fragment present,
|
|
Fragment absent) {
|
|
if (parsed_function_->function().IsClosureFunction()) {
|
|
LocalVariable* closure = parsed_function_->ParameterVariable(0);
|
|
|
|
JoinEntryInstr* complete = BuildJoinEntry();
|
|
JoinEntryInstr* present_entry = BuildJoinEntry();
|
|
|
|
Fragment test = TestTypeArgsLen(
|
|
TestDelayedTypeArgs(closure, Goto(present_entry), absent),
|
|
Goto(present_entry), 0);
|
|
test += Goto(complete);
|
|
|
|
Fragment(present_entry) + present + Goto(complete);
|
|
|
|
return Fragment(test.entry, complete);
|
|
} else {
|
|
return TestTypeArgsLen(absent, present, 0);
|
|
}
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadIndexed(classid_t class_id,
|
|
intptr_t index_scale,
|
|
bool index_unboxed,
|
|
AlignmentType alignment) {
|
|
Value* index = Pop();
|
|
// A C pointer if index_unboxed, otherwise a boxed Dart value.
|
|
Value* array = Pop();
|
|
|
|
// We use C behavior when dereferencing pointers, so we use aligned access in
|
|
// all cases.
|
|
LoadIndexedInstr* instr = new (Z)
|
|
LoadIndexedInstr(array, index, index_unboxed, index_scale, class_id,
|
|
alignment, DeoptId::kNone, InstructionSource());
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::GenericCheckBound() {
|
|
// Consume deopt_id even if not inserting the instruction to avoid
|
|
// problems with JIT (even though should_omit_check_bounds() will be false
|
|
// in JIT).
|
|
const intptr_t deopt_id = GetNextDeoptId();
|
|
Value* index = Pop();
|
|
Value* length = Pop();
|
|
auto* instr = new (Z) GenericCheckBoundInstr(
|
|
length, index, deopt_id,
|
|
should_omit_check_bounds() ? GenericCheckBoundInstr::Mode::kPhantom
|
|
: GenericCheckBoundInstr::Mode::kReal);
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadUntagged(intptr_t offset) {
|
|
Value* object = Pop();
|
|
auto load = new (Z) LoadUntaggedInstr(object, offset);
|
|
Push(load);
|
|
return Fragment(load);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::ConvertUntaggedToUnboxed() {
|
|
Value* value = Pop();
|
|
auto converted = new (Z) IntConverterInstr(kUntagged, kUnboxedAddress, value);
|
|
Push(converted);
|
|
return Fragment(converted);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::ConvertUnboxedToUntagged() {
|
|
Value* value = Pop();
|
|
auto converted = new (Z) IntConverterInstr(kUnboxedAddress, kUntagged, value);
|
|
Push(converted);
|
|
return Fragment(converted);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::CalculateElementAddress(intptr_t index_scale) {
|
|
Value* offset = Pop();
|
|
Value* index = Pop();
|
|
Value* base = Pop();
|
|
auto adjust =
|
|
new (Z) CalculateElementAddressInstr(base, index, index_scale, offset);
|
|
Push(adjust);
|
|
return Fragment(adjust);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::FloatToDouble() {
|
|
Value* value = Pop();
|
|
FloatToDoubleInstr* instr = new FloatToDoubleInstr(value, DeoptId::kNone);
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::DoubleToFloat() {
|
|
Value* value = Pop();
|
|
DoubleToFloatInstr* instr = new DoubleToFloatInstr(value, DeoptId::kNone);
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadField(const Field& field,
|
|
bool calls_initializer) {
|
|
return LoadNativeField(Slot::Get(MayCloneField(Z, field), parsed_function_),
|
|
calls_initializer);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadNativeField(
|
|
const Slot& native_field,
|
|
InnerPointerAccess loads_inner_pointer,
|
|
bool calls_initializer,
|
|
compiler::Assembler::MemoryOrder memory_order) {
|
|
LoadFieldInstr* load = new (Z) LoadFieldInstr(
|
|
Pop(), native_field, loads_inner_pointer, InstructionSource(),
|
|
calls_initializer, calls_initializer ? GetNextDeoptId() : DeoptId::kNone,
|
|
memory_order);
|
|
Push(load);
|
|
return Fragment(load);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadNativeField(
|
|
const Slot& native_field,
|
|
bool calls_initializer,
|
|
compiler::Assembler::MemoryOrder memory_order) {
|
|
const InnerPointerAccess loads_inner_pointer =
|
|
native_field.representation() == kUntagged
|
|
? (native_field.may_contain_inner_pointer()
|
|
? InnerPointerAccess::kMayBeInnerPointer
|
|
: InnerPointerAccess::kCannotBeInnerPointer)
|
|
: InnerPointerAccess::kNotUntagged;
|
|
return LoadNativeField(native_field, loads_inner_pointer, calls_initializer,
|
|
memory_order);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadLocal(LocalVariable* variable) {
|
|
ASSERT(!variable->is_captured());
|
|
LoadLocalInstr* load = new (Z) LoadLocalInstr(*variable, InstructionSource());
|
|
Push(load);
|
|
return Fragment(load);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::NullConstant() {
|
|
return Constant(Instance::ZoneHandle(Z, Instance::null()));
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::GuardFieldLength(const Field& field,
|
|
intptr_t deopt_id) {
|
|
return Fragment(new (Z) GuardFieldLengthInstr(Pop(), field, deopt_id));
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::GuardFieldClass(const Field& field,
|
|
intptr_t deopt_id) {
|
|
return Fragment(new (Z) GuardFieldClassInstr(Pop(), field, deopt_id));
|
|
}
|
|
|
|
const Field& BaseFlowGraphBuilder::MayCloneField(Zone* zone,
|
|
const Field& field) {
|
|
if (CompilerState::Current().should_clone_fields() && field.IsOriginal()) {
|
|
return Field::ZoneHandle(zone, field.CloneFromOriginal());
|
|
} else {
|
|
DEBUG_ASSERT(field.IsNotTemporaryScopedHandle());
|
|
return field;
|
|
}
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StoreNativeField(
|
|
TokenPosition position,
|
|
const Slot& slot,
|
|
InnerPointerAccess stores_inner_pointer,
|
|
StoreFieldInstr::Kind kind /* = StoreFieldInstr::Kind::kOther */,
|
|
StoreBarrierType emit_store_barrier /* = kEmitStoreBarrier */,
|
|
compiler::Assembler::MemoryOrder memory_order /* = kRelaxed */) {
|
|
Value* value = Pop();
|
|
Value* instance = Pop();
|
|
StoreFieldInstr* store = new (Z) StoreFieldInstr(
|
|
slot, instance, value, emit_store_barrier, stores_inner_pointer,
|
|
InstructionSource(position), kind, memory_order);
|
|
return Fragment(store);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StoreField(
|
|
const Field& field,
|
|
StoreFieldInstr::Kind kind /* = StoreFieldInstr::Kind::kOther */,
|
|
StoreBarrierType emit_store_barrier) {
|
|
return StoreNativeField(TokenPosition::kNoSource,
|
|
Slot::Get(MayCloneField(Z, field), parsed_function_),
|
|
kind, emit_store_barrier);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StoreFieldGuarded(
|
|
const Field& field,
|
|
StoreFieldInstr::Kind kind /* = StoreFieldInstr::Kind::kOther */,
|
|
bool requires_immutability_check /* = false */) {
|
|
Fragment instructions;
|
|
const Field& field_clone = MayCloneField(Z, field);
|
|
if (IG->use_field_guards()) {
|
|
LocalVariable* store_expression = MakeTemporary();
|
|
|
|
// Note: unboxing decision can only change due to hot reload at which
|
|
// point all code will be cleared, so there is no need to worry about
|
|
// stability of deopt id numbering.
|
|
if (!field_clone.is_unboxed()) {
|
|
instructions += LoadLocal(store_expression);
|
|
instructions += GuardFieldClass(field_clone, GetNextDeoptId());
|
|
}
|
|
|
|
// Field length guard can be omitted if it is not needed.
|
|
// However, it is possible that we were tracking list length previously,
|
|
// and generated length guards in the past. We need to generate same IL
|
|
// to keep deopt ids stable, but we can discard generated IL fragment
|
|
// if length guard is not needed.
|
|
Fragment length_guard;
|
|
length_guard += LoadLocal(store_expression);
|
|
length_guard += GuardFieldLength(field_clone, GetNextDeoptId());
|
|
|
|
if (field_clone.needs_length_check()) {
|
|
instructions += length_guard;
|
|
}
|
|
|
|
// If we are tracking exactness of the static type of the field then
|
|
// emit appropriate guard.
|
|
if (field_clone.static_type_exactness_state().IsTracking()) {
|
|
instructions += LoadLocal(store_expression);
|
|
instructions <<=
|
|
new (Z) GuardFieldTypeInstr(Pop(), field_clone, GetNextDeoptId());
|
|
}
|
|
}
|
|
if (requires_immutability_check) {
|
|
ASSERT(kind == StoreFieldInstr::Kind::kInitializing);
|
|
LocalVariable* store_expression = MakeTemporary();
|
|
instructions += LoadLocal(store_expression);
|
|
instructions <<= new (Z)
|
|
CheckFieldImmutabilityInstr(Pop(), field_clone, GetNextDeoptId());
|
|
}
|
|
instructions +=
|
|
StoreNativeField(Slot::Get(field_clone, parsed_function_), kind);
|
|
return instructions;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadStaticField(const Field& field,
|
|
bool calls_initializer) {
|
|
const bool check_access =
|
|
(dart::FLAG_experimental_shared_data && !field.is_shared());
|
|
const auto slow_path =
|
|
calls_initializer
|
|
? SlowPathOnSentinelValue::kCallInitializer
|
|
: (check_access ? SlowPathOnSentinelValue::kThrowAccessError
|
|
: SlowPathOnSentinelValue::kDoNothing);
|
|
LoadStaticFieldInstr* load = new (Z) LoadStaticFieldInstr(
|
|
field, InstructionSource(), slow_path,
|
|
slow_path != SlowPathOnSentinelValue::kDoNothing ? GetNextDeoptId()
|
|
: DeoptId::kNone);
|
|
Push(load);
|
|
return Fragment(load);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::RedefinitionWithType(const AbstractType& type) {
|
|
auto redefinition = new (Z) RedefinitionInstr(Pop());
|
|
redefinition->set_constrained_type(
|
|
new (Z) CompileType(CompileType::FromAbstractType(
|
|
type, CompileType::kCanBeNull, CompileType::kCannotBeSentinel)));
|
|
Push(redefinition);
|
|
return Fragment(redefinition);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::ReachabilityFence() {
|
|
Fragment instructions;
|
|
instructions <<= new (Z) ReachabilityFenceInstr(Pop());
|
|
return instructions;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::Utf8Scan() {
|
|
Value* table = Pop();
|
|
Value* end = Pop();
|
|
Value* start = Pop();
|
|
Value* bytes = Pop();
|
|
Value* decoder = Pop();
|
|
const Field& scan_flags_field =
|
|
compiler::LookupConvertUtf8DecoderScanFlagsField();
|
|
auto scan = new (Z) Utf8ScanInstr(
|
|
decoder, bytes, start, end, table,
|
|
Slot::Get(MayCloneField(Z, scan_flags_field), parsed_function_));
|
|
Push(scan);
|
|
return Fragment(scan);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StoreStaticField(TokenPosition position,
|
|
const Field& field) {
|
|
return Fragment(new (Z) StoreStaticFieldInstr(MayCloneField(Z, field), Pop(),
|
|
InstructionSource(position),
|
|
GetNextDeoptId()));
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StoreIndexed(classid_t class_id) {
|
|
// This fragment builder cannot be used for typed data accesses.
|
|
ASSERT(!IsTypedDataBaseClassId(class_id));
|
|
Value* value = Pop();
|
|
Value* index = Pop();
|
|
const StoreBarrierType emit_store_barrier =
|
|
value->BindsToConstant() ? kNoStoreBarrier : kEmitStoreBarrier;
|
|
StoreIndexedInstr* store = new (Z) StoreIndexedInstr(
|
|
Pop(), // Array.
|
|
index, value, emit_store_barrier, /*index_unboxed=*/false,
|
|
compiler::target::Instance::ElementSizeFor(class_id), class_id,
|
|
kAlignedAccess, DeoptId::kNone, InstructionSource());
|
|
return Fragment(store);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StoreIndexedTypedData(classid_t class_id,
|
|
intptr_t index_scale,
|
|
bool index_unboxed,
|
|
AlignmentType alignment) {
|
|
ASSERT(IsTypedDataBaseClassId(class_id));
|
|
Value* value = Pop();
|
|
Value* index = Pop();
|
|
Value* c_pointer = Pop();
|
|
StoreIndexedInstr* instr = new (Z) StoreIndexedInstr(
|
|
c_pointer, index, value, kNoStoreBarrier, index_unboxed, index_scale,
|
|
class_id, alignment, DeoptId::kNone, InstructionSource());
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StoreLocal(TokenPosition position,
|
|
LocalVariable* variable) {
|
|
if (variable->is_captured()) {
|
|
Fragment instructions;
|
|
LocalVariable* value = MakeTemporary();
|
|
instructions += LoadContextAt(variable->owner()->context_level());
|
|
instructions += LoadLocal(value);
|
|
instructions += StoreNativeField(
|
|
position, Slot::GetContextVariableSlotFor(thread_, *variable));
|
|
return instructions;
|
|
}
|
|
return StoreLocalRaw(position, variable);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StoreLocalRaw(TokenPosition position,
|
|
LocalVariable* variable) {
|
|
ASSERT(!variable->is_captured());
|
|
Value* value = Pop();
|
|
StoreLocalInstr* store =
|
|
new (Z) StoreLocalInstr(*variable, value, InstructionSource(position));
|
|
Fragment instructions(store);
|
|
Push(store);
|
|
return instructions;
|
|
}
|
|
|
|
LocalVariable* BaseFlowGraphBuilder::MakeTemporary(const char* suffix) {
|
|
static constexpr intptr_t kTemporaryNameLength = 64;
|
|
char name[kTemporaryNameLength];
|
|
intptr_t index = stack_->definition()->temp_index();
|
|
if (suffix != nullptr) {
|
|
Utils::SNPrint(name, kTemporaryNameLength, ":t_%s", suffix);
|
|
} else {
|
|
Utils::SNPrint(name, kTemporaryNameLength, ":t%" Pd, index);
|
|
}
|
|
const String& symbol_name =
|
|
String::ZoneHandle(Z, Symbols::New(thread_, name));
|
|
LocalVariable* variable =
|
|
new (Z) LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
|
|
symbol_name, Object::dynamic_type());
|
|
// Set the index relative to the base of the expression stack including
|
|
// outgoing arguments.
|
|
variable->set_index(
|
|
VariableIndex(-parsed_function_->num_stack_locals() - index));
|
|
|
|
// The value on top of the stack has uses as if it were a local variable.
|
|
// Mark all definitions on the stack as used so that their temp indices
|
|
// will not be cleared (causing them to never be materialized in the
|
|
// expression stack and skew stack depth).
|
|
for (Value* item = stack_; item != nullptr; item = item->next_use()) {
|
|
item->definition()->set_ssa_temp_index(0);
|
|
}
|
|
|
|
return variable;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::DropTemporary(LocalVariable** temp) {
|
|
ASSERT(temp != nullptr && *temp != nullptr && (*temp)->HasIndex());
|
|
// Check that the temporary matches the current stack definition.
|
|
ASSERT_EQUAL(
|
|
stack_->definition()->temp_index(),
|
|
-(*temp)->index().value() - parsed_function_->num_stack_locals());
|
|
*temp = nullptr; // Clear to avoid inadvertent usage after dropping.
|
|
return Drop();
|
|
}
|
|
|
|
void BaseFlowGraphBuilder::SetTempIndex(Definition* definition) {
|
|
definition->set_temp_index(
|
|
stack_ == nullptr ? 0 : stack_->definition()->temp_index() + 1);
|
|
}
|
|
|
|
void BaseFlowGraphBuilder::Push(Definition* definition) {
|
|
SetTempIndex(definition);
|
|
Value::AddToList(new (Z) Value(definition), &stack_);
|
|
}
|
|
|
|
Definition* BaseFlowGraphBuilder::Peek(intptr_t depth) {
|
|
Value* head = stack_;
|
|
for (intptr_t i = 0; i < depth; ++i) {
|
|
ASSERT(head != nullptr);
|
|
head = head->next_use();
|
|
}
|
|
ASSERT(head != nullptr);
|
|
return head->definition();
|
|
}
|
|
|
|
Value* BaseFlowGraphBuilder::Pop() {
|
|
ASSERT(stack_ != nullptr);
|
|
Value* value = stack_;
|
|
stack_ = value->next_use();
|
|
if (stack_ != nullptr) stack_->set_previous_use(nullptr);
|
|
|
|
value->set_next_use(nullptr);
|
|
value->set_previous_use(nullptr);
|
|
value->definition()->ClearSSATempIndex();
|
|
return value;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::Drop() {
|
|
ASSERT(stack_ != nullptr);
|
|
Fragment instructions;
|
|
Definition* definition = stack_->definition();
|
|
// The SSA renaming implementation doesn't like [LoadLocal]s without a
|
|
// tempindex.
|
|
if (definition->HasSSATemp() || definition->IsLoadLocal()) {
|
|
instructions <<= new (Z) DropTempsInstr(1, nullptr);
|
|
} else {
|
|
definition->ClearTempIndex();
|
|
}
|
|
|
|
Pop();
|
|
return instructions;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::DropTempsPreserveTop(
|
|
intptr_t num_temps_to_drop) {
|
|
Value* top = Pop();
|
|
|
|
for (intptr_t i = 0; i < num_temps_to_drop; ++i) {
|
|
Pop();
|
|
}
|
|
|
|
DropTempsInstr* drop_temps = new (Z) DropTempsInstr(num_temps_to_drop, top);
|
|
Push(drop_temps);
|
|
|
|
return Fragment(drop_temps);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::MakeTemp() {
|
|
MakeTempInstr* make_temp = new (Z) MakeTempInstr(Z);
|
|
Push(make_temp);
|
|
return Fragment(make_temp);
|
|
}
|
|
|
|
TargetEntryInstr* BaseFlowGraphBuilder::BuildTargetEntry() {
|
|
return new (Z) TargetEntryInstr(AllocateBlockId(), CurrentTryIndex(),
|
|
GetNextDeoptId(), GetStackDepth());
|
|
}
|
|
|
|
TargetEntryInstr* BaseFlowGraphBuilder::BuildTargetEntry(intptr_t try_index) {
|
|
return new (Z) TargetEntryInstr(AllocateBlockId(), try_index,
|
|
GetNextDeoptId(), GetStackDepth());
|
|
}
|
|
|
|
FunctionEntryInstr* BaseFlowGraphBuilder::BuildFunctionEntry(
|
|
GraphEntryInstr* graph_entry) {
|
|
return new (Z) FunctionEntryInstr(graph_entry, AllocateBlockId(),
|
|
CurrentTryIndex(), GetNextDeoptId());
|
|
}
|
|
|
|
JoinEntryInstr* BaseFlowGraphBuilder::BuildJoinEntry(intptr_t try_index) {
|
|
return new (Z) JoinEntryInstr(AllocateBlockId(), try_index, GetNextDeoptId(),
|
|
GetStackDepth());
|
|
}
|
|
|
|
JoinEntryInstr* BaseFlowGraphBuilder::BuildJoinEntry() {
|
|
return new (Z) JoinEntryInstr(AllocateBlockId(), CurrentTryIndex(),
|
|
GetNextDeoptId(), GetStackDepth());
|
|
}
|
|
|
|
TryEntryInstr* BaseFlowGraphBuilder::BuildTryEntry(intptr_t try_index) {
|
|
return new (Z) TryEntryInstr(AllocateBlockId(), try_index, GetNextDeoptId(),
|
|
GetStackDepth());
|
|
}
|
|
|
|
IndirectEntryInstr* BaseFlowGraphBuilder::BuildIndirectEntry(
|
|
intptr_t indirect_id,
|
|
intptr_t try_index) {
|
|
return new (Z) IndirectEntryInstr(AllocateBlockId(), indirect_id, try_index,
|
|
GetNextDeoptId());
|
|
}
|
|
|
|
InputsArray BaseFlowGraphBuilder::GetArguments(int count) {
|
|
InputsArray arguments(Z, count);
|
|
arguments.SetLength(count);
|
|
for (intptr_t i = count - 1; i >= 0; --i) {
|
|
arguments[i] = Pop();
|
|
}
|
|
return arguments;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::SmiRelationalOp(Token::Kind kind) {
|
|
Value* right = Pop();
|
|
Value* left = Pop();
|
|
RelationalOpInstr* instr = new (Z) RelationalOpInstr(
|
|
InstructionSource(), kind, left, right, kTagged, GetNextDeoptId());
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::SmiBinaryOp(Token::Kind kind,
|
|
bool is_truncating) {
|
|
return BinaryIntegerOp(kind, kTagged, is_truncating);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::BinaryIntegerOp(Token::Kind kind,
|
|
Representation representation,
|
|
bool is_truncating) {
|
|
ASSERT(representation == kUnboxedInt32 || representation == kUnboxedUint32 ||
|
|
representation == kUnboxedInt64 || representation == kTagged);
|
|
Value* right = Pop();
|
|
Value* left = Pop();
|
|
BinaryIntegerOpInstr* instr = BinaryIntegerOpInstr::Make(
|
|
representation, kind, left, right, GetNextDeoptId());
|
|
ASSERT(instr != nullptr);
|
|
if (is_truncating) {
|
|
instr->mark_truncating();
|
|
}
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadFpRelativeSlot(
|
|
intptr_t offset,
|
|
CompileType result_type,
|
|
Representation representation) {
|
|
LoadIndexedUnsafeInstr* instr = new (Z)
|
|
LoadIndexedUnsafeInstr(Pop(), offset, result_type, representation);
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::StoreFpRelativeSlot(intptr_t offset) {
|
|
Value* value = Pop();
|
|
Value* index = Pop();
|
|
StoreIndexedUnsafeInstr* instr =
|
|
new (Z) StoreIndexedUnsafeInstr(index, value, offset);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
JoinEntryInstr* BaseFlowGraphBuilder::BuildThrowNoSuchMethod() {
|
|
JoinEntryInstr* nsm = BuildJoinEntry();
|
|
|
|
Fragment failing(nsm);
|
|
failing += LoadArgDescriptor();
|
|
failing += TailCall(StubCode::CallClosureNoSuchMethod());
|
|
|
|
return nsm;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::ThrowException(TokenPosition position) {
|
|
Fragment instructions;
|
|
Value* exception = Pop();
|
|
instructions += Fragment(new (Z) ThrowInstr(InstructionSource(position),
|
|
GetNextDeoptId(), exception))
|
|
.closed();
|
|
// Use its side effect of leaving a constant on the stack (does not change
|
|
// the graph).
|
|
NullConstant();
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::BooleanNegate() {
|
|
BooleanNegateInstr* negate = new (Z) BooleanNegateInstr(Pop());
|
|
Push(negate);
|
|
return Fragment(negate);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::AllocateContext(
|
|
const ZoneGrowableArray<const Slot*>& context_slots) {
|
|
AllocateContextInstr* allocate = new (Z) AllocateContextInstr(
|
|
InstructionSource(), context_slots, GetNextDeoptId());
|
|
Push(allocate);
|
|
return Fragment(allocate);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::AllocateClosure(TokenPosition position,
|
|
bool has_delayed_type_args,
|
|
bool has_instantiator_type_args,
|
|
bool has_function_type_args,
|
|
bool is_tear_off) {
|
|
auto const context = Pop();
|
|
auto const function = Pop();
|
|
auto* allocate = new (Z) AllocateClosureInstr(
|
|
InstructionSource(position), function, context, has_delayed_type_args,
|
|
has_instantiator_type_args, has_function_type_args, is_tear_off,
|
|
GetNextDeoptId());
|
|
Push(allocate);
|
|
return Fragment(allocate);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::CreateArray() {
|
|
Value* element_count = Pop();
|
|
CreateArrayInstr* array =
|
|
new (Z) CreateArrayInstr(InstructionSource(),
|
|
Pop(), // Element type.
|
|
element_count, GetNextDeoptId());
|
|
Push(array);
|
|
return Fragment(array);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::AllocateRecord(TokenPosition position,
|
|
RecordShape shape) {
|
|
AllocateRecordInstr* allocate = new (Z)
|
|
AllocateRecordInstr(InstructionSource(position), shape, GetNextDeoptId());
|
|
Push(allocate);
|
|
return Fragment(allocate);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::AllocateSmallRecord(TokenPosition position,
|
|
RecordShape shape) {
|
|
const intptr_t num_fields = shape.num_fields();
|
|
ASSERT(num_fields == 2 || num_fields == 3);
|
|
Value* value2 = (num_fields > 2) ? Pop() : nullptr;
|
|
Value* value1 = Pop();
|
|
Value* value0 = Pop();
|
|
AllocateSmallRecordInstr* allocate = new (Z)
|
|
AllocateSmallRecordInstr(InstructionSource(position), shape, value0,
|
|
value1, value2, GetNextDeoptId());
|
|
Push(allocate);
|
|
return Fragment(allocate);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::AllocateTypedData(TokenPosition position,
|
|
classid_t class_id) {
|
|
Value* num_elements = Pop();
|
|
auto* instr = new (Z) AllocateTypedDataInstr(
|
|
InstructionSource(position), class_id, num_elements, GetNextDeoptId());
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::InstantiateType(const AbstractType& type) {
|
|
Value* function_type_args = Pop();
|
|
Value* instantiator_type_args = Pop();
|
|
InstantiateTypeInstr* instr = new (Z)
|
|
InstantiateTypeInstr(InstructionSource(), type, instantiator_type_args,
|
|
function_type_args, GetNextDeoptId());
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::InstantiateTypeArguments(
|
|
const TypeArguments& type_arguments_value) {
|
|
Fragment instructions;
|
|
instructions += Constant(type_arguments_value);
|
|
|
|
Value* type_arguments = Pop();
|
|
Value* function_type_args = Pop();
|
|
Value* instantiator_type_args = Pop();
|
|
const Class& instantiator_class = Class::ZoneHandle(Z, function_.Owner());
|
|
InstantiateTypeArgumentsInstr* instr = new (Z) InstantiateTypeArgumentsInstr(
|
|
InstructionSource(), instantiator_type_args, function_type_args,
|
|
type_arguments, instantiator_class, function_, GetNextDeoptId());
|
|
Push(instr);
|
|
instructions += Fragment(instr);
|
|
return instructions;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::InstantiateDynamicTypeArguments() {
|
|
Value* type_arguments = Pop();
|
|
Value* function_type_args = Pop();
|
|
Value* instantiator_type_args = Pop();
|
|
const Function& function = Object::null_function();
|
|
const Class& instantiator_class = Class::ZoneHandle(Z);
|
|
InstantiateTypeArgumentsInstr* instr = new (Z) InstantiateTypeArgumentsInstr(
|
|
InstructionSource(), instantiator_type_args, function_type_args,
|
|
type_arguments, instantiator_class, function, GetNextDeoptId());
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::LoadClassId() {
|
|
LoadClassIdInstr* load = new (Z) LoadClassIdInstr(Pop());
|
|
Push(load);
|
|
return Fragment(load);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::AllocateObject(TokenPosition position,
|
|
const Class& klass,
|
|
intptr_t argument_count) {
|
|
ASSERT((argument_count == 0) || (argument_count == 1));
|
|
Value* type_arguments = (argument_count > 0) ? Pop() : nullptr;
|
|
AllocateObjectInstr* allocate = new (Z) AllocateObjectInstr(
|
|
InstructionSource(position), klass, GetNextDeoptId(), type_arguments);
|
|
Push(allocate);
|
|
return Fragment(allocate);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::Box(Representation from) {
|
|
Fragment instructions;
|
|
if (from == kUnboxedFloat) {
|
|
instructions += FloatToDouble();
|
|
from = kUnboxedDouble;
|
|
}
|
|
BoxInstr* box = BoxInstr::Create(from, Pop());
|
|
instructions <<= box;
|
|
Push(box);
|
|
return instructions;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::DebugStepCheck(TokenPosition position) {
|
|
#ifdef PRODUCT
|
|
return Fragment();
|
|
#else
|
|
return Fragment(new (Z) DebugStepCheckInstr(
|
|
InstructionSource(position), UntaggedPcDescriptors::kRuntimeCall,
|
|
GetNextDeoptId()));
|
|
#endif
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::CheckNull(TokenPosition position,
|
|
LocalVariable* receiver,
|
|
const String& function_name) {
|
|
Fragment instructions = LoadLocal(receiver);
|
|
|
|
CheckNullInstr* check_null = new (Z) CheckNullInstr(
|
|
Pop(), function_name, GetNextDeoptId(), InstructionSource(position),
|
|
function_name.IsNull() ? CheckNullInstr::kCastError
|
|
: CheckNullInstr::kNoSuchMethod);
|
|
|
|
// Does not use the redefinition, no `Push(check_null)`.
|
|
instructions <<= check_null;
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::CheckNullOptimized(
|
|
const String& function_name,
|
|
CheckNullInstr::ExceptionType exception_type,
|
|
TokenPosition position) {
|
|
Value* value = Pop();
|
|
CheckNullInstr* check_null =
|
|
new (Z) CheckNullInstr(value, function_name, GetNextDeoptId(),
|
|
InstructionSource(position), exception_type);
|
|
Push(check_null); // Use the redefinition.
|
|
return Fragment(check_null);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::CheckNotDeeplyImmutable(
|
|
CheckWritableInstr::Kind kind) {
|
|
Value* value = Pop();
|
|
auto* check_writable = new (Z)
|
|
CheckWritableInstr(value, GetNextDeoptId(), InstructionSource(), kind);
|
|
return Fragment(check_writable);
|
|
}
|
|
|
|
void BaseFlowGraphBuilder::RecordUncheckedEntryPoint(
|
|
GraphEntryInstr* graph_entry,
|
|
FunctionEntryInstr* unchecked_entry) {
|
|
// Closures always check all arguments on their checked entry-point, most
|
|
// call-sites are unchecked, and they're inlined less often, so it's very
|
|
// beneficial to build multiple entry-points for them. Regular methods however
|
|
// have fewer checks to begin with since they have dynamic invocation
|
|
// forwarders, so in AOT we implement a more conservative time-space tradeoff
|
|
// by only building the unchecked entry-point when inlining. We should
|
|
// reconsider this heuristic if we identify non-inlined type-checks in
|
|
// hotspots of new benchmarks.
|
|
if (!IsInlining() && (parsed_function_->function().IsClosureFunction() ||
|
|
!CompilerState::Current().is_aot())) {
|
|
graph_entry->set_unchecked_entry(unchecked_entry);
|
|
} else if (InliningUncheckedEntry()) {
|
|
graph_entry->set_normal_entry(unchecked_entry);
|
|
}
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::BuildEntryPointsIntrospection() {
|
|
if (!FLAG_enable_testing_pragmas) return Drop();
|
|
|
|
auto& function = Function::Handle(Z, parsed_function_->function().ptr());
|
|
|
|
if (function.IsImplicitClosureFunction()) {
|
|
const auto& parent = Function::Handle(Z, function.parent_function());
|
|
const auto& func_name = String::Handle(Z, parent.name());
|
|
const auto& owner = Class::Handle(Z, parent.Owner());
|
|
if (owner.EnsureIsFinalized(thread_) == Error::null()) {
|
|
function = Resolver::ResolveFunction(Z, owner, func_name);
|
|
}
|
|
}
|
|
|
|
Object& options = Object::Handle(Z);
|
|
if (!Library::FindPragma(thread_, /*only_core=*/false, function,
|
|
Symbols::vm_trace_entrypoints(), /*multiple=*/false,
|
|
&options) ||
|
|
options.IsNull() || !options.IsClosure()) {
|
|
return Drop();
|
|
}
|
|
auto& closure = Closure::ZoneHandle(Z, Closure::Cast(options).ptr());
|
|
LocalVariable* entry_point_num = MakeTemporary("entry_point_num");
|
|
|
|
auto& function_name = String::ZoneHandle(
|
|
Z, String::New(function.ToLibNamePrefixedQualifiedCString(), Heap::kOld));
|
|
if (parsed_function_->function().IsImplicitClosureFunction()) {
|
|
function_name = String::Concat(
|
|
function_name, String::Handle(Z, String::New("#tearoff", Heap::kNew)),
|
|
Heap::kOld);
|
|
}
|
|
if (!function_name.IsCanonical()) {
|
|
function_name = Symbols::New(thread_, function_name);
|
|
}
|
|
|
|
Fragment call_hook;
|
|
call_hook += Constant(closure);
|
|
call_hook += Constant(function_name);
|
|
call_hook += LoadLocal(entry_point_num);
|
|
if (FLAG_precompiled_mode) {
|
|
call_hook += Constant(closure);
|
|
} else {
|
|
call_hook += Constant(Function::ZoneHandle(Z, closure.function()));
|
|
}
|
|
call_hook += ClosureCall(Function::null_function(), TokenPosition::kNoSource,
|
|
/*type_args_len=*/0, /*argument_count=*/3,
|
|
/*argument_names=*/Array::ZoneHandle(Z));
|
|
call_hook += Drop(); // result of closure call
|
|
call_hook += DropTemporary(&entry_point_num); // entrypoint number
|
|
return call_hook;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::ClosureCall(
|
|
const Function& target_function,
|
|
TokenPosition position,
|
|
intptr_t type_args_len,
|
|
intptr_t argument_count,
|
|
const Array& argument_names,
|
|
const InferredTypeMetadata* result_type) {
|
|
Fragment instructions = RecordCoverage(position);
|
|
const intptr_t total_count =
|
|
(type_args_len > 0 ? 1 : 0) + argument_count +
|
|
/*closure (bare instructions) or function (otherwise)*/ 1;
|
|
InputsArray arguments = GetArguments(total_count);
|
|
ClosureCallInstr* call = new (Z) ClosureCallInstr(
|
|
target_function, std::move(arguments), type_args_len, argument_names,
|
|
InstructionSource(position), GetNextDeoptId());
|
|
Push(call);
|
|
instructions <<= call;
|
|
if (result_type != nullptr && result_type->IsConstant()) {
|
|
instructions += Drop();
|
|
instructions += Constant(result_type->constant_value);
|
|
}
|
|
if (!target_function.IsNull()) {
|
|
const auto& return_type =
|
|
AbstractType::Handle(Z, target_function.result_type());
|
|
if (return_type.IsNeverType() && return_type.IsNonNullable()) {
|
|
instructions += Stop("unreachable after returning Never");
|
|
}
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
void BaseFlowGraphBuilder::reset_context_depth_for_deopt_id(intptr_t deopt_id) {
|
|
if (is_recording_context_levels()) {
|
|
for (intptr_t i = 0, n = context_level_array_->length(); i < n; i += 2) {
|
|
if (context_level_array_->At(i) == deopt_id) {
|
|
(*context_level_array_)[i + 1] = context_depth_;
|
|
return;
|
|
}
|
|
ASSERT(context_level_array_->At(i) < deopt_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::AssertAssignable(
|
|
TokenPosition position,
|
|
const String& dst_name,
|
|
AssertAssignableInstr::Kind kind) {
|
|
Value* function_type_args = Pop();
|
|
Value* instantiator_type_args = Pop();
|
|
Value* dst_type = Pop();
|
|
Value* value = Pop();
|
|
|
|
AssertAssignableInstr* instr = new (Z) AssertAssignableInstr(
|
|
InstructionSource(position), value, dst_type, instantiator_type_args,
|
|
function_type_args, dst_name, GetNextDeoptId(), kind);
|
|
Push(instr);
|
|
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::InitConstantParameters() {
|
|
Fragment instructions;
|
|
const intptr_t parameter_count = parsed_function_->function().NumParameters();
|
|
for (intptr_t i = 0; i < parameter_count; ++i) {
|
|
LocalVariable* raw_parameter = parsed_function_->RawParameterVariable(i);
|
|
const Object* param_value = raw_parameter->inferred_arg_value();
|
|
if (param_value != nullptr) {
|
|
instructions += Constant(*param_value);
|
|
instructions += StoreLocalRaw(TokenPosition::kNoSource, raw_parameter);
|
|
instructions += Drop();
|
|
}
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::InvokeMathCFunction(
|
|
MethodRecognizer::Kind recognized_kind,
|
|
intptr_t num_inputs) {
|
|
InputsArray args = GetArguments(num_inputs);
|
|
auto* instr = new (Z) InvokeMathCFunctionInstr(
|
|
std::move(args), GetNextDeoptId(), recognized_kind,
|
|
InstructionSource(TokenPosition::kNoSource));
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::DoubleToInteger(
|
|
MethodRecognizer::Kind recognized_kind) {
|
|
Value* value = Pop();
|
|
auto* instr =
|
|
new (Z) DoubleToIntegerInstr(value, recognized_kind, GetNextDeoptId());
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::UnaryDoubleOp(Token::Kind op) {
|
|
Value* value = Pop();
|
|
auto* instr = new (Z) UnaryDoubleOpInstr(op, value, GetNextDeoptId());
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::RecordCoverage(TokenPosition position) {
|
|
return RecordCoverageImpl(position, false /** is_branch_coverage **/);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::RecordBranchCoverage(TokenPosition position) {
|
|
return RecordCoverageImpl(position, true /** is_branch_coverage **/);
|
|
}
|
|
|
|
Fragment BaseFlowGraphBuilder::RecordCoverageImpl(TokenPosition position,
|
|
bool is_branch_coverage) {
|
|
Fragment instructions;
|
|
if (!SupportsCoverage()) return instructions;
|
|
if (!IG->coverage()) return instructions;
|
|
if (!position.IsReal()) return instructions;
|
|
if (is_branch_coverage && !IG->branch_coverage()) return instructions;
|
|
|
|
const intptr_t coverage_index =
|
|
GetCoverageIndexFor(position.EncodeCoveragePosition(is_branch_coverage));
|
|
instructions <<= new (Z) RecordCoverageInstr(coverage_array(), coverage_index,
|
|
InstructionSource(position));
|
|
return instructions;
|
|
}
|
|
|
|
intptr_t BaseFlowGraphBuilder::GetCoverageIndexFor(intptr_t encoded_position) {
|
|
if (coverage_array_.IsNull()) {
|
|
// We have not yet created coverage_array, this is the first time we are
|
|
// building the graph for this function. Collect coverage positions.
|
|
intptr_t value =
|
|
coverage_state_index_for_position_.Lookup(encoded_position);
|
|
if (value > 0) {
|
|
// Found.
|
|
return value;
|
|
}
|
|
// Not found: Insert.
|
|
const auto index = 2 * coverage_state_index_for_position_.Length() + 1;
|
|
coverage_state_index_for_position_.Insert(encoded_position, index);
|
|
return index;
|
|
}
|
|
|
|
if (coverage_state_index_for_position_.IsEmpty()) {
|
|
// coverage_array was already created, but we don't want to search
|
|
// it linearly: Fill in the coverage_state_index_for_position_ to do
|
|
// fast lookups.
|
|
// TODO(jensj): If Length is small enough it's probably better to just do
|
|
// the linear search.
|
|
for (intptr_t i = 0; i < coverage_array_.Length(); i += 2) {
|
|
intptr_t key = coverage_array_.GetUint32(i * kInt32Size);
|
|
intptr_t value = i + 1;
|
|
coverage_state_index_for_position_.Insert(key, value);
|
|
}
|
|
}
|
|
|
|
intptr_t value = coverage_state_index_for_position_.Lookup(encoded_position);
|
|
|
|
if (value > 0) {
|
|
// Found.
|
|
return value;
|
|
}
|
|
|
|
// Reaching here indicates that the graph is constructed in an unstable way.
|
|
UNREACHABLE();
|
|
return 1;
|
|
}
|
|
|
|
void BaseFlowGraphBuilder::FinalizeCoverageArray() {
|
|
if (!coverage_array_.IsNull()) {
|
|
return;
|
|
}
|
|
|
|
if (coverage_state_index_for_position_.IsEmpty()) {
|
|
coverage_array_ = TypedData::empty_coverage_array().ptr();
|
|
return;
|
|
}
|
|
|
|
coverage_array_ = TypedData::New(
|
|
kTypedDataUint32ArrayCid, coverage_state_index_for_position_.Length() * 2,
|
|
Heap::kOld);
|
|
|
|
auto it = coverage_state_index_for_position_.GetIterator();
|
|
for (auto* p = it.Next(); p != nullptr; p = it.Next()) {
|
|
intptr_t value = p->key;
|
|
// p->value is the index at which coverage state is stored, the
|
|
// full coverage entry begins at the previous index.
|
|
const intptr_t coverage_entry_byte_index = (p->value - 1) * kInt32Size;
|
|
coverage_array_.SetUint32(coverage_entry_byte_index, value);
|
|
// no coverage recorded.
|
|
const intptr_t coverage_state_byte_index = p->value * kInt32Size;
|
|
coverage_array_.SetUint32(coverage_state_byte_index, 0);
|
|
}
|
|
}
|
|
|
|
} // namespace kernel
|
|
} // namespace dart
|