d36adbacaf
The former contents of the VM isolate are now included into each isolate group. This makes each isolate group's heap independent, and in particular allows each heap to be allocated to a separate pointer cage (not done in this CL). The duplicated stubs that allowed PC relative calls are removed, since the originals can now be the target of PC relative calls. The bootstrapping needing to load an AppJIT or AppAOT snapshot is reduced to allocating the oddballs. The code is entirely dropped in the AOT runtime, but the JIT runtime still has it to allow for flags to affect the compilation of the stub code. Further refactoring might be able to remove this for the JIT runtime too, with only gen_snapshot knowing how to bootstrap. Class serialization no longer distinguishes predefined classes. The page containing null is marked as never-evacuate. null, false and true must not move because the compiler relies on their low bits having certain patterns for some optimizations. (Previously, the entire VM isolate heap never moved.) Compaction is disabled for IA32. Due to register pressure, some stub calls must not use a scratch register and embed the address of Code. The page containing the call-through-safepoint stub is frozen when running with --write-protect-code and the stub is created at runtime (instead of loaded from an AppJIT or AppAOT snapshot). This stub must remain executable even during a safepoint, as a foreign call might during return during a safepoint and only block after the stub directs it to the runtime. The snapshot symbols are renamed to kDartSnapshotData and kDartSnapshotText. There is no need to distinguish the VM isolate's snapshot, and snaphots are per isolate group not per isolate. Aliases with the old names are added to ease migration. Some global flags that were automatically set based on the VM isolate's snapshot are now isolate group flags and automatically set by the isolate group's snapshot. TEST=ci Change-Id: Iee82016057d609112e9b021d178fc3d4d18b5044 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500621 Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Tess Strickland <sstrickl@google.com> SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
1440 lines
50 KiB
C++
1440 lines
50 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 = Smi::Value(static_cast<SmiPtr>(coverage_array_.At(i)));
|
|
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_ = Array::empty_array().ptr();
|
|
return;
|
|
}
|
|
|
|
coverage_array_ =
|
|
Array::New(coverage_state_index_for_position_.Length() * 2, Heap::kOld);
|
|
|
|
Smi& value = Smi::Handle();
|
|
auto it = coverage_state_index_for_position_.GetIterator();
|
|
for (auto* p = it.Next(); p != nullptr; p = it.Next()) {
|
|
value = Smi::New(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_index = p->value - 1;
|
|
coverage_array_.SetAt(coverage_entry_index, value);
|
|
value = Smi::New(0); // no coverage recorded.
|
|
coverage_array_.SetAt(p->value, value);
|
|
}
|
|
}
|
|
|
|
} // namespace kernel
|
|
} // namespace dart
|