| // Copyright (c) 2016, 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/aot/precompiler.h" |
| #include "vm/compiler/frontend/kernel_to_il.h" |
| |
| #include "vm/compiler/backend/il.h" |
| #include "vm/compiler/backend/il_printer.h" |
| #include "vm/compiler/frontend/kernel_binary_flowgraph.h" |
| #include "vm/compiler/frontend/kernel_translation_helper.h" |
| #include "vm/compiler/frontend/prologue_builder.h" |
| #include "vm/compiler/jit/compiler.h" |
| #include "vm/kernel_loader.h" |
| #include "vm/longjump.h" |
| #include "vm/object_store.h" |
| #include "vm/report.h" |
| #include "vm/resolver.h" |
| #include "vm/stack_frame.h" |
| |
| #if !defined(DART_PRECOMPILED_RUNTIME) |
| |
| namespace dart { |
| namespace kernel { |
| |
| #define Z (zone_) |
| #define H (translation_helper_) |
| #define T (type_translator_) |
| #define I Isolate::Current() |
| |
| FlowGraphBuilder::FlowGraphBuilder( |
| ParsedFunction* parsed_function, |
| ZoneGrowableArray<const ICData*>* ic_data_array, |
| ZoneGrowableArray<intptr_t>* context_level_array, |
| InlineExitCollector* exit_collector, |
| bool optimizing, |
| intptr_t osr_id, |
| intptr_t first_block_id, |
| bool inlining_unchecked_entry) |
| : BaseFlowGraphBuilder(parsed_function, |
| first_block_id - 1, |
| osr_id, |
| context_level_array, |
| exit_collector, |
| inlining_unchecked_entry), |
| translation_helper_(Thread::Current()), |
| thread_(translation_helper_.thread()), |
| zone_(translation_helper_.zone()), |
| parsed_function_(parsed_function), |
| optimizing_(optimizing), |
| ic_data_array_(*ic_data_array), |
| next_function_id_(0), |
| loop_depth_(0), |
| try_depth_(0), |
| catch_depth_(0), |
| for_in_depth_(0), |
| graph_entry_(NULL), |
| scopes_(NULL), |
| breakable_block_(NULL), |
| switch_block_(NULL), |
| try_catch_block_(NULL), |
| try_finally_block_(NULL), |
| catch_block_(NULL) { |
| const Script& script = |
| Script::Handle(Z, parsed_function->function().script()); |
| H.InitFromScript(script); |
| } |
| |
| FlowGraphBuilder::~FlowGraphBuilder() {} |
| |
| Fragment FlowGraphBuilder::EnterScope( |
| intptr_t kernel_offset, |
| const LocalScope** context_scope /* = nullptr */) { |
| Fragment instructions; |
| const LocalScope* scope = scopes_->scopes.Lookup(kernel_offset); |
| if (scope->num_context_variables() > 0) { |
| instructions += PushContext(scope); |
| instructions += Drop(); |
| } |
| if (context_scope != nullptr) { |
| *context_scope = scope; |
| } |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::ExitScope(intptr_t kernel_offset) { |
| Fragment instructions; |
| const intptr_t context_size = |
| scopes_->scopes.Lookup(kernel_offset)->num_context_variables(); |
| if (context_size > 0) { |
| instructions += PopContext(); |
| } |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::AdjustContextTo(int depth) { |
| ASSERT(depth <= context_depth_ && depth >= 0); |
| Fragment instructions; |
| if (depth < context_depth_) { |
| instructions += LoadContextAt(depth); |
| instructions += StoreLocal(TokenPosition::kNoSource, |
| parsed_function_->current_context_var()); |
| instructions += Drop(); |
| context_depth_ = depth; |
| } |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::PushContext(const LocalScope* scope) { |
| ASSERT(scope->num_context_variables() > 0); |
| Fragment instructions = AllocateContext(scope->context_variables()); |
| LocalVariable* context = MakeTemporary(); |
| instructions += LoadLocal(context); |
| instructions += LoadLocal(parsed_function_->current_context_var()); |
| instructions += |
| StoreInstanceField(TokenPosition::kNoSource, Slot::Context_parent()); |
| instructions += StoreLocal(TokenPosition::kNoSource, |
| parsed_function_->current_context_var()); |
| ++context_depth_; |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::PopContext() { |
| return AdjustContextTo(context_depth_ - 1); |
| } |
| |
| Fragment FlowGraphBuilder::LoadInstantiatorTypeArguments() { |
| // TODO(27590): We could use `active_class_->IsGeneric()`. |
| Fragment instructions; |
| if (scopes_->type_arguments_variable != NULL) { |
| #ifdef DEBUG |
| Function& function = |
| Function::Handle(Z, parsed_function_->function().raw()); |
| while (function.IsClosureFunction()) { |
| function = function.parent_function(); |
| } |
| ASSERT(function.IsFactory()); |
| #endif |
| instructions += LoadLocal(scopes_->type_arguments_variable); |
| } else if (scopes_->this_variable != NULL && |
| active_class_.ClassNumTypeArguments() > 0) { |
| ASSERT(!parsed_function_->function().IsFactory()); |
| instructions += LoadLocal(scopes_->this_variable); |
| instructions += LoadNativeField( |
| Slot::GetTypeArgumentsSlotFor(thread_, *active_class_.klass)); |
| } else { |
| instructions += NullConstant(); |
| } |
| return instructions; |
| } |
| |
| // This function is responsible for pushing a type arguments vector which |
| // contains all type arguments of enclosing functions prepended to the type |
| // arguments of the current function. |
| Fragment FlowGraphBuilder::LoadFunctionTypeArguments() { |
| Fragment instructions; |
| |
| const Function& function = parsed_function_->function(); |
| |
| if (function.IsGeneric() || function.HasGenericParent()) { |
| ASSERT(parsed_function_->function_type_arguments() != NULL); |
| instructions += LoadLocal(parsed_function_->function_type_arguments()); |
| } else { |
| instructions += NullConstant(); |
| } |
| |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::TranslateInstantiatedTypeArguments( |
| const TypeArguments& type_arguments) { |
| Fragment instructions; |
| |
| if (type_arguments.IsNull() || type_arguments.IsInstantiated()) { |
| // There are no type references to type parameters so we can just take it. |
| instructions += Constant(type_arguments); |
| } else { |
| // The [type_arguments] vector contains a type reference to a type |
| // parameter we need to resolve it. |
| if (type_arguments.CanShareInstantiatorTypeArguments( |
| *active_class_.klass)) { |
| // If the instantiator type arguments are just passed on, we don't need to |
| // resolve the type parameters. |
| // |
| // This is for example the case here: |
| // class Foo<T> { |
| // newList() => new List<T>(); |
| // } |
| // We just use the type argument vector from the [Foo] object and pass it |
| // directly to the `new List<T>()` factory constructor. |
| instructions += LoadInstantiatorTypeArguments(); |
| } else if (type_arguments.CanShareFunctionTypeArguments( |
| parsed_function_->function())) { |
| instructions += LoadFunctionTypeArguments(); |
| } else { |
| // Otherwise we need to resolve [TypeParameterType]s in the type |
| // expression based on the current instantiator type argument vector. |
| if (!type_arguments.IsInstantiated(kCurrentClass)) { |
| instructions += LoadInstantiatorTypeArguments(); |
| } else { |
| instructions += NullConstant(); |
| } |
| if (!type_arguments.IsInstantiated(kFunctions)) { |
| instructions += LoadFunctionTypeArguments(); |
| } else { |
| instructions += NullConstant(); |
| } |
| instructions += InstantiateTypeArguments(type_arguments); |
| } |
| } |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::AllocateObject(TokenPosition position, |
| const Class& klass, |
| intptr_t argument_count) { |
| ArgumentArray arguments = GetArguments(argument_count); |
| AllocateObjectInstr* allocate = |
| new (Z) AllocateObjectInstr(position, klass, arguments); |
| Push(allocate); |
| return Fragment(allocate); |
| } |
| |
| Fragment FlowGraphBuilder::AllocateObject(const Class& klass, |
| const Function& closure_function) { |
| ArgumentArray arguments = new (Z) ZoneGrowableArray<PushArgumentInstr*>(Z, 0); |
| AllocateObjectInstr* allocate = |
| new (Z) AllocateObjectInstr(TokenPosition::kNoSource, klass, arguments); |
| allocate->set_closure_function(closure_function); |
| Push(allocate); |
| return Fragment(allocate); |
| } |
| |
| Fragment FlowGraphBuilder::CatchBlockEntry(const Array& handler_types, |
| intptr_t handler_index, |
| bool needs_stacktrace, |
| bool is_synthesized) { |
| LocalVariable* exception_var = CurrentException(); |
| LocalVariable* stacktrace_var = CurrentStackTrace(); |
| LocalVariable* raw_exception_var = CurrentRawException(); |
| LocalVariable* raw_stacktrace_var = CurrentRawStackTrace(); |
| |
| CatchBlockEntryInstr* entry = new (Z) CatchBlockEntryInstr( |
| TokenPosition::kNoSource, // Token position of catch block. |
| is_synthesized, // whether catch block was synthesized by FE compiler |
| AllocateBlockId(), CurrentTryIndex(), graph_entry_, handler_types, |
| handler_index, needs_stacktrace, GetNextDeoptId(), exception_var, |
| stacktrace_var, raw_exception_var, raw_stacktrace_var); |
| graph_entry_->AddCatchEntry(entry); |
| |
| Fragment instructions(entry); |
| |
| // Auxiliary variables introduced by the try catch can be captured if we are |
| // inside a function with yield/resume points. In this case we first need |
| // to restore the context to match the context at entry into the closure. |
| const bool should_restore_closure_context = |
| CurrentException()->is_captured() || CurrentCatchContext()->is_captured(); |
| LocalVariable* context_variable = parsed_function_->current_context_var(); |
| if (should_restore_closure_context) { |
| ASSERT(parsed_function_->function().IsClosureFunction()); |
| LocalScope* scope = parsed_function_->node_sequence()->scope(); |
| |
| LocalVariable* closure_parameter = scope->VariableAt(0); |
| ASSERT(!closure_parameter->is_captured()); |
| instructions += LoadLocal(closure_parameter); |
| instructions += LoadNativeField(Slot::Closure_context()); |
| instructions += StoreLocal(TokenPosition::kNoSource, context_variable); |
| instructions += Drop(); |
| } |
| |
| if (exception_var->is_captured()) { |
| instructions += LoadLocal(context_variable); |
| instructions += LoadLocal(raw_exception_var); |
| instructions += StoreInstanceField( |
| TokenPosition::kNoSource, |
| Slot::GetContextVariableSlotFor(thread_, *exception_var)); |
| } |
| if (stacktrace_var->is_captured()) { |
| instructions += LoadLocal(context_variable); |
| instructions += LoadLocal(raw_stacktrace_var); |
| instructions += StoreInstanceField( |
| TokenPosition::kNoSource, |
| Slot::GetContextVariableSlotFor(thread_, *stacktrace_var)); |
| } |
| |
| // :saved_try_context_var can be captured in the context of |
| // of the closure, in this case CatchBlockEntryInstr restores |
| // :current_context_var to point to closure context in the |
| // same way as normal function prologue does. |
| // Update current context depth to reflect that. |
| const intptr_t saved_context_depth = context_depth_; |
| ASSERT(!CurrentCatchContext()->is_captured() || |
| CurrentCatchContext()->owner()->context_level() == 0); |
| context_depth_ = 0; |
| instructions += LoadLocal(CurrentCatchContext()); |
| instructions += StoreLocal(TokenPosition::kNoSource, |
| parsed_function_->current_context_var()); |
| instructions += Drop(); |
| context_depth_ = saved_context_depth; |
| |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::TryCatch(int try_handler_index) { |
| // The body of the try needs to have it's own block in order to get a new try |
| // index. |
| // |
| // => We therefore create a block for the body (fresh try index) and another |
| // join block (with current try index). |
| Fragment body; |
| JoinEntryInstr* entry = new (Z) |
| JoinEntryInstr(AllocateBlockId(), try_handler_index, GetNextDeoptId()); |
| body += LoadLocal(parsed_function_->current_context_var()); |
| body += StoreLocal(TokenPosition::kNoSource, CurrentCatchContext()); |
| body += Drop(); |
| body += Goto(entry); |
| return Fragment(body.entry, entry); |
| } |
| |
| Fragment FlowGraphBuilder::CheckStackOverflowInPrologue( |
| TokenPosition position) { |
| 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. |
| CheckStackOverflow(position, loop_depth_); |
| return Fragment(); |
| } |
| return CheckStackOverflow(position, loop_depth_); |
| } |
| |
| Fragment FlowGraphBuilder::CloneContext( |
| const GrowableArray<LocalVariable*>& context_variables) { |
| LocalVariable* context_variable = parsed_function_->current_context_var(); |
| |
| Fragment instructions = LoadLocal(context_variable); |
| |
| CloneContextInstr* clone_instruction = new (Z) CloneContextInstr( |
| TokenPosition::kNoSource, Pop(), context_variables, GetNextDeoptId()); |
| instructions <<= clone_instruction; |
| Push(clone_instruction); |
| |
| instructions += StoreLocal(TokenPosition::kNoSource, context_variable); |
| instructions += Drop(); |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::InstanceCall( |
| TokenPosition position, |
| const String& name, |
| Token::Kind kind, |
| intptr_t type_args_len, |
| intptr_t argument_count, |
| const Array& argument_names, |
| intptr_t checked_argument_count, |
| const Function& interface_target, |
| const InferredTypeMetadata* result_type, |
| bool use_unchecked_entry, |
| const CallSiteAttributesMetadata* call_site_attrs) { |
| const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0); |
| ArgumentArray arguments = GetArguments(total_count); |
| InstanceCallInstr* call = new (Z) |
| InstanceCallInstr(position, name, kind, arguments, type_args_len, |
| argument_names, checked_argument_count, ic_data_array_, |
| GetNextDeoptId(), interface_target); |
| if ((result_type != NULL) && !result_type->IsTrivial()) { |
| call->SetResultType(Z, result_type->ToCompileType(Z)); |
| } |
| if (use_unchecked_entry) { |
| call->set_entry_kind(Code::EntryKind::kUnchecked); |
| } |
| if (call_site_attrs != nullptr && call_site_attrs->receiver_type != nullptr && |
| call_site_attrs->receiver_type->IsInstantiated()) { |
| call->set_static_receiver_type(call_site_attrs->receiver_type); |
| } |
| Push(call); |
| return Fragment(call); |
| } |
| |
| Fragment FlowGraphBuilder::ClosureCall(TokenPosition position, |
| intptr_t type_args_len, |
| intptr_t argument_count, |
| const Array& argument_names, |
| bool is_statically_checked) { |
| Value* function = Pop(); |
| const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0); |
| ArgumentArray arguments = GetArguments(total_count); |
| ClosureCallInstr* call = new (Z) |
| ClosureCallInstr(function, arguments, type_args_len, argument_names, |
| position, GetNextDeoptId(), |
| is_statically_checked ? Code::EntryKind::kUnchecked |
| : Code::EntryKind::kNormal); |
| Push(call); |
| return Fragment(call); |
| } |
| |
| Fragment FlowGraphBuilder::RethrowException(TokenPosition position, |
| int catch_try_index) { |
| Fragment instructions; |
| instructions += Drop(); |
| instructions += Drop(); |
| instructions += Fragment(new (Z) ReThrowInstr(position, catch_try_index, |
| GetNextDeoptId())) |
| .closed(); |
| // Use it's side effect of leaving a constant on the stack (does not change |
| // the graph). |
| NullConstant(); |
| |
| pending_argument_count_ -= 2; |
| |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::LoadClassId() { |
| LoadClassIdInstr* load = new (Z) LoadClassIdInstr(Pop()); |
| Push(load); |
| return Fragment(load); |
| } |
| |
| Fragment FlowGraphBuilder::LoadLocal(LocalVariable* variable) { |
| if (variable->is_captured()) { |
| Fragment instructions; |
| instructions += LoadContextAt(variable->owner()->context_level()); |
| instructions += |
| LoadNativeField(Slot::GetContextVariableSlotFor(thread_, *variable)); |
| return instructions; |
| } else { |
| return BaseFlowGraphBuilder::LoadLocal(variable); |
| } |
| } |
| |
| Fragment FlowGraphBuilder::InitStaticField(const Field& field) { |
| InitStaticFieldInstr* init = new (Z) |
| InitStaticFieldInstr(Pop(), MayCloneField(field), GetNextDeoptId()); |
| return Fragment(init); |
| } |
| |
| Fragment FlowGraphBuilder::NativeCall(const String* name, |
| const Function* function) { |
| InlineBailout("kernel::FlowGraphBuilder::NativeCall"); |
| const intptr_t num_args = |
| function->NumParameters() + (function->IsGeneric() ? 1 : 0); |
| ArgumentArray arguments = GetArguments(num_args); |
| NativeCallInstr* call = |
| new (Z) NativeCallInstr(name, function, FLAG_link_natives_lazily, |
| function->end_token_pos(), arguments); |
| Push(call); |
| return Fragment(call); |
| } |
| |
| Fragment FlowGraphBuilder::Return(TokenPosition position, |
| bool omit_result_type_check /* = false */) { |
| Fragment instructions; |
| const Function& function = parsed_function_->function(); |
| |
| // Emit a type check of the return type in checked mode for all functions |
| // and in strong mode for native functions. |
| if (!omit_result_type_check && function.is_native()) { |
| const AbstractType& return_type = |
| AbstractType::Handle(Z, function.result_type()); |
| instructions += CheckAssignable(return_type, Symbols::FunctionResult()); |
| } |
| |
| if (NeedsDebugStepCheck(function, position)) { |
| instructions += DebugStepCheck(position); |
| } |
| |
| if (FLAG_causal_async_stacks && |
| (function.IsAsyncClosure() || function.IsAsyncGenClosure())) { |
| // We are returning from an asynchronous closure. Before we do that, be |
| // sure to clear the thread's asynchronous stack trace. |
| const Function& target = Function::ZoneHandle( |
| Z, I->object_store()->async_clear_thread_stack_trace()); |
| ASSERT(!target.IsNull()); |
| instructions += StaticCall(TokenPosition::kNoSource, target, |
| /* argument_count = */ 0, ICData::kStatic); |
| instructions += Drop(); |
| } |
| |
| instructions += BaseFlowGraphBuilder::Return(position); |
| |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::CheckNull(TokenPosition position, |
| LocalVariable* receiver, |
| const String& function_name, |
| bool clear_the_temp /* = true */) { |
| Fragment instructions = LoadLocal(receiver); |
| |
| CheckNullInstr* check_null = |
| new (Z) CheckNullInstr(Pop(), function_name, GetNextDeoptId(), position); |
| |
| instructions <<= check_null; |
| |
| if (clear_the_temp) { |
| // Null out receiver to make sure it is not saved into the frame before |
| // doing the call. |
| instructions += NullConstant(); |
| instructions += StoreLocal(TokenPosition::kNoSource, receiver); |
| instructions += Drop(); |
| } |
| |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::StaticCall(TokenPosition position, |
| const Function& target, |
| intptr_t argument_count, |
| ICData::RebindRule rebind_rule) { |
| return StaticCall(position, target, argument_count, Array::null_array(), |
| rebind_rule); |
| } |
| |
| static intptr_t GetResultCidOfListFactory(Zone* zone, |
| const Function& function, |
| intptr_t argument_count) { |
| if (!function.IsFactory()) { |
| return kDynamicCid; |
| } |
| |
| const Class& owner = Class::Handle(zone, function.Owner()); |
| if ((owner.library() != Library::CoreLibrary()) && |
| (owner.library() != Library::TypedDataLibrary())) { |
| return kDynamicCid; |
| } |
| |
| if ((owner.Name() == Symbols::List().raw()) && |
| (function.name() == Symbols::ListFactory().raw())) { |
| ASSERT(argument_count == 1 || argument_count == 2); |
| return (argument_count == 1) ? kGrowableObjectArrayCid : kArrayCid; |
| } |
| return FactoryRecognizer::ResultCid(function); |
| } |
| |
| void FlowGraphBuilder::SetResultTypeForStaticCall( |
| StaticCallInstr* call, |
| const Function& target, |
| intptr_t argument_count, |
| const InferredTypeMetadata* result_type) { |
| const intptr_t list_cid = |
| GetResultCidOfListFactory(Z, target, argument_count); |
| if (list_cid != kDynamicCid) { |
| ASSERT((result_type == NULL) || (result_type->cid == kDynamicCid) || |
| (result_type->cid == list_cid)); |
| call->SetResultType(Z, CompileType::FromCid(list_cid)); |
| call->set_is_known_list_constructor(true); |
| return; |
| } |
| if (target.has_pragma()) { |
| intptr_t recognized_cid = MethodRecognizer::ResultCidFromPragma(target); |
| if (recognized_cid != kDynamicCid) { |
| ASSERT((result_type == NULL) || (result_type->cid == kDynamicCid) || |
| (result_type->cid == recognized_cid)); |
| call->SetResultType(Z, CompileType::FromCid(recognized_cid)); |
| return; |
| } |
| } |
| if ((result_type != NULL) && !result_type->IsTrivial()) { |
| call->SetResultType(Z, result_type->ToCompileType(Z)); |
| } |
| } |
| |
| Fragment FlowGraphBuilder::StaticCall(TokenPosition position, |
| const Function& target, |
| intptr_t argument_count, |
| const Array& argument_names, |
| ICData::RebindRule rebind_rule, |
| const InferredTypeMetadata* result_type, |
| intptr_t type_args_count, |
| bool use_unchecked_entry) { |
| const intptr_t total_count = argument_count + (type_args_count > 0 ? 1 : 0); |
| ArgumentArray arguments = GetArguments(total_count); |
| StaticCallInstr* call = new (Z) |
| StaticCallInstr(position, target, type_args_count, argument_names, |
| arguments, ic_data_array_, GetNextDeoptId(), rebind_rule); |
| SetResultTypeForStaticCall(call, target, argument_count, result_type); |
| if (use_unchecked_entry) { |
| call->set_entry_kind(Code::EntryKind::kUnchecked); |
| } |
| Push(call); |
| return Fragment(call); |
| } |
| |
| Fragment FlowGraphBuilder::StringInterpolate(TokenPosition position) { |
| Value* array = Pop(); |
| StringInterpolateInstr* interpolate = |
| new (Z) StringInterpolateInstr(array, position, GetNextDeoptId()); |
| Push(interpolate); |
| return Fragment(interpolate); |
| } |
| |
| Fragment FlowGraphBuilder::StringInterpolateSingle(TokenPosition position) { |
| const int kTypeArgsLen = 0; |
| const int kNumberOfArguments = 1; |
| const Array& kNoArgumentNames = Object::null_array(); |
| const Class& cls = |
| Class::Handle(Library::LookupCoreClass(Symbols::StringBase())); |
| ASSERT(!cls.IsNull()); |
| const Function& function = Function::ZoneHandle( |
| Z, Resolver::ResolveStatic( |
| cls, Library::PrivateCoreLibName(Symbols::InterpolateSingle()), |
| kTypeArgsLen, kNumberOfArguments, kNoArgumentNames)); |
| Fragment instructions; |
| instructions += PushArgument(); |
| instructions += |
| StaticCall(position, function, /* argument_count = */ 1, ICData::kStatic); |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::ThrowTypeError() { |
| const Class& klass = |
| Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::TypeError())); |
| ASSERT(!klass.IsNull()); |
| GrowableHandlePtrArray<const String> pieces(Z, 3); |
| pieces.Add(Symbols::TypeError()); |
| pieces.Add(Symbols::Dot()); |
| pieces.Add(H.DartSymbolObfuscate("_create")); |
| |
| const Function& constructor = Function::ZoneHandle( |
| Z, klass.LookupConstructorAllowPrivate( |
| String::ZoneHandle(Z, Symbols::FromConcatAll(thread_, pieces)))); |
| ASSERT(!constructor.IsNull()); |
| |
| const String& url = H.DartString( |
| parsed_function_->function().ToLibNamePrefixedQualifiedCString(), |
| Heap::kOld); |
| |
| Fragment instructions; |
| |
| // Create instance of _FallThroughError |
| instructions += AllocateObject(TokenPosition::kNoSource, klass, 0); |
| LocalVariable* instance = MakeTemporary(); |
| |
| // Call _TypeError._create constructor. |
| instructions += LoadLocal(instance); |
| instructions += PushArgument(); // this |
| |
| instructions += Constant(url); |
| instructions += PushArgument(); // url |
| |
| instructions += NullConstant(); |
| instructions += PushArgument(); // line |
| |
| instructions += IntConstant(0); |
| instructions += PushArgument(); // column |
| |
| instructions += Constant(H.DartSymbolPlain("Malformed type.")); |
| instructions += PushArgument(); // message |
| |
| instructions += StaticCall(TokenPosition::kNoSource, constructor, |
| /* argument_count = */ 5, ICData::kStatic); |
| instructions += Drop(); |
| |
| // Throw the exception |
| instructions += PushArgument(); |
| instructions += ThrowException(TokenPosition::kNoSource); |
| |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::ThrowNoSuchMethodError() { |
| const Class& klass = Class::ZoneHandle( |
| Z, Library::LookupCoreClass(Symbols::NoSuchMethodError())); |
| ASSERT(!klass.IsNull()); |
| const Function& throw_function = Function::ZoneHandle( |
| Z, klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNew())); |
| ASSERT(!throw_function.IsNull()); |
| |
| Fragment instructions; |
| |
| // Call NoSuchMethodError._throwNew static function. |
| instructions += NullConstant(); |
| instructions += PushArgument(); // receiver |
| |
| instructions += Constant(H.DartString("<unknown>", Heap::kOld)); |
| instructions += PushArgument(); // memberName |
| |
| instructions += IntConstant(-1); |
| instructions += PushArgument(); // invocation_type |
| |
| instructions += NullConstant(); |
| instructions += PushArgument(); // type arguments |
| |
| instructions += NullConstant(); |
| instructions += PushArgument(); // arguments |
| |
| instructions += NullConstant(); |
| instructions += PushArgument(); // argumentNames |
| |
| instructions += StaticCall(TokenPosition::kNoSource, throw_function, |
| /* argument_count = */ 6, ICData::kStatic); |
| // Leave "result" on the stack since callers expect it to be there (even |
| // though the function will result in an exception). |
| |
| return instructions; |
| } |
| |
| LocalVariable* FlowGraphBuilder::LookupVariable(intptr_t kernel_offset) { |
| LocalVariable* local = scopes_->locals.Lookup(kernel_offset); |
| ASSERT(local != NULL); |
| return local; |
| } |
| |
| FlowGraph* FlowGraphBuilder::BuildGraph() { |
| const Function& function = parsed_function_->function(); |
| |
| #ifdef DEBUG |
| // If we attached the native name to the function after it's creation (namely |
| // after reading the constant table from the kernel blob), we must have done |
| // so before building flow graph for the functions (since FGB depends needs |
| // the native name to be there). |
| const Script& script = Script::Handle(Z, function.script()); |
| const KernelProgramInfo& info = |
| KernelProgramInfo::Handle(script.kernel_program_info()); |
| ASSERT(info.IsNull() || |
| info.potential_natives() == GrowableObjectArray::null()); |
| #endif |
| |
| StreamingFlowGraphBuilder streaming_flow_graph_builder( |
| this, ExternalTypedData::Handle(Z, function.KernelData()), |
| function.KernelDataProgramOffset()); |
| return streaming_flow_graph_builder.BuildGraph(); |
| } |
| |
| Fragment FlowGraphBuilder::NativeFunctionBody(const Function& function, |
| LocalVariable* first_parameter) { |
| ASSERT(function.is_native()); |
| // We explicitly build the graph for native functions in the same way that the |
| // from-source backend does. We should find a way to have a single component |
| // to build these graphs so that this code is not duplicated. |
| |
| Fragment body; |
| const MethodRecognizer::Kind kind = MethodRecognizer::RecognizeKind(function); |
| bool omit_result_type_check = true; |
| switch (kind) { |
| case MethodRecognizer::kObjectEquals: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadLocal(first_parameter); |
| body += StrictCompare(Token::kEQ_STRICT); |
| break; |
| case MethodRecognizer::kStringBaseLength: |
| case MethodRecognizer::kStringBaseIsEmpty: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadNativeField(Slot::String_length()); |
| if (kind == MethodRecognizer::kStringBaseIsEmpty) { |
| body += IntConstant(0); |
| body += StrictCompare(Token::kEQ_STRICT); |
| } |
| break; |
| case MethodRecognizer::kGrowableArrayLength: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadNativeField(Slot::GrowableObjectArray_length()); |
| break; |
| case MethodRecognizer::kObjectArrayLength: |
| case MethodRecognizer::kImmutableArrayLength: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadNativeField(Slot::Array_length()); |
| break; |
| case MethodRecognizer::kTypedDataLength: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadNativeField(Slot::TypedData_length()); |
| break; |
| case MethodRecognizer::kClassIDgetID: |
| body += LoadLocal(first_parameter); |
| body += LoadClassId(); |
| break; |
| case MethodRecognizer::kGrowableArrayCapacity: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadNativeField(Slot::GrowableObjectArray_data()); |
| body += LoadNativeField(Slot::Array_length()); |
| break; |
| case MethodRecognizer::kListFactory: { |
| // factory List<E>([int length]) { |
| // return (:arg_desc.positional_count == 2) ? new _List<E>(length) |
| // : new _GrowableList<E>(0); |
| // } |
| const Library& core_lib = Library::Handle(Z, Library::CoreLibrary()); |
| |
| TargetEntryInstr *allocate_non_growable, *allocate_growable; |
| |
| body += LoadArgDescriptor(); |
| body += LoadNativeField(Slot::ArgumentsDescriptor_positional_count()); |
| body += IntConstant(2); |
| body += BranchIfStrictEqual(&allocate_non_growable, &allocate_growable); |
| |
| JoinEntryInstr* join = BuildJoinEntry(); |
| |
| { |
| const Class& cls = Class::Handle( |
| Z, core_lib.LookupClass( |
| Library::PrivateCoreLibName(Symbols::_List()))); |
| ASSERT(!cls.IsNull()); |
| const Function& func = Function::ZoneHandle( |
| Z, cls.LookupFactoryAllowPrivate(Symbols::_ListFactory())); |
| ASSERT(!func.IsNull()); |
| |
| Fragment allocate(allocate_non_growable); |
| allocate += LoadLocal(scopes_->type_arguments_variable); |
| allocate += PushArgument(); |
| allocate += LoadLocal(first_parameter); |
| allocate += PushArgument(); |
| allocate += |
| StaticCall(TokenPosition::kNoSource, func, 2, ICData::kStatic); |
| allocate += StoreLocal(TokenPosition::kNoSource, |
| parsed_function_->expression_temp_var()); |
| allocate += Drop(); |
| allocate += Goto(join); |
| } |
| |
| { |
| const Class& cls = Class::Handle( |
| Z, core_lib.LookupClass( |
| Library::PrivateCoreLibName(Symbols::_GrowableList()))); |
| ASSERT(!cls.IsNull()); |
| const Function& func = Function::ZoneHandle( |
| Z, cls.LookupFactoryAllowPrivate(Symbols::_GrowableListFactory())); |
| ASSERT(!func.IsNull()); |
| |
| Fragment allocate(allocate_growable); |
| allocate += LoadLocal(scopes_->type_arguments_variable); |
| allocate += PushArgument(); |
| allocate += IntConstant(0); |
| allocate += PushArgument(); |
| allocate += |
| StaticCall(TokenPosition::kNoSource, func, 2, ICData::kStatic); |
| allocate += StoreLocal(TokenPosition::kNoSource, |
| parsed_function_->expression_temp_var()); |
| allocate += Drop(); |
| allocate += Goto(join); |
| } |
| |
| body = Fragment(body.entry, join); |
| body += LoadLocal(parsed_function_->expression_temp_var()); |
| break; |
| } |
| case MethodRecognizer::kObjectArrayAllocate: |
| body += LoadLocal(scopes_->type_arguments_variable); |
| body += LoadLocal(first_parameter); |
| body += CreateArray(); |
| break; |
| case MethodRecognizer::kLinkedHashMap_getIndex: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadNativeField(Slot::LinkedHashMap_index()); |
| break; |
| case MethodRecognizer::kLinkedHashMap_setIndex: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadLocal(first_parameter); |
| body += StoreInstanceField(TokenPosition::kNoSource, |
| Slot::LinkedHashMap_index()); |
| body += NullConstant(); |
| break; |
| case MethodRecognizer::kLinkedHashMap_getData: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadNativeField(Slot::LinkedHashMap_data()); |
| break; |
| case MethodRecognizer::kLinkedHashMap_setData: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadLocal(first_parameter); |
| body += StoreInstanceField(TokenPosition::kNoSource, |
| Slot::LinkedHashMap_data()); |
| body += NullConstant(); |
| break; |
| case MethodRecognizer::kLinkedHashMap_getHashMask: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadNativeField(Slot::LinkedHashMap_hash_mask()); |
| break; |
| case MethodRecognizer::kLinkedHashMap_setHashMask: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadLocal(first_parameter); |
| body += |
| StoreInstanceField(TokenPosition::kNoSource, |
| Slot::LinkedHashMap_hash_mask(), kNoStoreBarrier); |
| body += NullConstant(); |
| break; |
| case MethodRecognizer::kLinkedHashMap_getUsedData: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadNativeField(Slot::LinkedHashMap_used_data()); |
| break; |
| case MethodRecognizer::kLinkedHashMap_setUsedData: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadLocal(first_parameter); |
| body += |
| StoreInstanceField(TokenPosition::kNoSource, |
| Slot::LinkedHashMap_used_data(), kNoStoreBarrier); |
| body += NullConstant(); |
| break; |
| case MethodRecognizer::kLinkedHashMap_getDeletedKeys: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadNativeField(Slot::LinkedHashMap_deleted_keys()); |
| break; |
| case MethodRecognizer::kLinkedHashMap_setDeletedKeys: |
| body += LoadLocal(scopes_->this_variable); |
| body += LoadLocal(first_parameter); |
| body += StoreInstanceField(TokenPosition::kNoSource, |
| Slot::LinkedHashMap_deleted_keys(), |
| kNoStoreBarrier); |
| body += NullConstant(); |
| break; |
| default: { |
| String& name = String::ZoneHandle(Z, function.native_name()); |
| if (function.IsGeneric()) { |
| body += LoadLocal(parsed_function_->RawTypeArgumentsVariable()); |
| body += PushArgument(); |
| } |
| for (intptr_t i = 0; i < function.NumParameters(); ++i) { |
| body += LoadLocal(parsed_function_->RawParameterVariable(i)); |
| body += PushArgument(); |
| } |
| body += NativeCall(&name, &function); |
| // We typecheck results of native calls for type safety. |
| omit_result_type_check = false; |
| break; |
| } |
| } |
| return body + Return(TokenPosition::kNoSource, omit_result_type_check); |
| } |
| |
| static const LocalScope* MakeImplicitClosureScope(Zone* Z, |
| const Function& function) { |
| Class& klass = Class::Handle(Z, function.Owner()); |
| ASSERT(!klass.IsNull()); |
| // Note that if klass is _Closure, DeclarationType will be _Closure, |
| // and not the signature type. |
| Type& klass_type = Type::ZoneHandle(Z, klass.DeclarationType()); |
| |
| LocalVariable* this_variable = new (Z) |
| LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource, |
| Symbols::This(), klass_type, /*param_type=*/nullptr); |
| |
| this_variable->set_is_captured(); |
| // this_variable->set_is_final(); |
| LocalScope* scope = new (Z) LocalScope(NULL, 0, 0); |
| scope->set_context_level(0); |
| scope->AddVariable(this_variable); |
| scope->AddContextVariable(this_variable); |
| return scope; |
| } |
| |
| Fragment FlowGraphBuilder::BuildImplicitClosureCreation( |
| const Function& target) { |
| Fragment fragment; |
| const Class& closure_class = |
| Class::ZoneHandle(Z, I->object_store()->closure_class()); |
| fragment += AllocateObject(closure_class, target); |
| LocalVariable* closure = MakeTemporary(); |
| |
| // The function signature can have uninstantiated class type parameters. |
| if (!target.HasInstantiatedSignature(kCurrentClass)) { |
| fragment += LoadLocal(closure); |
| fragment += LoadInstantiatorTypeArguments(); |
| fragment += StoreInstanceField(TokenPosition::kNoSource, |
| Slot::Closure_instantiator_type_arguments()); |
| } |
| |
| // The function signature cannot have uninstantiated function type parameters, |
| // because the function cannot be local and have parent generic functions. |
| ASSERT(target.HasInstantiatedSignature(kFunctions)); |
| |
| // Allocate a context that closes over `this`. |
| // Note: this must be kept in sync with ScopeBuilder::BuildScopes. |
| const LocalScope* implicit_closure_scope = |
| MakeImplicitClosureScope(Z, target); |
| fragment += AllocateContext(implicit_closure_scope->context_variables()); |
| LocalVariable* context = MakeTemporary(); |
| |
| // Store the function and the context in the closure. |
| fragment += LoadLocal(closure); |
| fragment += Constant(target); |
| fragment += |
| StoreInstanceField(TokenPosition::kNoSource, Slot::Closure_function()); |
| |
| fragment += LoadLocal(closure); |
| fragment += LoadLocal(context); |
| fragment += |
| StoreInstanceField(TokenPosition::kNoSource, Slot::Closure_context()); |
| |
| fragment += LoadLocal(closure); |
| fragment += Constant(Object::empty_type_arguments()); |
| fragment += StoreInstanceField(TokenPosition::kNoSource, |
| Slot::Closure_delayed_type_arguments()); |
| |
| // The context is on top of the operand stack. Store `this`. The context |
| // doesn't need a parent pointer because it doesn't close over anything |
| // else. |
| fragment += LoadLocal(scopes_->this_variable); |
| fragment += StoreInstanceField( |
| TokenPosition::kNoSource, |
| Slot::GetContextVariableSlotFor( |
| thread_, *implicit_closure_scope->context_variables()[0])); |
| |
| return fragment; |
| } |
| |
| Fragment FlowGraphBuilder::CheckVariableTypeInCheckedMode( |
| const AbstractType& dst_type, |
| const String& name_symbol) { |
| return Fragment(); |
| } |
| |
| bool FlowGraphBuilder::NeedsDebugStepCheck(const Function& function, |
| TokenPosition position) { |
| return position.IsDebugPause() && !function.is_native() && |
| function.is_debuggable(); |
| } |
| |
| bool FlowGraphBuilder::NeedsDebugStepCheck(Value* value, |
| TokenPosition position) { |
| if (!position.IsDebugPause()) { |
| return false; |
| } |
| Definition* definition = value->definition(); |
| if (definition->IsConstant() || definition->IsLoadStaticField()) { |
| return true; |
| } |
| if (definition->IsAllocateObject()) { |
| return !definition->AsAllocateObject()->closure_function().IsNull(); |
| } |
| return definition->IsLoadLocal() && |
| !definition->AsLoadLocal()->local().IsInternal(); |
| } |
| |
| Fragment FlowGraphBuilder::DebugStepCheck(TokenPosition position) { |
| return Fragment(new (Z) DebugStepCheckInstr( |
| position, RawPcDescriptors::kRuntimeCall, GetNextDeoptId())); |
| } |
| |
| Fragment FlowGraphBuilder::EvaluateAssertion() { |
| const Class& klass = |
| Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::AssertionError())); |
| ASSERT(!klass.IsNull()); |
| const Function& target = Function::ZoneHandle( |
| Z, klass.LookupStaticFunctionAllowPrivate(Symbols::EvaluateAssertion())); |
| ASSERT(!target.IsNull()); |
| return StaticCall(TokenPosition::kNoSource, target, /* argument_count = */ 1, |
| ICData::kStatic); |
| } |
| |
| Fragment FlowGraphBuilder::CheckBoolean(TokenPosition position) { |
| Fragment instructions; |
| LocalVariable* top_of_stack = MakeTemporary(); |
| instructions += LoadLocal(top_of_stack); |
| instructions += AssertBool(position); |
| instructions += Drop(); |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::CheckAssignable(const AbstractType& dst_type, |
| const String& dst_name, |
| AssertAssignableInstr::Kind kind) { |
| Fragment instructions; |
| if (!I->should_emit_strong_mode_checks()) { |
| return Fragment(); |
| } |
| if (!dst_type.IsDynamicType() && !dst_type.IsObjectType() && |
| !dst_type.IsVoidType()) { |
| LocalVariable* top_of_stack = MakeTemporary(); |
| instructions += LoadLocal(top_of_stack); |
| instructions += |
| AssertAssignable(TokenPosition::kNoSource, dst_type, dst_name, kind); |
| instructions += Drop(); |
| } |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::AssertAssignable(TokenPosition position, |
| const AbstractType& dst_type, |
| const String& dst_name, |
| AssertAssignableInstr::Kind kind) { |
| if (!I->should_emit_strong_mode_checks()) { |
| return Fragment(); |
| } |
| |
| Fragment instructions; |
| Value* value = Pop(); |
| |
| if (!dst_type.IsInstantiated(kCurrentClass)) { |
| instructions += LoadInstantiatorTypeArguments(); |
| } else { |
| instructions += NullConstant(); |
| } |
| Value* instantiator_type_args = Pop(); |
| |
| if (!dst_type.IsInstantiated(kFunctions)) { |
| instructions += LoadFunctionTypeArguments(); |
| } else { |
| instructions += NullConstant(); |
| } |
| Value* function_type_args = Pop(); |
| |
| AssertAssignableInstr* instr = new (Z) AssertAssignableInstr( |
| position, value, instantiator_type_args, function_type_args, dst_type, |
| dst_name, GetNextDeoptId(), kind); |
| Push(instr); |
| |
| instructions += Fragment(instr); |
| |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::AssertSubtype(TokenPosition position, |
| const AbstractType& sub_type, |
| const AbstractType& super_type, |
| const String& dst_name) { |
| Fragment instructions; |
| |
| instructions += LoadInstantiatorTypeArguments(); |
| Value* instantiator_type_args = Pop(); |
| instructions += LoadFunctionTypeArguments(); |
| Value* function_type_args = Pop(); |
| |
| AssertSubtypeInstr* instr = new (Z) |
| AssertSubtypeInstr(position, instantiator_type_args, function_type_args, |
| sub_type, super_type, dst_name, GetNextDeoptId()); |
| instructions += Fragment(instr); |
| |
| return instructions; |
| } |
| |
| void FlowGraphBuilder::BuildArgumentTypeChecks( |
| TypeChecksToBuild mode, |
| Fragment* explicit_checks, |
| Fragment* implicit_checks, |
| Fragment* implicit_redefinitions) { |
| if (!I->should_emit_strong_mode_checks()) return; |
| const Function& dart_function = parsed_function_->function(); |
| |
| const Function* forwarding_target = nullptr; |
| if (parsed_function_->is_forwarding_stub()) { |
| forwarding_target = parsed_function_->forwarding_stub_super_target(); |
| ASSERT(!forwarding_target->IsNull()); |
| } |
| |
| TypeArguments& type_parameters = TypeArguments::Handle(Z); |
| if (dart_function.IsFactory()) { |
| type_parameters = Class::Handle(Z, dart_function.Owner()).type_parameters(); |
| } else { |
| type_parameters = dart_function.type_parameters(); |
| } |
| intptr_t num_type_params = type_parameters.Length(); |
| if (forwarding_target != nullptr) { |
| type_parameters = forwarding_target->type_parameters(); |
| ASSERT(type_parameters.Length() == num_type_params); |
| } |
| |
| TypeParameter& type_param = TypeParameter::Handle(Z); |
| String& name = String::Handle(Z); |
| AbstractType& bound = AbstractType::Handle(Z); |
| Fragment check_bounds; |
| for (intptr_t i = 0; i < num_type_params; ++i) { |
| type_param ^= type_parameters.TypeAt(i); |
| |
| bound = type_param.bound(); |
| if (bound.IsTopType()) { |
| continue; |
| } |
| |
| switch (mode) { |
| case TypeChecksToBuild::kCheckAllTypeParameterBounds: |
| break; |
| case TypeChecksToBuild::kCheckCovariantTypeParameterBounds: |
| if (!type_param.IsGenericCovariantImpl()) { |
| continue; |
| } |
| break; |
| case TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds: |
| if (type_param.IsGenericCovariantImpl()) { |
| continue; |
| } |
| break; |
| } |
| |
| name = type_param.name(); |
| |
| ASSERT(type_param.IsFinalized()); |
| check_bounds += |
| AssertSubtype(TokenPosition::kNoSource, type_param, bound, name); |
| } |
| |
| // Type arguments passed through partial instantiation are guaranteed to be |
| // bounds-checked at the point of partial instantiation, so we don't need to |
| // check them again at the call-site. |
| if (dart_function.IsClosureFunction() && !check_bounds.is_empty() && |
| FLAG_eliminate_type_checks) { |
| LocalVariable* closure = |
| parsed_function_->node_sequence()->scope()->VariableAt(0); |
| *implicit_checks += TestDelayedTypeArgs(closure, /*present=*/{}, |
| /*absent=*/check_bounds); |
| } else { |
| *implicit_checks += check_bounds; |
| } |
| |
| const intptr_t num_params = dart_function.NumParameters(); |
| for (intptr_t i = dart_function.NumImplicitParameters(); i < num_params; |
| ++i) { |
| LocalVariable* param = |
| parsed_function_->node_sequence()->scope()->VariableAt(i); |
| if (!param->needs_type_check()) { |
| continue; |
| } |
| |
| const AbstractType* target_type = ¶m->type(); |
| if (forwarding_target != NULL) { |
| // We add 1 to the parameter index to account for the receiver. |
| target_type = |
| &AbstractType::ZoneHandle(Z, forwarding_target->ParameterTypeAt(i)); |
| } |
| |
| if (target_type->IsTopType()) continue; |
| |
| const bool is_covariant = param->is_explicit_covariant_parameter(); |
| Fragment* checks = is_covariant ? explicit_checks : implicit_checks; |
| |
| *checks += LoadLocal(param); |
| *checks += CheckAssignable(*target_type, param->name(), |
| AssertAssignableInstr::kParameterCheck); |
| *checks += Drop(); |
| |
| if (!is_covariant && implicit_redefinitions != nullptr && optimizing_) { |
| // We generate slightly different code in optimized vs. un-optimized code, |
| // which is ok since we don't allocate any deopt ids. |
| AssertNoDeoptIdsAllocatedScope no_deopt_allocation(thread_); |
| |
| *implicit_redefinitions += LoadLocal(param); |
| *implicit_redefinitions += RedefinitionWithType(*target_type); |
| *implicit_redefinitions += StoreLocal(TokenPosition::kNoSource, param); |
| *implicit_redefinitions += Drop(); |
| } |
| } |
| } |
| |
| BlockEntryInstr* FlowGraphBuilder::BuildPrologue(BlockEntryInstr* normal_entry, |
| PrologueInfo* prologue_info) { |
| const bool compiling_for_osr = IsCompiledForOsr(); |
| |
| kernel::PrologueBuilder prologue_builder( |
| parsed_function_, last_used_block_id_, compiling_for_osr, IsInlining()); |
| BlockEntryInstr* instruction_cursor = |
| prologue_builder.BuildPrologue(normal_entry, prologue_info); |
| |
| last_used_block_id_ = prologue_builder.last_used_block_id(); |
| |
| return instruction_cursor; |
| } |
| |
| FlowGraph* FlowGraphBuilder::BuildGraphOfMethodExtractor( |
| const Function& method) { |
| // A method extractor is the implicit getter for a method. |
| const Function& function = |
| Function::ZoneHandle(Z, method.extracted_method_closure()); |
| |
| graph_entry_ = |
| new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId); |
| |
| auto normal_entry = BuildFunctionEntry(graph_entry_); |
| graph_entry_->set_normal_entry(normal_entry); |
| |
| Fragment body(normal_entry); |
| body += CheckStackOverflowInPrologue(method.token_pos()); |
| body += BuildImplicitClosureCreation(function); |
| body += Return(TokenPosition::kNoSource); |
| |
| // There is no prologue code for a method extractor. |
| PrologueInfo prologue_info(-1, -1); |
| return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, |
| prologue_info); |
| } |
| |
| FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodDispatcher( |
| const Function& function) { |
| // This function is specialized for a receiver class, a method name, and |
| // the arguments descriptor at a call site. |
| |
| graph_entry_ = |
| new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId); |
| |
| auto normal_entry = BuildFunctionEntry(graph_entry_); |
| graph_entry_->set_normal_entry(normal_entry); |
| |
| PrologueInfo prologue_info(-1, -1); |
| BlockEntryInstr* instruction_cursor = |
| BuildPrologue(normal_entry, &prologue_info); |
| |
| // The backend will expect an array of default values for all the named |
| // parameters, even if they are all known to be passed at the call site |
| // because the call site matches the arguments descriptor. Use null for |
| // the default values. |
| const Array& descriptor_array = |
| Array::ZoneHandle(Z, function.saved_args_desc()); |
| ArgumentsDescriptor descriptor(descriptor_array); |
| ZoneGrowableArray<const Instance*>* default_values = |
| new ZoneGrowableArray<const Instance*>(Z, descriptor.NamedCount()); |
| for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) { |
| default_values->Add(&Object::null_instance()); |
| } |
| parsed_function_->set_default_parameter_values(default_values); |
| |
| Fragment body(instruction_cursor); |
| body += CheckStackOverflowInPrologue(function.token_pos()); |
| |
| // The receiver is the first argument to noSuchMethod, and it is the first |
| // argument passed to the dispatcher function. |
| LocalScope* scope = parsed_function_->node_sequence()->scope(); |
| body += LoadLocal(scope->VariableAt(0)); |
| body += PushArgument(); |
| |
| // The second argument to noSuchMethod is an invocation mirror. Push the |
| // arguments for allocating the invocation mirror. First, the name. |
| body += Constant(String::ZoneHandle(Z, function.name())); |
| body += PushArgument(); |
| |
| // Second, the arguments descriptor. |
| body += Constant(descriptor_array); |
| body += PushArgument(); |
| |
| // Third, an array containing the original arguments. Create it and fill |
| // it in. |
| const intptr_t receiver_index = descriptor.TypeArgsLen() > 0 ? 1 : 0; |
| body += Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null())); |
| body += IntConstant(receiver_index + descriptor.Count()); |
| body += CreateArray(); |
| LocalVariable* array = MakeTemporary(); |
| if (receiver_index > 0) { |
| LocalVariable* type_args = parsed_function_->function_type_arguments(); |
| ASSERT(type_args != NULL); |
| body += LoadLocal(array); |
| body += IntConstant(0); |
| body += LoadLocal(type_args); |
| body += StoreIndexed(kArrayCid); |
| } |
| for (intptr_t i = 0; i < descriptor.PositionalCount(); ++i) { |
| body += LoadLocal(array); |
| body += IntConstant(receiver_index + i); |
| body += LoadLocal(scope->VariableAt(i)); |
| body += StoreIndexed(kArrayCid); |
| } |
| String& name = String::Handle(Z); |
| for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) { |
| intptr_t parameter_index = descriptor.PositionalCount() + i; |
| name = descriptor.NameAt(i); |
| name = Symbols::New(H.thread(), name); |
| body += LoadLocal(array); |
| body += IntConstant(receiver_index + descriptor.PositionAt(i)); |
| body += LoadLocal(scope->VariableAt(parameter_index)); |
| body += StoreIndexed(kArrayCid); |
| } |
| body += PushArgument(); |
| |
| // Fourth, false indicating this is not a super NoSuchMethod. |
| body += Constant(Bool::False()); |
| body += PushArgument(); |
| |
| const Class& mirror_class = |
| Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror())); |
| ASSERT(!mirror_class.IsNull()); |
| const Function& allocation_function = Function::ZoneHandle( |
| Z, mirror_class.LookupStaticFunction( |
| Library::PrivateCoreLibName(Symbols::AllocateInvocationMirror()))); |
| ASSERT(!allocation_function.IsNull()); |
| body += StaticCall(TokenPosition::kMinSource, allocation_function, |
| /* argument_count = */ 4, ICData::kStatic); |
| body += PushArgument(); // For the call to noSuchMethod. |
| |
| const int kTypeArgsLen = 0; |
| ArgumentsDescriptor two_arguments( |
| Array::Handle(Z, ArgumentsDescriptor::New(kTypeArgsLen, 2))); |
| Function& no_such_method = |
| Function::ZoneHandle(Z, Resolver::ResolveDynamicForReceiverClass( |
| Class::Handle(Z, function.Owner()), |
| Symbols::NoSuchMethod(), two_arguments)); |
| if (no_such_method.IsNull()) { |
| // If noSuchMethod is not found on the receiver class, call |
| // Object.noSuchMethod. |
| no_such_method = Resolver::ResolveDynamicForReceiverClass( |
| Class::Handle(Z, I->object_store()->object_class()), |
| Symbols::NoSuchMethod(), two_arguments); |
| } |
| body += StaticCall(TokenPosition::kMinSource, no_such_method, |
| /* argument_count = */ 2, ICData::kNSMDispatch); |
| body += Return(TokenPosition::kNoSource); |
| |
| return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, |
| prologue_info); |
| } |
| |
| FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher( |
| const Function& function) { |
| // Find the name of the field we should dispatch to. |
| const Class& owner = Class::Handle(Z, function.Owner()); |
| ASSERT(!owner.IsNull()); |
| const String& field_name = String::Handle(Z, function.name()); |
| const String& getter_name = String::ZoneHandle( |
| Z, Symbols::New(thread_, |
| String::Handle(Z, Field::GetterSymbol(field_name)))); |
| |
| // Determine if this is `class Closure { get call => this; }` |
| const Class& closure_class = |
| Class::Handle(Z, I->object_store()->closure_class()); |
| const bool is_closure_call = (owner.raw() == closure_class.raw()) && |
| field_name.Equals(Symbols::Call()); |
| |
| // Set default parameters & construct argument names array. |
| // |
| // The backend will expect an array of default values for all the named |
| // parameters, even if they are all known to be passed at the call site |
| // because the call site matches the arguments descriptor. Use null for |
| // the default values. |
| const Array& descriptor_array = |
| Array::ZoneHandle(Z, function.saved_args_desc()); |
| ArgumentsDescriptor descriptor(descriptor_array); |
| const Array& argument_names = |
| Array::ZoneHandle(Z, Array::New(descriptor.NamedCount(), Heap::kOld)); |
| ZoneGrowableArray<const Instance*>* default_values = |
| new ZoneGrowableArray<const Instance*>(Z, descriptor.NamedCount()); |
| String& string_handle = String::Handle(Z); |
| for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) { |
| default_values->Add(&Object::null_instance()); |
| string_handle = descriptor.NameAt(i); |
| argument_names.SetAt(i, string_handle); |
| } |
| parsed_function_->set_default_parameter_values(default_values); |
| |
| graph_entry_ = |
| new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId); |
| |
| auto normal_entry = BuildFunctionEntry(graph_entry_); |
| graph_entry_->set_normal_entry(normal_entry); |
| |
| PrologueInfo prologue_info(-1, -1); |
| BlockEntryInstr* instruction_cursor = |
| BuildPrologue(normal_entry, &prologue_info); |
| |
| Fragment body(instruction_cursor); |
| body += CheckStackOverflowInPrologue(function.token_pos()); |
| |
| LocalScope* scope = parsed_function_->node_sequence()->scope(); |
| |
| if (descriptor.TypeArgsLen() > 0) { |
| LocalVariable* type_args = parsed_function_->function_type_arguments(); |
| ASSERT(type_args != NULL); |
| body += LoadLocal(type_args); |
| body += PushArgument(); |
| } |
| |
| LocalVariable* closure = NULL; |
| if (is_closure_call) { |
| closure = scope->VariableAt(0); |
| |
| // The closure itself is the first argument. |
| body += LoadLocal(closure); |
| } else { |
| // Invoke the getter to get the field value. |
| body += LoadLocal(scope->VariableAt(0)); |
| body += PushArgument(); |
| const intptr_t kTypeArgsLen = 0; |
| const intptr_t kNumArgsChecked = 1; |
| body += InstanceCall(TokenPosition::kMinSource, getter_name, Token::kGET, |
| kTypeArgsLen, 1, Array::null_array(), kNumArgsChecked, |
| Function::null_function()); |
| } |
| |
| body += PushArgument(); |
| |
| // Push all arguments onto the stack. |
| intptr_t pos = 1; |
| for (; pos < descriptor.Count(); pos++) { |
| body += LoadLocal(scope->VariableAt(pos)); |
| body += PushArgument(); |
| } |
| |
| if (is_closure_call) { |
| // Lookup the function in the closure. |
| body += LoadLocal(closure); |
| body += LoadNativeField(Slot::Closure_function()); |
| |
| body += ClosureCall(TokenPosition::kNoSource, descriptor.TypeArgsLen(), |
| descriptor.Count(), argument_names); |
| } else { |
| const intptr_t kNumArgsChecked = 1; |
| body += InstanceCall(TokenPosition::kMinSource, Symbols::Call(), |
| Token::kILLEGAL, descriptor.TypeArgsLen(), |
| descriptor.Count(), argument_names, kNumArgsChecked, |
| Function::null_function()); |
| } |
| |
| body += Return(TokenPosition::kNoSource); |
| |
| return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, |
| prologue_info); |
| } |
| |
| FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodForwarder( |
| const Function& function, |
| bool is_implicit_closure_function, |
| bool throw_no_such_method_error) { |
| graph_entry_ = |
| new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId); |
| |
| auto normal_entry = BuildFunctionEntry(graph_entry_); |
| graph_entry_->set_normal_entry(normal_entry); |
| |
| PrologueInfo prologue_info(-1, -1); |
| BlockEntryInstr* instruction_cursor = |
| BuildPrologue(normal_entry, &prologue_info); |
| |
| Fragment body(instruction_cursor); |
| body += CheckStackOverflowInPrologue(function.token_pos()); |
| |
| // If we are inside the tearoff wrapper function (implicit closure), we need |
| // to extract the receiver from the context. We just replace it directly on |
| // the stack to simplify the rest of the code. |
| if (is_implicit_closure_function && !function.is_static()) { |
| if (parsed_function_->has_arg_desc_var()) { |
| body += LoadArgDescriptor(); |
| body += LoadNativeField(Slot::ArgumentsDescriptor_count()); |
| body += LoadLocal(parsed_function_->current_context_var()); |
| body += LoadNativeField( |
| Slot::GetContextVariableSlotFor(thread_, *scopes_->this_variable)); |
| body += StoreFpRelativeSlot( |
| kWordSize * compiler::target::frame_layout.param_end_from_fp); |
| } else { |
| body += LoadLocal(parsed_function_->current_context_var()); |
| body += LoadNativeField( |
| Slot::GetContextVariableSlotFor(thread_, *scopes_->this_variable)); |
| body += StoreFpRelativeSlot( |
| kWordSize * (compiler::target::frame_layout.param_end_from_fp + |
| function.NumParameters())); |
| } |
| } |
| |
| if (function.NeedsArgumentTypeChecks(I)) { |
| BuildArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds, |
| &body, &body, nullptr); |
| } |
| |
| body += MakeTemp(); |
| LocalVariable* result = MakeTemporary(); |
| |
| // Do "++argument_count" if any type arguments were passed. |
| LocalVariable* argument_count_var = parsed_function_->expression_temp_var(); |
| body += IntConstant(0); |
| body += StoreLocal(TokenPosition::kNoSource, argument_count_var); |
| body += Drop(); |
| if (function.IsGeneric()) { |
| Fragment then; |
| Fragment otherwise; |
| otherwise += IntConstant(1); |
| otherwise += StoreLocal(TokenPosition::kNoSource, argument_count_var); |
| otherwise += Drop(); |
| body += TestAnyTypeArgs(then, otherwise); |
| } |
| |
| if (function.HasOptionalParameters()) { |
| body += LoadArgDescriptor(); |
| body += LoadNativeField(Slot::ArgumentsDescriptor_count()); |
| } else { |
| body += IntConstant(function.NumParameters()); |
| } |
| body += LoadLocal(argument_count_var); |
| body += SmiBinaryOp(Token::kADD, /* truncate= */ true); |
| LocalVariable* argument_count = MakeTemporary(); |
| |
| // We are generating code like the following: |
| // |
| // var arguments = new Array<dynamic>(argument_count); |
| // |
| // int i = 0; |
| // if (any type arguments are passed) { |
| // arguments[0] = function_type_arguments; |
| // ++i; |
| // } |
| // |
| // for (; i < argument_count; ++i) { |
| // arguments[i] = LoadFpRelativeSlot( |
| // kWordSize * (frame_layout.param_end_from_fp + argument_count - i)); |
| // } |
| body += Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null())); |
| body += LoadLocal(argument_count); |
| body += CreateArray(); |
| LocalVariable* arguments = MakeTemporary(); |
| |
| { |
| // int i = 0 |
| LocalVariable* index = parsed_function_->expression_temp_var(); |
| body += IntConstant(0); |
| body += StoreLocal(TokenPosition::kNoSource, index); |
| body += Drop(); |
| |
| // if (any type arguments are passed) { |
| // arguments[0] = function_type_arguments; |
| // i = 1; |
| // } |
| if (function.IsGeneric()) { |
| Fragment store; |
| store += LoadLocal(arguments); |
| store += IntConstant(0); |
| store += LoadFunctionTypeArguments(); |
| store += StoreIndexed(kArrayCid); |
| store += IntConstant(1); |
| store += StoreLocal(TokenPosition::kNoSource, index); |
| store += Drop(); |
| body += TestAnyTypeArgs(store, Fragment()); |
| } |
| |
| TargetEntryInstr* body_entry; |
| TargetEntryInstr* loop_exit; |
| |
| Fragment condition; |
| // i < argument_count |
| condition += LoadLocal(index); |
| condition += LoadLocal(argument_count); |
| condition += SmiRelationalOp(Token::kLT); |
| condition += BranchIfTrue(&body_entry, &loop_exit, /*negate=*/false); |
| |
| Fragment loop_body(body_entry); |
| |
| // arguments[i] = LoadFpRelativeSlot( |
| // kWordSize * (frame_layout.param_end_from_fp + argument_count - i)); |
| loop_body += LoadLocal(arguments); |
| loop_body += LoadLocal(index); |
| loop_body += LoadLocal(argument_count); |
| loop_body += LoadLocal(index); |
| loop_body += SmiBinaryOp(Token::kSUB, /*truncate=*/true); |
| loop_body += LoadFpRelativeSlot( |
| kWordSize * compiler::target::frame_layout.param_end_from_fp); |
| loop_body += StoreIndexed(kArrayCid); |
| |
| // ++i |
| loop_body += LoadLocal(index); |
| loop_body += IntConstant(1); |
| loop_body += SmiBinaryOp(Token::kADD, /*truncate=*/true); |
| loop_body += StoreLocal(TokenPosition::kNoSource, index); |
| loop_body += Drop(); |
| |
| JoinEntryInstr* join = BuildJoinEntry(); |
| loop_body += Goto(join); |
| |
| Fragment loop(join); |
| loop += condition; |
| |
| Instruction* entry = |
| new (Z) GotoInstr(join, CompilerState::Current().GetNextDeoptId()); |
| body += Fragment(entry, loop_exit); |
| } |
| |
| // Load receiver. |
| if (is_implicit_closure_function) { |
| if (throw_no_such_method_error) { |
| const Function& parent = |
| Function::ZoneHandle(Z, function.parent_function()); |
| const Class& owner = Class::ZoneHandle(Z, parent.Owner()); |
| AbstractType& type = AbstractType::ZoneHandle(Z); |
| type ^= Type::New(owner, TypeArguments::Handle(Z), owner.token_pos(), |
| Heap::kOld); |
| type ^= ClassFinalizer::FinalizeType(owner, type); |
| body += Constant(type); |
| } else { |
| body += LoadLocal(parsed_function_->current_context_var()); |
| body += LoadNativeField( |
| Slot::GetContextVariableSlotFor(thread_, *scopes_->this_variable)); |
| } |
| } else { |
| LocalScope* scope = parsed_function_->node_sequence()->scope(); |
| body += LoadLocal(scope->VariableAt(0)); |
| } |
| body += PushArgument(); |
| |
| body += Constant(String::ZoneHandle(Z, function.name())); |
| body += PushArgument(); |
| |
| if (!parsed_function_->has_arg_desc_var()) { |
| // If there is no variable for the arguments descriptor (this function's |
| // signature doesn't require it), then we need to create one. |
| Array& args_desc = Array::ZoneHandle( |
| Z, ArgumentsDescriptor::New(0, function.NumParameters())); |
| body += Constant(args_desc); |
| } else { |
| body += LoadArgDescriptor(); |
| } |
| body += PushArgument(); |
| |
| body += LoadLocal(arguments); |
| body += PushArgument(); |
| |
| if (throw_no_such_method_error) { |
| const Function& parent = |
| Function::ZoneHandle(Z, function.parent_function()); |
| const Class& owner = Class::ZoneHandle(Z, parent.Owner()); |
| InvocationMirror::Level im_level = owner.IsTopLevel() |
| ? InvocationMirror::kTopLevel |
| : InvocationMirror::kStatic; |
| InvocationMirror::Kind im_kind; |
| if (function.IsImplicitGetterFunction() || function.IsGetterFunction()) { |
| im_kind = InvocationMirror::kGetter; |
| } else if (function.IsImplicitSetterFunction() || |
| function.IsSetterFunction()) { |
| im_kind = InvocationMirror::kSetter; |
| } else { |
| im_kind = InvocationMirror::kMethod; |
| } |
| body += IntConstant(InvocationMirror::EncodeType(im_level, im_kind)); |
| } else { |
| body += NullConstant(); |
| } |
| body += PushArgument(); |
| |
| // Push the number of delayed type arguments. |
| if (function.IsClosureFunction()) { |
| LocalVariable* closure = |
| parsed_function_->node_sequence()->scope()->VariableAt(0); |
| Fragment then; |
| then += IntConstant(function.NumTypeParameters()); |
| then += StoreLocal(TokenPosition::kNoSource, argument_count_var); |
| then += Drop(); |
| Fragment otherwise; |
| otherwise += IntConstant(0); |
| otherwise += StoreLocal(TokenPosition::kNoSource, argument_count_var); |
| otherwise += Drop(); |
| body += TestDelayedTypeArgs(closure, then, otherwise); |
| body += LoadLocal(argument_count_var); |
| } else { |
| body += IntConstant(0); |
| } |
| body += PushArgument(); |
| |
| const Class& mirror_class = |
| Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror())); |
| ASSERT(!mirror_class.IsNull()); |
| const Function& allocation_function = Function::ZoneHandle( |
| Z, mirror_class.LookupStaticFunction(Library::PrivateCoreLibName( |
| Symbols::AllocateInvocationMirrorForClosure()))); |
| ASSERT(!allocation_function.IsNull()); |
| body += StaticCall(TokenPosition::kMinSource, allocation_function, |
| /* argument_count = */ 5, ICData::kStatic); |
| body += PushArgument(); // For the call to noSuchMethod. |
| |
| if (throw_no_such_method_error) { |
| const Class& klass = Class::ZoneHandle( |
| Z, Library::LookupCoreClass(Symbols::NoSuchMethodError())); |
| ASSERT(!klass.IsNull()); |
| const Function& throw_function = Function::ZoneHandle( |
| Z, |
| klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNewInvocation())); |
| ASSERT(!throw_function.IsNull()); |
| body += StaticCall(TokenPosition::kNoSource, throw_function, 2, |
| ICData::kStatic); |
| } else { |
| body += InstanceCall( |
| TokenPosition::kNoSource, Symbols::NoSuchMethod(), Token::kILLEGAL, |
| /*type_args_len=*/0, /*argument_count=*/2, Array::null_array(), |
| /*checked_argument_count=*/1, Function::null_function()); |
| } |
| body += StoreLocal(TokenPosition::kNoSource, result); |
| body += Drop(); |
| |
| body += Drop(); // arguments |
| body += Drop(); // argument count |
| |
| AbstractType& return_type = AbstractType::Handle(function.result_type()); |
| if (!return_type.IsDynamicType() && !return_type.IsVoidType() && |
| !return_type.IsObjectType()) { |
| body += AssertAssignable(TokenPosition::kNoSource, return_type, |
| Symbols::Empty()); |
| } |
| body += Return(TokenPosition::kNoSource); |
| |
| return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, |
| prologue_info); |
| } |
| |
| Fragment FlowGraphBuilder::BuildDefaultTypeHandling(const Function& function) { |
| if (function.IsGeneric()) { |
| const TypeArguments& default_types = |
| parsed_function_->DefaultFunctionTypeArguments(); |
| |
| if (!default_types.IsNull()) { |
| Fragment then; |
| Fragment otherwise; |
| |
| otherwise += TranslateInstantiatedTypeArguments(default_types); |
| otherwise += StoreLocal(TokenPosition::kNoSource, |
| parsed_function_->function_type_arguments()); |
| otherwise += Drop(); |
| return TestAnyTypeArgs(then, otherwise); |
| } |
| } |
| return Fragment(); |
| } |
| |
| // Pop the index of the current entry-point off the stack. If there is any |
| // entrypoint-tracing hook registered in a pragma for the function, it is called |
| // with the name of the current function and the current entry-point index. |
| Fragment FlowGraphBuilder::BuildEntryPointsIntrospection() { |
| if (!FLAG_enable_testing_pragmas) return Drop(); |
| |
| auto& function = Function::Handle(Z, parsed_function_->function().raw()); |
| |
| 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()); |
| function = owner.LookupFunction(func_name); |
| } |
| |
| auto& tmp = Object::Handle(Z); |
| tmp = function.Owner(); |
| tmp = Class::Cast(tmp).library(); |
| auto& library = Library::Cast(tmp); |
| |
| Object& options = Object::Handle(Z); |
| if (!library.FindPragma(thread_, function, Symbols::vm_trace_entrypoints(), |
| &options) || |
| options.IsNull() || !options.IsClosure()) { |
| return Drop(); |
| } |
| auto& closure = Closure::ZoneHandle(Z, Closure::Cast(options).raw()); |
| LocalVariable* entry_point_num = MakeTemporary(); |
| |
| 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); |
| } |
| |
| Fragment call_hook; |
| call_hook += Constant(closure); |
| call_hook += PushArgument(); |
| call_hook += Constant(function_name); |
| call_hook += PushArgument(); |
| call_hook += LoadLocal(entry_point_num); |
| call_hook += PushArgument(); |
| call_hook += Constant(Function::ZoneHandle(Z, closure.function())); |
| call_hook += ClosureCall(TokenPosition::kNoSource, |
| /*type_args_len=*/0, /*argument_count=*/3, |
| /*argument_names=*/Array::ZoneHandle(Z)); |
| call_hook += Drop(); // result of closure call |
| call_hook += Drop(); // entrypoint number |
| return call_hook; |
| } |
| |
| FunctionEntryInstr* FlowGraphBuilder::BuildSharedUncheckedEntryPoint( |
| Fragment shared_prologue_linked_in, |
| Fragment skippable_checks, |
| Fragment redefinitions_if_skipped, |
| Fragment body) { |
| ASSERT(shared_prologue_linked_in.entry == graph_entry_->normal_entry()); |
| ASSERT(parsed_function_->has_entry_points_temp_var()); |
| Instruction* prologue_start = shared_prologue_linked_in.entry->next(); |
| |
| auto* join_entry = BuildJoinEntry(); |
| |
| Fragment normal_entry(shared_prologue_linked_in.entry); |
| normal_entry += |
| IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kNone)); |
| normal_entry += StoreLocal(TokenPosition::kNoSource, |
| parsed_function_->entry_points_temp_var()); |
| normal_entry += Drop(); |
| normal_entry += Goto(join_entry); |
| |
| auto* extra_target_entry = BuildFunctionEntry(graph_entry_); |
| Fragment extra_entry(extra_target_entry); |
| extra_entry += IntConstant( |
| static_cast<intptr_t>(UncheckedEntryPointStyle::kSharedWithVariable)); |
| extra_entry += StoreLocal(TokenPosition::kNoSource, |
| parsed_function_->entry_points_temp_var()); |
| extra_entry += Drop(); |
| extra_entry += Goto(join_entry); |
| |
| join_entry->LinkTo(prologue_start); |
| |
| TargetEntryInstr *do_checks, *skip_checks; |
| shared_prologue_linked_in += |
| LoadLocal(parsed_function_->entry_points_temp_var()); |
| shared_prologue_linked_in += BuildEntryPointsIntrospection(); |
| shared_prologue_linked_in += |
| LoadLocal(parsed_function_->entry_points_temp_var()); |
| shared_prologue_linked_in += IntConstant( |
| static_cast<intptr_t>(UncheckedEntryPointStyle::kSharedWithVariable)); |
| shared_prologue_linked_in += |
| BranchIfEqual(&skip_checks, &do_checks, /*negate=*/false); |
| |
| JoinEntryInstr* rest_entry = BuildJoinEntry(); |
| |
| Fragment(do_checks) + skippable_checks + Goto(rest_entry); |
| Fragment(skip_checks) + redefinitions_if_skipped + Goto(rest_entry); |
| Fragment(rest_entry) + body; |
| |
| return extra_target_entry; |
| } |
| |
| FunctionEntryInstr* FlowGraphBuilder::BuildSeparateUncheckedEntryPoint( |
| BlockEntryInstr* normal_entry, |
| Fragment normal_prologue, |
| Fragment extra_prologue, |
| Fragment shared_prologue, |
| Fragment body) { |
| auto* join_entry = BuildJoinEntry(); |
| auto* extra_entry = BuildFunctionEntry(graph_entry_); |
| |
| Fragment normal(normal_entry); |
| normal += IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kNone)); |
| normal += BuildEntryPointsIntrospection(); |
| normal += normal_prologue; |
| normal += Goto(join_entry); |
| |
| Fragment extra(extra_entry); |
| extra += |
| IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kSeparate)); |
| extra += BuildEntryPointsIntrospection(); |
| extra += extra_prologue; |
| extra += Goto(join_entry); |
| |
| Fragment(join_entry) + shared_prologue + body; |
| return extra_entry; |
| } |
| |
| void FlowGraphBuilder::RecordUncheckedEntryPoint( |
| FunctionEntryInstr* extra_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() || |
| !FLAG_precompiled_mode)) { |
| graph_entry_->set_unchecked_entry(extra_entry); |
| } else if (InliningUncheckedEntry()) { |
| graph_entry_->set_normal_entry(extra_entry); |
| } |
| } |
| |
| FlowGraph* FlowGraphBuilder::BuildGraphOfImplicitClosureFunction( |
| const Function& function) { |
| const Function& parent = Function::ZoneHandle(Z, function.parent_function()); |
| const String& func_name = String::ZoneHandle(Z, parent.name()); |
| const Class& owner = Class::ZoneHandle(Z, parent.Owner()); |
| Function& target = Function::ZoneHandle(Z, owner.LookupFunction(func_name)); |
| |
| if (!target.IsNull() && (target.raw() != parent.raw())) { |
| DEBUG_ASSERT(Isolate::Current()->HasAttemptedReload()); |
| if ((target.is_static() != parent.is_static()) || |
| (target.kind() != parent.kind())) { |
| target = Function::null(); |
| } |
| } |
| |
| if (target.IsNull() || |
| (parent.num_fixed_parameters() != target.num_fixed_parameters())) { |
| return BuildGraphOfNoSuchMethodForwarder(function, true, |
| parent.is_static()); |
| } |
| |
| graph_entry_ = |
| new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId); |
| |
| auto normal_entry = BuildFunctionEntry(graph_entry_); |
| graph_entry_->set_normal_entry(normal_entry); |
| |
| PrologueInfo prologue_info(-1, -1); |
| BlockEntryInstr* instruction_cursor = |
| BuildPrologue(normal_entry, &prologue_info); |
| |
| const Fragment prologue = CheckStackOverflowInPrologue(function.token_pos()); |
| |
| const Fragment default_type_handling = BuildDefaultTypeHandling(function); |
| |
| // We're going to throw away the explicit checks because the target will |
| // always check them. |
| Fragment implicit_checks; |
| if (function.NeedsArgumentTypeChecks(I)) { |
| Fragment explicit_checks_unused; |
| if (target.is_static()) { |
| // Tearoffs of static methods needs to perform arguments checks since |
| // static methods they forward to don't do it themselves. |
| BuildArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds, |
| &explicit_checks_unused, &implicit_checks, |
| nullptr); |
| } else { |
| if (MethodCanSkipTypeChecksForNonCovariantArguments( |
| parent, ProcedureAttributesMetadata())) { |
| // Generate checks that are skipped inside a body of a function. |
| BuildArgumentTypeChecks( |
| TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds, |
| &explicit_checks_unused, &implicit_checks, nullptr); |
| } |
| } |
| } |
| |
| Fragment body; |
| |
| intptr_t type_args_len = 0; |
| if (function.IsGeneric()) { |
| type_args_len = function.NumTypeParameters(); |
| ASSERT(parsed_function_->function_type_arguments() != NULL); |
| body += LoadLocal(parsed_function_->function_type_arguments()); |
| body += PushArgument(); |
| } |
| |
| // Load all the arguments. |
| auto scope = parsed_function_->node_sequence()->scope(); |
| |
| if (!target.is_static()) { |
| // The context has a fixed shape: a single variable which is the |
| // closed-over receiver. |
| body += LoadLocal(scope->VariableAt(0)); |
| body += LoadNativeField(Slot::Closure_context()); |
| body += LoadNativeField( |
| Slot::GetContextVariableSlotFor(thread_, *scopes_->this_variable)); |
| body += PushArgument(); |
| } |
| |
| // Positional. |
| intptr_t arg_index = function.NumImplicitParameters(); |
| const intptr_t named_argument_count = function.NumOptionalNamedParameters(); |
| const intptr_t positional_argument_count = function.NumParameters() - |
| function.NumImplicitParameters() - |
| named_argument_count; |
| for (intptr_t i = 0; i < positional_argument_count; ++i, ++arg_index) { |
| body += LoadLocal(scope->VariableAt(arg_index)); // ith variable offset. |
| body += PushArgument(); |
| } |
| |
| // Named. |
| Array& argument_names = Array::ZoneHandle(Z); |
| if (named_argument_count > 0) { |
| argument_names = Array::New(named_argument_count, Heap::kOld); |
| String& name = String::Handle(Z); |
| for (intptr_t i = 0; i < named_argument_count; ++i, ++arg_index) { |
| body += LoadLocal(scope->VariableAt(arg_index)); |
| body += PushArgument(); |
| |
| name = function.ParameterNameAt(arg_index); |
| argument_names.SetAt(i, name); |
| } |
| } |
| |
| // Forward them to the parent. |
| intptr_t argument_count = positional_argument_count + named_argument_count; |
| if (!parent.is_static()) { |
| ++argument_count; |
| } |
| ASSERT(argument_count == target.NumParameters()); |
| body += StaticCall(TokenPosition::kNoSource, target, argument_count, |
| argument_names, ICData::kNoRebind, |
| /* result_type = */ NULL, type_args_len); |
| |
| // Return the result. |
| body += Return(function.end_token_pos()); |
| |
| // Setup multiple entrypoints if useful. |
| FunctionEntryInstr* extra_entry = nullptr; |
| if (function.MayHaveUncheckedEntryPoint(I)) { |
| // The prologue for a closure will always have context handling (e.g. |
| // setting up the 'this_variable'), but we don't need it on the unchecked |
| // entry because the only time we reference this is for loading the |
| // receiver, which we fetch directly from the context. |
| if (PrologueBuilder::PrologueSkippableOnUncheckedEntry(function)) { |
| // Use separate entry points since we can skip almost everything on the |
| // static entry. |
| extra_entry = BuildSeparateUncheckedEntryPoint( |
| /*normal_entry=*/instruction_cursor, |
| /*normal_prologue=*/prologue + default_type_handling + |
| implicit_checks, |
| /*extra_prologue=*/ |
| CheckStackOverflowInPrologue(function.token_pos()), |
| /*shared_prologue=*/Fragment(), |
| /*body=*/body); |
| } else { |
| Fragment shared_prologue(normal_entry, instruction_cursor); |
| shared_prologue += prologue; |
| extra_entry = BuildSharedUncheckedEntryPoint( |
| /*shared_prologue_linked_in=*/shared_prologue, |
| /*skippable_checks=*/default_type_handling + implicit_checks, |
| /*redefinitions_if_skipped=*/Fragment(), |
| /*body=*/body); |
| } |
| RecordUncheckedEntryPoint(extra_entry); |
| } else { |
| Fragment function(instruction_cursor); |
| function += prologue; |
| function += default_type_handling; |
| function += implicit_checks; |
| function += body; |
| } |
| |
| return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, |
| prologue_info); |
| } |
| |
| void FlowGraphBuilder::SetCurrentTryCatchBlock(TryCatchBlock* try_catch_block) { |
| try_catch_block_ = try_catch_block; |
| SetCurrentTryIndex(try_catch_block == nullptr ? kInvalidTryIndex |
| : try_catch_block->try_index()); |
| } |
| |
| } // namespace kernel |
| } // namespace dart |
| |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) |