| // 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 |