// 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/frontend/kernel_to_il.h" #include "platform/assert.h" #include "platform/globals.h" #include "vm/class_id.h" #include "vm/compiler/aot/precompiler.h" #include "vm/compiler/backend/il.h" #include "vm/compiler/backend/il_printer.h" #include "vm/compiler/backend/locations.h" #include "vm/compiler/backend/range_analysis.h" #include "vm/compiler/ffi/abi.h" #include "vm/compiler/ffi/marshaller.h" #include "vm/compiler/ffi/native_calling_convention.h" #include "vm/compiler/ffi/native_type.h" #include "vm/compiler/ffi/recognized_method.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/compiler/runtime_api.h" #include "vm/kernel_isolate.h" #include "vm/kernel_loader.h" #include "vm/log.h" #include "vm/longjump.h" #include "vm/native_entry.h" #include "vm/object_store.h" #include "vm/report.h" #include "vm/resolver.h" #include "vm/scopes.h" #include "vm/stack_frame.h" #include "vm/symbols.h" namespace dart { namespace kernel { #define Z (zone_) #define H (translation_helper_) #define T (type_translator_) #define I Isolate::Current() #define IG IsolateGroup::Current() FlowGraphBuilder::FlowGraphBuilder( ParsedFunction* parsed_function, ZoneGrowableArray* ic_data_array, ZoneGrowableArray* 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), block_expression_depth_(0), graph_entry_(NULL), scopes_(NULL), breakable_block_(NULL), switch_block_(NULL), try_catch_block_(NULL), try_finally_block_(NULL), catch_block_(NULL), prepend_type_arguments_(Function::ZoneHandle(zone_)), throw_new_null_assertion_(Function::ZoneHandle(zone_)) { 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_slots()); LocalVariable* context = MakeTemporary(); instructions += LoadLocal(context); instructions += LoadLocal(parsed_function_->current_context_var()); instructions += StoreInstanceField(TokenPosition::kNoSource, Slot::Context_parent(), StoreInstanceFieldInstr::Kind::kInitializing); 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_ != nullptr && scopes_->type_arguments_variable != nullptr) { #ifdef DEBUG Function& function = Function::Handle(Z, parsed_function_->function().ptr()); while (function.IsClosureFunction()) { function = function.parent_function(); } ASSERT(function.IsFactory()); #endif instructions += LoadLocal(scopes_->type_arguments_variable); } else if (parsed_function_->has_receiver_var() && active_class_.ClassNumTypeArguments() > 0) { ASSERT(!parsed_function_->function().IsFactory()); instructions += LoadLocal(parsed_function_->receiver_var()); 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 { // newList() => new List(); // } // We just use the type argument vector from the [Foo] object and pass it // directly to the `new List()` 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::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( 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()); LocalVariable* closure_parameter = parsed_function_->ParameterVariable(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) { ASSERT(loop_depth_ == 0); return BaseFlowGraphBuilder::CheckStackOverflowInPrologue(position); } Fragment FlowGraphBuilder::CloneContext( const ZoneGrowableArray& context_slots) { LocalVariable* context_variable = parsed_function_->current_context_var(); Fragment instructions = LoadLocal(context_variable); CloneContextInstr* clone_instruction = new (Z) CloneContextInstr( InstructionSource(), Pop(), context_slots, 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 Function& tearoff_interface_target, const InferredTypeMetadata* result_type, bool use_unchecked_entry, const CallSiteAttributesMetadata* call_site_attrs, bool receiver_is_not_smi) { const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0); InputsArray* arguments = GetArguments(total_count); InstanceCallInstr* call = new (Z) InstanceCallInstr( InstructionSource(position), name, kind, arguments, type_args_len, argument_names, checked_argument_count, ic_data_array_, GetNextDeoptId(), interface_target, tearoff_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_receivers_static_type(call_site_attrs->receiver_type); } else if (!interface_target.IsNull()) { const Class& owner = Class::Handle(Z, interface_target.Owner()); const AbstractType& type = AbstractType::ZoneHandle(Z, owner.DeclarationType()); call->set_receivers_static_type(&type); } call->set_receiver_is_not_smi(receiver_is_not_smi); Push(call); if (result_type != nullptr && result_type->IsConstant()) { Fragment instructions(call); instructions += Drop(); instructions += Constant(result_type->constant_value); return instructions; } return Fragment(call); } Fragment FlowGraphBuilder::FfiCall( const compiler::ffi::CallMarshaller& marshaller) { Fragment body; FfiCallInstr* const call = new (Z) FfiCallInstr(Z, GetNextDeoptId(), marshaller); for (intptr_t i = call->InputCount() - 1; i >= 0; --i) { call->SetInputAt(i, Pop()); } Push(call); body <<= call; return body; } Fragment FlowGraphBuilder::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 FlowGraphBuilder::RethrowException(TokenPosition position, int catch_try_index) { Fragment instructions; Value* stacktrace = Pop(); Value* exception = Pop(); instructions += Fragment(new (Z) ReThrowInstr( InstructionSource(position), catch_try_index, GetNextDeoptId(), exception, stacktrace)) .closed(); // Use its side effect of leaving a constant on the stack (does not change // the graph). NullConstant(); return instructions; } Fragment FlowGraphBuilder::LoadLocal(LocalVariable* variable) { // Captured 'this' is immutable, so within the outer method we don't need to // load it from the context. const ParsedFunction* pf = parsed_function_; if (pf->function().HasThisParameter() && pf->has_receiver_var() && variable == pf->receiver_var()) { ASSERT(variable == pf->ParameterVariable(0)); variable = pf->RawParameterVariable(0); } 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::ThrowLateInitializationError( TokenPosition position, const char* throw_method_name, const String& name) { const Class& klass = Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::LateError())); ASSERT(!klass.IsNull()); const auto& error = klass.EnsureIsFinalized(thread_); ASSERT(error == Error::null()); const Function& throw_new = Function::ZoneHandle(Z, klass.LookupStaticFunctionAllowPrivate( H.DartSymbolObfuscate(throw_method_name))); ASSERT(!throw_new.IsNull()); Fragment instructions; // Call LateError._throwFoo. instructions += Constant(name); instructions += StaticCall(position, throw_new, /* argument_count = */ 1, ICData::kStatic); instructions += Drop(); return instructions; } Fragment FlowGraphBuilder::StoreLateField(const Field& field, LocalVariable* instance, LocalVariable* setter_value) { Fragment instructions; TargetEntryInstr *is_uninitialized, *is_initialized; const TokenPosition position = field.token_pos(); const bool is_static = field.is_static(); const bool is_final = field.is_final(); if (is_final) { // Check whether the field has been initialized already. if (is_static) { instructions += LoadStaticField(field, /*calls_initializer=*/false); } else { instructions += LoadLocal(instance); instructions += LoadField(field, /*calls_initializer=*/false); } instructions += Constant(Object::sentinel()); instructions += BranchIfStrictEqual(&is_uninitialized, &is_initialized); JoinEntryInstr* join = BuildJoinEntry(); { // If the field isn't initialized, do nothing. Fragment initialize(is_uninitialized); initialize += Goto(join); } { // If the field is already initialized, throw a LateInitializationError. Fragment already_initialized(is_initialized); already_initialized += ThrowLateInitializationError( position, "_throwFieldAlreadyInitialized", String::ZoneHandle(Z, field.name())); already_initialized += Goto(join); } instructions = Fragment(instructions.entry, join); } if (!is_static) { instructions += LoadLocal(instance); } instructions += LoadLocal(setter_value); if (is_static) { instructions += StoreStaticField(position, field); } else { instructions += StoreInstanceFieldGuarded(field); } return instructions; } Fragment FlowGraphBuilder::NativeCall(const String* name, const Function* function) { InlineBailout("kernel::FlowGraphBuilder::NativeCall"); const intptr_t num_args = function->NumParameters() + (function->IsGeneric() ? 1 : 0); InputsArray* arguments = GetArguments(num_args); NativeCallInstr* call = new (Z) NativeCallInstr(name, function, FLAG_link_natives_lazily, InstructionSource(function->end_token_pos()), arguments); Push(call); return Fragment(call); } Fragment FlowGraphBuilder::Return(TokenPosition position, bool omit_result_type_check, intptr_t yield_index) { 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); } instructions += BaseFlowGraphBuilder::Return(position, yield_index); 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); } void FlowGraphBuilder::SetResultTypeForStaticCall( StaticCallInstr* call, const Function& target, intptr_t argument_count, const InferredTypeMetadata* result_type) { if (call->InitResultType(Z)) { ASSERT((result_type == NULL) || (result_type->cid == kDynamicCid) || (result_type->cid == call->result_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); InputsArray* arguments = GetArguments(total_count); StaticCallInstr* call = new (Z) StaticCallInstr( InstructionSource(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); if (result_type != nullptr && result_type->IsConstant()) { Fragment instructions(call); instructions += Drop(); instructions += Constant(result_type->constant_value); return instructions; } return Fragment(call); } 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 += 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 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); // this instructions += Constant(url); // url instructions += NullConstant(); // line instructions += IntConstant(0); // column instructions += Constant(H.DartSymbolPlain("Malformed type.")); // message instructions += StaticCall(TokenPosition::kNoSource, constructor, /* argument_count = */ 5, ICData::kStatic); instructions += Drop(); // Throw the exception instructions += ThrowException(TokenPosition::kNoSource); return instructions; } Fragment FlowGraphBuilder::ThrowNoSuchMethodError(const Function& target) { const Class& klass = Class::ZoneHandle( Z, Library::LookupCoreClass(Symbols::NoSuchMethodError())); ASSERT(!klass.IsNull()); const auto& error = klass.EnsureIsFinalized(H.thread()); ASSERT(error == Error::null()); const Function& throw_function = Function::ZoneHandle( Z, klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNew())); ASSERT(!throw_function.IsNull()); Fragment instructions; const Class& owner = Class::Handle(Z, target.Owner()); AbstractType& receiver = AbstractType::ZoneHandle(); InvocationMirror::Kind kind = InvocationMirror::Kind::kMethod; if (target.IsImplicitGetterFunction() || target.IsGetterFunction()) { kind = InvocationMirror::kGetter; } else if (target.IsImplicitSetterFunction() || target.IsSetterFunction()) { kind = InvocationMirror::kSetter; } InvocationMirror::Level level; if (owner.IsTopLevel()) { level = InvocationMirror::Level::kTopLevel; } else { receiver = owner.RareType(); if (target.kind() == UntaggedFunction::kConstructor) { level = InvocationMirror::Level::kConstructor; } else { level = InvocationMirror::Level::kStatic; } } // Call NoSuchMethodError._throwNew static function. instructions += Constant(receiver); // receiver instructions += Constant(String::ZoneHandle(Z, target.name())); // memberName instructions += IntConstant(InvocationMirror::EncodeType(level, kind)); instructions += IntConstant(0); // type arguments length instructions += NullConstant(); // type arguments instructions += NullConstant(); // arguments instructions += NullConstant(); // argumentNames instructions += StaticCall(TokenPosition::kNoSource, throw_function, /* argument_count = */ 7, ICData::kStatic); // Properly close graph with a ThrowInstr, although it is not executed. instructions += ThrowException(TokenPosition::kNoSource); instructions += Drop(); 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()); // Check that all functions that are explicitly marked as recognized with the // vm:recognized annotation are in fact recognized. The check can't be done on // function creation, since the recognized status isn't set until later. if ((function.IsRecognized() != MethodRecognizer::IsMarkedAsRecognized(function)) && !function.IsDynamicInvocationForwarder()) { if (function.IsRecognized()) { FATAL1( "Recognized method %s is not marked with the vm:recognized pragma.", function.ToQualifiedCString()); } else { FATAL1( "Non-recognized method %s is marked with the vm:recognized pragma.", function.ToQualifiedCString()); } } #endif auto& kernel_data = ExternalTypedData::Handle(Z, function.KernelData()); intptr_t kernel_data_program_offset = function.KernelDataProgramOffset(); StreamingFlowGraphBuilder streaming_flow_graph_builder( this, kernel_data, kernel_data_program_offset); return streaming_flow_graph_builder.BuildGraph(); } Fragment FlowGraphBuilder::NativeFunctionBody(const Function& function, LocalVariable* first_parameter) { ASSERT(function.is_native()); ASSERT(!IsRecognizedMethodForFlowGraph(function)); Fragment body; String& name = String::ZoneHandle(Z, function.native_name()); if (function.IsGeneric()) { body += LoadLocal(parsed_function_->RawTypeArgumentsVariable()); } for (intptr_t i = 0; i < function.NumParameters(); ++i) { body += LoadLocal(parsed_function_->RawParameterVariable(i)); } body += NativeCall(&name, &function); // We typecheck results of native calls for type safety. body += Return(TokenPosition::kNoSource, /* omit_result_type_check = */ false); return body; } bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph( const Function& function) { const MethodRecognizer::Kind kind = function.recognized_kind(); switch (kind) { case MethodRecognizer::kTypedData_ByteDataView_factory: case MethodRecognizer::kTypedData_Int8ArrayView_factory: case MethodRecognizer::kTypedData_Uint8ArrayView_factory: case MethodRecognizer::kTypedData_Uint8ClampedArrayView_factory: case MethodRecognizer::kTypedData_Int16ArrayView_factory: case MethodRecognizer::kTypedData_Uint16ArrayView_factory: case MethodRecognizer::kTypedData_Int32ArrayView_factory: case MethodRecognizer::kTypedData_Uint32ArrayView_factory: case MethodRecognizer::kTypedData_Int64ArrayView_factory: case MethodRecognizer::kTypedData_Uint64ArrayView_factory: case MethodRecognizer::kTypedData_Float32ArrayView_factory: case MethodRecognizer::kTypedData_Float64ArrayView_factory: case MethodRecognizer::kTypedData_Float32x4ArrayView_factory: case MethodRecognizer::kTypedData_Int32x4ArrayView_factory: case MethodRecognizer::kTypedData_Float64x2ArrayView_factory: case MethodRecognizer::kTypedData_Int8Array_factory: case MethodRecognizer::kTypedData_Uint8Array_factory: case MethodRecognizer::kTypedData_Uint8ClampedArray_factory: case MethodRecognizer::kTypedData_Int16Array_factory: case MethodRecognizer::kTypedData_Uint16Array_factory: case MethodRecognizer::kTypedData_Int32Array_factory: case MethodRecognizer::kTypedData_Uint32Array_factory: case MethodRecognizer::kTypedData_Int64Array_factory: case MethodRecognizer::kTypedData_Uint64Array_factory: case MethodRecognizer::kTypedData_Float32Array_factory: case MethodRecognizer::kTypedData_Float64Array_factory: case MethodRecognizer::kTypedData_Float32x4Array_factory: case MethodRecognizer::kTypedData_Int32x4Array_factory: case MethodRecognizer::kTypedData_Float64x2Array_factory: case MethodRecognizer::kFfiLoadInt8: case MethodRecognizer::kFfiLoadInt16: case MethodRecognizer::kFfiLoadInt32: case MethodRecognizer::kFfiLoadInt64: case MethodRecognizer::kFfiLoadUint8: case MethodRecognizer::kFfiLoadUint16: case MethodRecognizer::kFfiLoadUint32: case MethodRecognizer::kFfiLoadUint64: case MethodRecognizer::kFfiLoadIntPtr: case MethodRecognizer::kFfiLoadFloat: case MethodRecognizer::kFfiLoadFloatUnaligned: case MethodRecognizer::kFfiLoadDouble: case MethodRecognizer::kFfiLoadDoubleUnaligned: case MethodRecognizer::kFfiLoadPointer: case MethodRecognizer::kFfiStoreInt8: case MethodRecognizer::kFfiStoreInt16: case MethodRecognizer::kFfiStoreInt32: case MethodRecognizer::kFfiStoreInt64: case MethodRecognizer::kFfiStoreUint8: case MethodRecognizer::kFfiStoreUint16: case MethodRecognizer::kFfiStoreUint32: case MethodRecognizer::kFfiStoreUint64: case MethodRecognizer::kFfiStoreIntPtr: case MethodRecognizer::kFfiStoreFloat: case MethodRecognizer::kFfiStoreFloatUnaligned: case MethodRecognizer::kFfiStoreDouble: case MethodRecognizer::kFfiStoreDoubleUnaligned: case MethodRecognizer::kFfiStorePointer: case MethodRecognizer::kFfiFromAddress: case MethodRecognizer::kFfiGetAddress: case MethodRecognizer::kObjectEquals: case MethodRecognizer::kStringBaseLength: case MethodRecognizer::kStringBaseIsEmpty: case MethodRecognizer::kGrowableArrayLength: case MethodRecognizer::kObjectArrayLength: case MethodRecognizer::kImmutableArrayLength: case MethodRecognizer::kTypedListLength: case MethodRecognizer::kTypedListViewLength: case MethodRecognizer::kByteDataViewLength: case MethodRecognizer::kByteDataViewOffsetInBytes: case MethodRecognizer::kTypedDataViewOffsetInBytes: case MethodRecognizer::kByteDataViewTypedData: case MethodRecognizer::kTypedDataViewTypedData: case MethodRecognizer::kClassIDgetID: case MethodRecognizer::kGrowableArrayCapacity: case MethodRecognizer::kListFactory: case MethodRecognizer::kObjectArrayAllocate: case MethodRecognizer::kCopyRangeFromUint8ListToOneByteString: case MethodRecognizer::kLinkedHashMap_getIndex: case MethodRecognizer::kLinkedHashMap_setIndex: case MethodRecognizer::kLinkedHashMap_getData: case MethodRecognizer::kLinkedHashMap_setData: case MethodRecognizer::kLinkedHashMap_getHashMask: case MethodRecognizer::kLinkedHashMap_setHashMask: case MethodRecognizer::kLinkedHashMap_getUsedData: case MethodRecognizer::kLinkedHashMap_setUsedData: case MethodRecognizer::kLinkedHashMap_getDeletedKeys: case MethodRecognizer::kLinkedHashMap_setDeletedKeys: case MethodRecognizer::kWeakProperty_getKey: case MethodRecognizer::kWeakProperty_setKey: case MethodRecognizer::kWeakProperty_getValue: case MethodRecognizer::kWeakProperty_setValue: case MethodRecognizer::kFfiAbi: case MethodRecognizer::kReachabilityFence: case MethodRecognizer::kUtf8DecoderScan: return true; default: return false; } } FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod( const Function& function) { ASSERT(IsRecognizedMethodForFlowGraph(function)); 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()); const MethodRecognizer::Kind kind = function.recognized_kind(); switch (kind) { case MethodRecognizer::kTypedData_ByteDataView_factory: body += BuildTypedDataViewFactoryConstructor(function, kByteDataViewCid); break; case MethodRecognizer::kTypedData_Int8ArrayView_factory: body += BuildTypedDataViewFactoryConstructor(function, kTypedDataInt8ArrayViewCid); break; case MethodRecognizer::kTypedData_Uint8ArrayView_factory: body += BuildTypedDataViewFactoryConstructor(function, kTypedDataUint8ArrayViewCid); break; case MethodRecognizer::kTypedData_Uint8ClampedArrayView_factory: body += BuildTypedDataViewFactoryConstructor( function, kTypedDataUint8ClampedArrayViewCid); break; case MethodRecognizer::kTypedData_Int16ArrayView_factory: body += BuildTypedDataViewFactoryConstructor(function, kTypedDataInt16ArrayViewCid); break; case MethodRecognizer::kTypedData_Uint16ArrayView_factory: body += BuildTypedDataViewFactoryConstructor( function, kTypedDataUint16ArrayViewCid); break; case MethodRecognizer::kTypedData_Int32ArrayView_factory: body += BuildTypedDataViewFactoryConstructor(function, kTypedDataInt32ArrayViewCid); break; case MethodRecognizer::kTypedData_Uint32ArrayView_factory: body += BuildTypedDataViewFactoryConstructor( function, kTypedDataUint32ArrayViewCid); break; case MethodRecognizer::kTypedData_Int64ArrayView_factory: body += BuildTypedDataViewFactoryConstructor(function, kTypedDataInt64ArrayViewCid); break; case MethodRecognizer::kTypedData_Uint64ArrayView_factory: body += BuildTypedDataViewFactoryConstructor( function, kTypedDataUint64ArrayViewCid); break; case MethodRecognizer::kTypedData_Float32ArrayView_factory: body += BuildTypedDataViewFactoryConstructor( function, kTypedDataFloat32ArrayViewCid); break; case MethodRecognizer::kTypedData_Float64ArrayView_factory: body += BuildTypedDataViewFactoryConstructor( function, kTypedDataFloat64ArrayViewCid); break; case MethodRecognizer::kTypedData_Float32x4ArrayView_factory: body += BuildTypedDataViewFactoryConstructor( function, kTypedDataFloat32x4ArrayViewCid); break; case MethodRecognizer::kTypedData_Int32x4ArrayView_factory: body += BuildTypedDataViewFactoryConstructor( function, kTypedDataInt32x4ArrayViewCid); break; case MethodRecognizer::kTypedData_Float64x2ArrayView_factory: body += BuildTypedDataViewFactoryConstructor( function, kTypedDataFloat64x2ArrayViewCid); break; case MethodRecognizer::kTypedData_Int8Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataInt8ArrayCid); break; case MethodRecognizer::kTypedData_Uint8Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataUint8ArrayCid); break; case MethodRecognizer::kTypedData_Uint8ClampedArray_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataUint8ClampedArrayCid); break; case MethodRecognizer::kTypedData_Int16Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataInt16ArrayCid); break; case MethodRecognizer::kTypedData_Uint16Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataUint16ArrayCid); break; case MethodRecognizer::kTypedData_Int32Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataInt32ArrayCid); break; case MethodRecognizer::kTypedData_Uint32Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataUint32ArrayCid); break; case MethodRecognizer::kTypedData_Int64Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataInt64ArrayCid); break; case MethodRecognizer::kTypedData_Uint64Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataUint64ArrayCid); break; case MethodRecognizer::kTypedData_Float32Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataFloat32ArrayCid); break; case MethodRecognizer::kTypedData_Float64Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataFloat64ArrayCid); break; case MethodRecognizer::kTypedData_Float32x4Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataFloat32x4ArrayCid); break; case MethodRecognizer::kTypedData_Int32x4Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataInt32x4ArrayCid); break; case MethodRecognizer::kTypedData_Float64x2Array_factory: body += BuildTypedDataFactoryConstructor(function, kTypedDataFloat64x2ArrayCid); break; case MethodRecognizer::kObjectEquals: ASSERT(function.NumParameters() == 2); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadLocal(parsed_function_->RawParameterVariable(1)); body += StrictCompare(Token::kEQ_STRICT); break; case MethodRecognizer::kStringBaseLength: case MethodRecognizer::kStringBaseIsEmpty: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::String_length()); if (kind == MethodRecognizer::kStringBaseIsEmpty) { body += IntConstant(0); body += StrictCompare(Token::kEQ_STRICT); } break; case MethodRecognizer::kGrowableArrayLength: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::GrowableObjectArray_length()); break; case MethodRecognizer::kObjectArrayLength: case MethodRecognizer::kImmutableArrayLength: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::Array_length()); break; case MethodRecognizer::kTypedListLength: case MethodRecognizer::kTypedListViewLength: case MethodRecognizer::kByteDataViewLength: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::TypedDataBase_length()); break; case MethodRecognizer::kByteDataViewOffsetInBytes: case MethodRecognizer::kTypedDataViewOffsetInBytes: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::TypedDataView_offset_in_bytes()); break; case MethodRecognizer::kByteDataViewTypedData: case MethodRecognizer::kTypedDataViewTypedData: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::TypedDataView_data()); break; case MethodRecognizer::kClassIDgetID: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadClassId(); break; case MethodRecognizer::kGrowableArrayCapacity: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::GrowableObjectArray_data()); body += LoadNativeField(Slot::Array_length()); break; case MethodRecognizer::kListFactory: { ASSERT(function.IsFactory() && (function.NumParameters() == 2) && function.HasOptionalParameters()); // factory List([int length]) { // return (:arg_desc.positional_count == 2) ? new _List(length) // : new _GrowableList(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(parsed_function_->RawParameterVariable(0)); allocate += LoadLocal(parsed_function_->RawParameterVariable(1)); 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(parsed_function_->RawParameterVariable(0)); allocate += IntConstant(0); 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: ASSERT(function.IsFactory() && (function.NumParameters() == 2)); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadLocal(parsed_function_->RawParameterVariable(1)); body += CreateArray(); break; case MethodRecognizer::kCopyRangeFromUint8ListToOneByteString: ASSERT(function.NumParameters() == 5); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadLocal(parsed_function_->RawParameterVariable(1)); body += LoadLocal(parsed_function_->RawParameterVariable(2)); body += LoadLocal(parsed_function_->RawParameterVariable(3)); body += LoadLocal(parsed_function_->RawParameterVariable(4)); body += MemoryCopy(kTypedDataUint8ArrayCid, kOneByteStringCid); body += NullConstant(); break; case MethodRecognizer::kLinkedHashMap_getIndex: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::LinkedHashMap_index()); break; case MethodRecognizer::kLinkedHashMap_setIndex: ASSERT(function.NumParameters() == 2); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadLocal(parsed_function_->RawParameterVariable(1)); body += StoreInstanceField(TokenPosition::kNoSource, Slot::LinkedHashMap_index()); body += NullConstant(); break; case MethodRecognizer::kLinkedHashMap_getData: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::LinkedHashMap_data()); break; case MethodRecognizer::kLinkedHashMap_setData: ASSERT(function.NumParameters() == 2); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadLocal(parsed_function_->RawParameterVariable(1)); body += StoreInstanceField(TokenPosition::kNoSource, Slot::LinkedHashMap_data()); body += NullConstant(); break; case MethodRecognizer::kLinkedHashMap_getHashMask: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::LinkedHashMap_hash_mask()); break; case MethodRecognizer::kLinkedHashMap_setHashMask: ASSERT(function.NumParameters() == 2); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadLocal(parsed_function_->RawParameterVariable(1)); body += StoreInstanceField( TokenPosition::kNoSource, Slot::LinkedHashMap_hash_mask(), StoreInstanceFieldInstr::Kind::kOther, kNoStoreBarrier); body += NullConstant(); break; case MethodRecognizer::kLinkedHashMap_getUsedData: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::LinkedHashMap_used_data()); break; case MethodRecognizer::kLinkedHashMap_setUsedData: ASSERT(function.NumParameters() == 2); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadLocal(parsed_function_->RawParameterVariable(1)); body += StoreInstanceField( TokenPosition::kNoSource, Slot::LinkedHashMap_used_data(), StoreInstanceFieldInstr::Kind::kOther, kNoStoreBarrier); body += NullConstant(); break; case MethodRecognizer::kLinkedHashMap_getDeletedKeys: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::LinkedHashMap_deleted_keys()); break; case MethodRecognizer::kLinkedHashMap_setDeletedKeys: ASSERT(function.NumParameters() == 2); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadLocal(parsed_function_->RawParameterVariable(1)); body += StoreInstanceField( TokenPosition::kNoSource, Slot::LinkedHashMap_deleted_keys(), StoreInstanceFieldInstr::Kind::kOther, kNoStoreBarrier); body += NullConstant(); break; case MethodRecognizer::kWeakProperty_getKey: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::WeakProperty_key()); break; case MethodRecognizer::kWeakProperty_setKey: ASSERT(function.NumParameters() == 2); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadLocal(parsed_function_->RawParameterVariable(1)); body += StoreInstanceField(TokenPosition::kNoSource, Slot::WeakProperty_key()); body += NullConstant(); break; case MethodRecognizer::kWeakProperty_getValue: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField(Slot::WeakProperty_value()); break; case MethodRecognizer::kWeakProperty_setValue: ASSERT(function.NumParameters() == 2); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadLocal(parsed_function_->RawParameterVariable(1)); body += StoreInstanceField(TokenPosition::kNoSource, Slot::WeakProperty_value()); body += NullConstant(); break; case MethodRecognizer::kUtf8DecoderScan: ASSERT(function.NumParameters() == 5); body += LoadLocal(parsed_function_->RawParameterVariable(0)); // decoder body += LoadLocal(parsed_function_->RawParameterVariable(1)); // bytes body += LoadLocal(parsed_function_->RawParameterVariable(2)); // start body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, function.name())); body += UnboxTruncate(kUnboxedIntPtr); body += LoadLocal(parsed_function_->RawParameterVariable(3)); // end body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, function.name())); body += UnboxTruncate(kUnboxedIntPtr); body += LoadLocal(parsed_function_->RawParameterVariable(4)); // table body += Utf8Scan(); body += Box(kUnboxedIntPtr); break; case MethodRecognizer::kReachabilityFence: ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += ReachabilityFence(); body += NullConstant(); break; case MethodRecognizer::kFfiAbi: ASSERT(function.NumParameters() == 0); body += IntConstant(static_cast(compiler::ffi::TargetAbi())); break; case MethodRecognizer::kFfiLoadInt8: case MethodRecognizer::kFfiLoadInt16: case MethodRecognizer::kFfiLoadInt32: case MethodRecognizer::kFfiLoadInt64: case MethodRecognizer::kFfiLoadUint8: case MethodRecognizer::kFfiLoadUint16: case MethodRecognizer::kFfiLoadUint32: case MethodRecognizer::kFfiLoadUint64: case MethodRecognizer::kFfiLoadIntPtr: case MethodRecognizer::kFfiLoadFloat: case MethodRecognizer::kFfiLoadFloatUnaligned: case MethodRecognizer::kFfiLoadDouble: case MethodRecognizer::kFfiLoadDoubleUnaligned: case MethodRecognizer::kFfiLoadPointer: { const classid_t ffi_type_arg_cid = compiler::ffi::RecognizedMethodTypeArgCid(kind); const AlignmentType alignment = compiler::ffi::RecognizedMethodAlignment(kind); const classid_t typed_data_cid = compiler::ffi::ElementTypedDataCid(ffi_type_arg_cid); ASSERT(function.NumParameters() == 2); LocalVariable* arg_pointer = parsed_function_->RawParameterVariable(0); LocalVariable* arg_offset = parsed_function_->RawParameterVariable(1); body += LoadLocal(arg_offset); body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, function.name())); LocalVariable* arg_offset_not_null = MakeTemporary(); body += LoadLocal(arg_pointer); body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, function.name())); // No GC from here til LoadIndexed. body += LoadUntagged(compiler::target::PointerBase::data_field_offset()); body += LoadLocal(arg_offset_not_null); body += UnboxTruncate(kUnboxedFfiIntPtr); body += LoadIndexed(typed_data_cid, /*index_scale=*/1, /*index_unboxed=*/true, alignment); if (kind == MethodRecognizer::kFfiLoadFloat || kind == MethodRecognizer::kFfiLoadFloatUnaligned) { body += FloatToDouble(); } // Avoid any unnecessary (and potentially deoptimizing) int // conversions by using the representation returned from LoadIndexed. body += Box(LoadIndexedInstr::RepresentationOfArrayElement(typed_data_cid)); if (kind == MethodRecognizer::kFfiLoadPointer) { const auto class_table = thread_->isolate_group()->class_table(); ASSERT(class_table->HasValidClassAt(kFfiPointerCid)); const auto& pointer_class = Class::ZoneHandle(H.zone(), class_table->At(kFfiPointerCid)); // We find the reified type to use for the pointer allocation. // // Call sites to this recognized method are guaranteed to pass a // Pointer> as RawParameterVariable(0). This function // will return a Pointer object - for which we inspect the // reified type on the argument. // // The following is safe to do, as (1) we are guaranteed to have a // Pointer> as argument, and (2) the bound on the pointer // type parameter guarantees X is an interface type. ASSERT(function.NumTypeParameters() == 1); LocalVariable* address = MakeTemporary(); body += LoadLocal(parsed_function_->RawParameterVariable(0)); body += LoadNativeField( Slot::GetTypeArgumentsSlotFor(thread_, pointer_class)); body += LoadNativeField(Slot::GetTypeArgumentsIndexSlot( thread_, Pointer::kNativeTypeArgPos)); body += LoadNativeField(Slot::Type_arguments()); body += AllocateObject(TokenPosition::kNoSource, pointer_class, 1); LocalVariable* pointer = MakeTemporary(); body += LoadLocal(pointer); body += LoadLocal(address); body += UnboxTruncate(kUnboxedFfiIntPtr); body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr); body += StoreUntagged(compiler::target::Pointer::data_field_offset()); body += DropTempsPreserveTop(1); // Drop [address] keep [pointer]. } body += DropTempsPreserveTop(1); // Drop [arg_offset]. } break; case MethodRecognizer::kFfiStoreInt8: case MethodRecognizer::kFfiStoreInt16: case MethodRecognizer::kFfiStoreInt32: case MethodRecognizer::kFfiStoreInt64: case MethodRecognizer::kFfiStoreUint8: case MethodRecognizer::kFfiStoreUint16: case MethodRecognizer::kFfiStoreUint32: case MethodRecognizer::kFfiStoreUint64: case MethodRecognizer::kFfiStoreIntPtr: case MethodRecognizer::kFfiStoreFloat: case MethodRecognizer::kFfiStoreFloatUnaligned: case MethodRecognizer::kFfiStoreDouble: case MethodRecognizer::kFfiStoreDoubleUnaligned: case MethodRecognizer::kFfiStorePointer: { const classid_t ffi_type_arg_cid = compiler::ffi::RecognizedMethodTypeArgCid(kind); const AlignmentType alignment = compiler::ffi::RecognizedMethodAlignment(kind); const classid_t typed_data_cid = compiler::ffi::ElementTypedDataCid(ffi_type_arg_cid); LocalVariable* arg_pointer = parsed_function_->RawParameterVariable(0); LocalVariable* arg_offset = parsed_function_->RawParameterVariable(1); LocalVariable* arg_value = parsed_function_->RawParameterVariable(2); if (kind == MethodRecognizer::kFfiStorePointer) { // Do type check before anything untagged is on the stack. const auto class_table = thread_->isolate_group()->class_table(); ASSERT(class_table->HasValidClassAt(kFfiPointerCid)); const auto& pointer_class = Class::ZoneHandle(H.zone(), class_table->At(kFfiPointerCid)); const auto& pointer_type_args = TypeArguments::Handle(pointer_class.type_parameters()); const auto& pointer_type_arg = AbstractType::ZoneHandle(pointer_type_args.TypeAt(0)); // But we type check it as a method on a generic class at runtime. body += LoadLocal(arg_value); // value. body += Constant(pointer_type_arg); // dst_type. // We pass the Pointer type argument as instantiator_type_args. // // Call sites to this recognized method are guaranteed to pass a // Pointer> as RawParameterVariable(0). This function // will takes a Pointer object - for which we inspect the // reified type on the argument. // // The following is safe to do, as (1) we are guaranteed to have a // Pointer> as argument, and (2) the bound on the pointer // type parameter guarantees X is an interface type. body += LoadLocal(arg_pointer); body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, function.name())); body += LoadNativeField( Slot::GetTypeArgumentsSlotFor(thread_, pointer_class)); body += NullConstant(); // function_type_args. body += AssertAssignable(TokenPosition::kNoSource, Symbols::Empty()); body += Drop(); } ASSERT(function.NumParameters() == 3); body += LoadLocal(arg_offset); body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, function.name())); LocalVariable* arg_offset_not_null = MakeTemporary(); body += LoadLocal(arg_value); body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, function.name())); LocalVariable* arg_value_not_null = MakeTemporary(); body += LoadLocal(arg_pointer); // Pointer. body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, function.name())); // No GC from here til StoreIndexed. body += LoadUntagged(compiler::target::PointerBase::data_field_offset()); body += LoadLocal(arg_offset_not_null); body += UnboxTruncate(kUnboxedFfiIntPtr); body += LoadLocal(arg_value_not_null); if (kind == MethodRecognizer::kFfiStorePointer) { // This can only be Pointer, so it is always safe to LoadUntagged. body += LoadUntagged(compiler::target::Pointer::data_field_offset()); body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr); } else { // Avoid any unnecessary (and potentially deoptimizing) int // conversions by using the representation consumed by StoreIndexed. body += UnboxTruncate( StoreIndexedInstr::RepresentationOfArrayElement(typed_data_cid)); if (kind == MethodRecognizer::kFfiStoreFloat || kind == MethodRecognizer::kFfiStoreFloatUnaligned) { body += DoubleToFloat(); } } body += StoreIndexedTypedData(typed_data_cid, /*index_scale=*/1, /*index_unboxed=*/true, alignment); body += Drop(); // Drop [arg_value]. body += Drop(); // Drop [arg_offset]. body += NullConstant(); } break; case MethodRecognizer::kFfiFromAddress: { const auto class_table = thread_->isolate_group()->class_table(); ASSERT(class_table->HasValidClassAt(kFfiPointerCid)); const auto& pointer_class = Class::ZoneHandle(H.zone(), class_table->At(kFfiPointerCid)); ASSERT(function.NumTypeParameters() == 1); ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawTypeArgumentsVariable()); body += AllocateObject(TokenPosition::kNoSource, pointer_class, 1); body += LoadLocal(MakeTemporary()); // Duplicate Pointer. body += LoadLocal(parsed_function_->RawParameterVariable(0)); // Address. body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, function.name())); body += UnboxTruncate(kUnboxedFfiIntPtr); body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr); body += StoreUntagged(compiler::target::Pointer::data_field_offset()); } break; case MethodRecognizer::kFfiGetAddress: { ASSERT(function.NumParameters() == 1); body += LoadLocal(parsed_function_->RawParameterVariable(0)); // Pointer. body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, function.name())); // This can only be Pointer, so it is always safe to LoadUntagged. body += LoadUntagged(compiler::target::Pointer::data_field_offset()); body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr); body += Box(kUnboxedFfiIntPtr); } break; default: { UNREACHABLE(); break; } } body += Return(TokenPosition::kNoSource, /* omit_result_type_check = */ true); return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, prologue_info); } Fragment FlowGraphBuilder::BuildTypedDataViewFactoryConstructor( const Function& function, classid_t cid) { auto token_pos = function.token_pos(); auto class_table = Thread::Current()->isolate_group()->class_table(); ASSERT(class_table->HasValidClassAt(cid)); const auto& view_class = Class::ZoneHandle(H.zone(), class_table->At(cid)); ASSERT(function.IsFactory() && (function.NumParameters() == 4)); LocalVariable* typed_data = parsed_function_->RawParameterVariable(1); LocalVariable* offset_in_bytes = parsed_function_->RawParameterVariable(2); LocalVariable* length = parsed_function_->RawParameterVariable(3); Fragment body; body += AllocateObject(token_pos, view_class, /*arg_count=*/0); LocalVariable* view_object = MakeTemporary(); body += LoadLocal(view_object); body += LoadLocal(typed_data); body += StoreInstanceField(token_pos, Slot::TypedDataView_data(), StoreInstanceFieldInstr::Kind::kInitializing); body += LoadLocal(view_object); body += LoadLocal(offset_in_bytes); body += StoreInstanceField(token_pos, Slot::TypedDataView_offset_in_bytes(), StoreInstanceFieldInstr::Kind::kInitializing, kNoStoreBarrier); body += LoadLocal(view_object); body += LoadLocal(length); body += StoreInstanceField(token_pos, Slot::TypedDataBase_length(), StoreInstanceFieldInstr::Kind::kInitializing, kNoStoreBarrier); // Update the inner pointer. // // WARNING: Notice that we assume here no GC happens between those 4 // instructions! body += LoadLocal(view_object); body += LoadLocal(typed_data); body += LoadUntagged(compiler::target::TypedDataBase::data_field_offset()); body += ConvertUntaggedToUnboxed(kUnboxedIntPtr); body += LoadLocal(offset_in_bytes); body += UnboxSmiToIntptr(); body += AddIntptrIntegers(); body += ConvertUnboxedToUntagged(kUnboxedIntPtr); body += StoreUntagged(compiler::target::TypedDataBase::data_field_offset()); return body; } Fragment FlowGraphBuilder::BuildTypedDataFactoryConstructor( const Function& function, classid_t cid) { const auto token_pos = function.token_pos(); ASSERT( Thread::Current()->isolate_group()->class_table()->HasValidClassAt(cid)); ASSERT(function.IsFactory() && (function.NumParameters() == 2)); LocalVariable* length = parsed_function_->RawParameterVariable(1); Fragment instructions; instructions += LoadLocal(length); // AllocateTypedData instruction checks that length is valid (a non-negative // Smi below maximum allowed length). instructions += AllocateTypedData(token_pos, cid); return instructions; } static const LocalScope* MakeImplicitClosureScope(Zone* Z, const Class& klass) { 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* receiver_variable = new (Z) LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource, Symbols::This(), klass_type, /*param_type=*/nullptr); receiver_variable->set_is_captured(); // receiver_variable->set_is_final(); LocalScope* scope = new (Z) LocalScope(NULL, 0, 0); scope->set_context_level(0); scope->AddVariable(receiver_variable); scope->AddContextVariable(receiver_variable); return scope; } Fragment FlowGraphBuilder::BuildImplicitClosureCreation( const Function& target) { Fragment fragment; fragment += AllocateClosure(TokenPosition::kNoSource, 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(), StoreInstanceFieldInstr::Kind::kInitializing); } // The function cannot be local and have parent generic functions. ASSERT(!target.HasGenericParent()); // Allocate a context that closes over `this`. // Note: this must be kept in sync with ScopeBuilder::BuildScopes. const LocalScope* implicit_closure_scope = MakeImplicitClosureScope(Z, Class::Handle(Z, target.Owner())); fragment += AllocateContext(implicit_closure_scope->context_slots()); 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(), StoreInstanceFieldInstr::Kind::kInitializing); fragment += LoadLocal(closure); fragment += LoadLocal(context); fragment += StoreInstanceField(TokenPosition::kNoSource, Slot::Closure_context(), StoreInstanceFieldInstr::Kind::kInitializing); if (target.IsGeneric()) { // Only generic functions need to have properly initialized // delayed_type_arguments. fragment += LoadLocal(closure); fragment += Constant(Object::empty_type_arguments()); fragment += StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_delayed_type_arguments(), StoreInstanceFieldInstr::Kind::kInitializing); } // 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(parsed_function_->receiver_var()); fragment += StoreInstanceField( TokenPosition::kNoSource, Slot::GetContextVariableSlotFor( thread_, *implicit_closure_scope->context_variables()[0]), StoreInstanceFieldInstr::Kind::kInitializing); 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(); } Fragment FlowGraphBuilder::EvaluateAssertion() { const Class& klass = Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::AssertionError())); ASSERT(!klass.IsNull()); const auto& error = klass.EnsureIsFinalized(H.thread()); ASSERT(error == Error::null()); 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 (!dst_type.IsTopTypeForSubtyping()) { LocalVariable* top_of_stack = MakeTemporary(); instructions += LoadLocal(top_of_stack); instructions += AssertAssignableLoadTypeArguments(TokenPosition::kNoSource, dst_type, dst_name, kind); instructions += Drop(); } return instructions; } Fragment FlowGraphBuilder::AssertAssignableLoadTypeArguments( TokenPosition position, const AbstractType& dst_type, const String& dst_name, AssertAssignableInstr::Kind kind) { Fragment instructions; instructions += Constant(AbstractType::ZoneHandle(dst_type.ptr())); if (!dst_type.IsInstantiated(kCurrentClass)) { instructions += LoadInstantiatorTypeArguments(); } else { instructions += NullConstant(); } if (!dst_type.IsInstantiated(kFunctions)) { instructions += LoadFunctionTypeArguments(); } else { instructions += NullConstant(); } instructions += AssertAssignable(position, dst_name, kind); return instructions; } Fragment FlowGraphBuilder::AssertSubtype(TokenPosition position, const AbstractType& sub_type_value, const AbstractType& super_type_value, const String& dst_name_value) { Fragment instructions; instructions += LoadInstantiatorTypeArguments(); instructions += LoadFunctionTypeArguments(); instructions += Constant(AbstractType::ZoneHandle(Z, sub_type_value.ptr())); instructions += Constant(AbstractType::ZoneHandle(Z, super_type_value.ptr())); instructions += Constant(String::ZoneHandle(Z, dst_name_value.ptr())); instructions += AssertSubtype(position); return instructions; } Fragment FlowGraphBuilder::AssertSubtype(TokenPosition position) { Fragment instructions; Value* dst_name = Pop(); Value* super_type = Pop(); Value* sub_type = Pop(); Value* function_type_args = Pop(); Value* instantiator_type_args = Pop(); AssertSubtypeInstr* instr = new (Z) AssertSubtypeInstr( InstructionSource(position), instantiator_type_args, function_type_args, sub_type, super_type, dst_name, GetNextDeoptId()); instructions += Fragment(instr); return instructions; } void FlowGraphBuilder::BuildTypeArgumentTypeChecks(TypeChecksToBuild mode, Fragment* implicit_checks) { 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.IsTopTypeForSubtyping()) { 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_->ParameterVariable(0); *implicit_checks += TestDelayedTypeArgs(closure, /*present=*/{}, /*absent=*/check_bounds); } else { *implicit_checks += check_bounds; } } void FlowGraphBuilder::BuildArgumentTypeChecks( Fragment* explicit_checks, Fragment* implicit_checks, Fragment* implicit_redefinitions) { 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()); } const intptr_t num_params = dart_function.NumParameters(); for (intptr_t i = dart_function.NumImplicitParameters(); i < num_params; ++i) { LocalVariable* param = parsed_function_->ParameterVariable(i); const String& name = param->name(); if (!param->needs_type_check()) { continue; } if (param->is_captured()) { param = parsed_function_->RawParameterVariable(i); } 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->IsTopTypeForSubtyping()) continue; const bool is_covariant = param->is_explicit_covariant_parameter(); Fragment* checks = is_covariant ? explicit_checks : implicit_checks; *checks += LoadLocal(param); *checks += AssertAssignableLoadTypeArguments( TokenPosition::kNoSource, *target_type, name, AssertAssignableInstr::kParameterCheck); *checks += StoreLocal(param); *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; } ArrayPtr FlowGraphBuilder::GetOptionalParameterNames(const Function& function) { if (!function.HasOptionalNamedParameters()) { return Array::null(); } const intptr_t num_fixed_params = function.num_fixed_parameters(); const intptr_t num_opt_params = function.NumOptionalNamedParameters(); const auto& names = Array::Handle(Z, Array::New(num_opt_params, Heap::kOld)); auto& name = String::Handle(Z); for (intptr_t i = 0; i < num_opt_params; ++i) { name = function.ParameterNameAt(num_fixed_params + i); names.SetAt(i, name); } return names.ptr(); } Fragment FlowGraphBuilder::PushExplicitParameters( const Function& function, const Function& target /* = Function::null_function()*/) { Fragment instructions; for (intptr_t i = function.NumImplicitParameters(), n = function.NumParameters(); i < n; ++i) { Fragment push_param = LoadLocal(parsed_function_->ParameterVariable(i)); if (!target.IsNull() && target.is_unboxed_parameter_at(i)) { Representation to; if (target.is_unboxed_integer_parameter_at(i)) { to = kUnboxedInt64; } else { ASSERT(target.is_unboxed_double_parameter_at(i)); to = kUnboxedDouble; } const auto unbox = UnboxInstr::Create(to, Pop(), DeoptId::kNone, Instruction::kNotSpeculative); Push(unbox); push_param += Fragment(unbox); } instructions += push_param; } return instructions; } 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. const ArgumentsDescriptor descriptor(saved_args_desc_array()); 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()); // The receiver is the first argument to noSuchMethod, and it is the first // argument passed to the dispatcher function. body += LoadLocal(parsed_function_->ParameterVariable(0)); // 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())); // Second, the arguments descriptor. body += Constant(saved_args_desc_array()); // 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.Size()); 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(parsed_function_->ParameterVariable(i)); body += StoreIndexed(kArrayCid); } String& name = String::Handle(Z); for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) { const intptr_t parameter_index = descriptor.PositionAt(i); name = descriptor.NameAt(i); name = Symbols::New(H.thread(), name); body += LoadLocal(array); body += IntConstant(receiver_index + parameter_index); body += LoadLocal(parsed_function_->ParameterVariable(parameter_index)); body += StoreIndexed(kArrayCid); } // Fourth, false indicating this is not a super NoSuchMethod. body += Constant(Bool::False()); const Class& mirror_class = Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror())); ASSERT(!mirror_class.IsNull()); const auto& error = mirror_class.EnsureIsFinalized(H.thread()); ASSERT(error == Error::null()); 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); const int kTypeArgsLen = 0; ArgumentsDescriptor two_arguments( Array::Handle(Z, ArgumentsDescriptor::NewBoxed(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, IG->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); } // Information used by the various dynamic closure call fragment builders. struct FlowGraphBuilder::ClosureCallInfo { ClosureCallInfo(LocalVariable* closure, JoinEntryInstr* throw_no_such_method, const Array& arguments_descriptor_array, ParsedFunction::DynamicClosureCallVars* const vars) : closure(ASSERT_NOTNULL(closure)), throw_no_such_method(ASSERT_NOTNULL(throw_no_such_method)), descriptor(arguments_descriptor_array), vars(ASSERT_NOTNULL(vars)) {} LocalVariable* const closure; JoinEntryInstr* const throw_no_such_method; const ArgumentsDescriptor descriptor; ParsedFunction::DynamicClosureCallVars* const vars; // Set up by BuildDynamicCallChecks() when needed. These values are // read-only, so they don't need real local variables and are created // using MakeTemporary(). LocalVariable* signature = nullptr; LocalVariable* num_fixed_params = nullptr; LocalVariable* num_opt_params = nullptr; LocalVariable* num_max_params = nullptr; LocalVariable* has_named_params = nullptr; LocalVariable* parameter_names = nullptr; LocalVariable* parameter_types = nullptr; LocalVariable* type_parameters = nullptr; LocalVariable* instantiator_type_args = nullptr; LocalVariable* parent_function_type_args = nullptr; }; Fragment FlowGraphBuilder::TestClosureFunctionGeneric( const ClosureCallInfo& info, Fragment generic, Fragment not_generic) { JoinEntryInstr* after_branch = BuildJoinEntry(); Fragment check; check += LoadLocal(info.type_parameters); TargetEntryInstr *is_not_generic, *is_generic; check += BranchIfNull(&is_not_generic, &is_generic); generic.Prepend(is_generic); generic += Goto(after_branch); not_generic.Prepend(is_not_generic); not_generic += Goto(after_branch); return Fragment(check.entry, after_branch); } Fragment FlowGraphBuilder::TestClosureFunctionNamedParameterRequired( const ClosureCallInfo& info, Fragment set, Fragment not_set) { // Required named arguments only exist if null_safety is enabled. if (!IG->use_strict_null_safety_checks()) return not_set; Fragment check_required; // First, we convert the index to be in terms of the number of optional // parameters, not total parameters (to calculate the flag index and shift). check_required += LoadLocal(info.vars->current_param_index); check_required += LoadLocal(info.num_fixed_params); check_required += SmiBinaryOp(Token::kSUB, /*is_truncating=*/true); LocalVariable* opt_index = MakeTemporary("opt_index"); // Read-only. // Next, we calculate the index to dereference in the parameter names array. check_required += LoadLocal(opt_index); check_required += IntConstant(compiler::target::kNumParameterFlagsPerElementLog2); check_required += SmiBinaryOp(Token::kSHR); check_required += LoadLocal(info.num_max_params); check_required += SmiBinaryOp(Token::kADD); LocalVariable* flags_index = MakeTemporary("flags_index"); // Read-only. // Two read-only stack values (opt_index, flag_index) that must be dropped // after we rejoin at after_check. JoinEntryInstr* after_check = BuildJoinEntry(); // Now we check to see if the flags index is within the bounds of the // parameters names array. If not, it cannot be required. check_required += LoadLocal(flags_index); check_required += LoadLocal(info.parameter_names); check_required += LoadNativeField(Slot::Array_length()); check_required += SmiRelationalOp(Token::kLT); TargetEntryInstr *valid_index, *invalid_index; check_required += BranchIfTrue(&valid_index, &invalid_index); JoinEntryInstr* join_not_set = BuildJoinEntry(); Fragment(invalid_index) + Goto(join_not_set); // Otherwise, we need to retrieve the value. We're guaranteed the Smis in // the flag slots are non-null, so after loading we can immediate check // the required flag bit for the given named parameter. check_required.current = valid_index; check_required += LoadLocal(info.parameter_names); check_required += LoadLocal(flags_index); check_required += LoadIndexed(kArrayCid); check_required += LoadLocal(opt_index); check_required += IntConstant(compiler::target::kNumParameterFlagsPerElement - 1); check_required += SmiBinaryOp(Token::kBIT_AND); // If the below changes, we'll need to multiply by the number of parameter // flags before shifting. static_assert(compiler::target::kNumParameterFlags == 1, "IL builder assumes only one flag bit per parameter"); check_required += SmiBinaryOp(Token::kSHR); check_required += IntConstant(1 << compiler::target::kRequiredNamedParameterFlag); check_required += SmiBinaryOp(Token::kBIT_AND); check_required += IntConstant(0); TargetEntryInstr *is_not_set, *is_set; check_required += BranchIfEqual(&is_not_set, &is_set); Fragment(is_not_set) + Goto(join_not_set); set.Prepend(is_set); set += Goto(after_check); not_set.Prepend(join_not_set); not_set += Goto(after_check); // After rejoining, drop the introduced temporaries. check_required.current = after_check; check_required += DropTemporary(&flags_index); check_required += DropTemporary(&opt_index); return check_required; } Fragment FlowGraphBuilder::BuildClosureCallDefaultTypeHandling( const ClosureCallInfo& info) { if (info.descriptor.TypeArgsLen() > 0) { ASSERT(parsed_function_->function_type_arguments() != nullptr); // A TAV was provided, so we don't need default type argument handling // and can just take the arguments we were given. Fragment store_provided; store_provided += LoadLocal(parsed_function_->function_type_arguments()); store_provided += StoreLocal(info.vars->function_type_args); store_provided += Drop(); return store_provided; } // Load the defaults, instantiating or replacing them with the other type // arguments as appropriate. Fragment store_default; store_default += LoadLocal(info.closure); store_default += LoadNativeField(Slot::Closure_function()); store_default += LoadNativeField(Slot::Function_data()); LocalVariable* closure_data = MakeTemporary("closure_data"); store_default += LoadLocal(closure_data); const auto& slot = Slot::ClosureData_default_type_arguments_kind(); store_default += LoadNativeField(slot); store_default += Box(slot.representation()); LocalVariable* default_tav_kind = MakeTemporary("default_tav_kind"); // Two locals to drop after join, closure_data and default_tav_kind. JoinEntryInstr* done = BuildJoinEntry(); store_default += LoadLocal(default_tav_kind); TargetEntryInstr *is_instantiated, *is_not_instantiated; store_default += IntConstant(static_cast( ClosureData::DefaultTypeArgumentsKind::kIsInstantiated)); store_default += BranchIfEqual(&is_instantiated, &is_not_instantiated); store_default.current = is_not_instantiated; // Check next case. store_default += LoadLocal(default_tav_kind); TargetEntryInstr *needs_instantiation, *can_share; store_default += IntConstant(static_cast( ClosureData::DefaultTypeArgumentsKind::kNeedsInstantiation)); store_default += BranchIfEqual(&needs_instantiation, &can_share); store_default.current = can_share; // Check next case. store_default += LoadLocal(default_tav_kind); TargetEntryInstr *can_share_instantiator, *can_share_function; store_default += IntConstant(static_cast( ClosureData::DefaultTypeArgumentsKind::kSharesInstantiatorTypeArguments)); store_default += BranchIfEqual(&can_share_instantiator, &can_share_function); Fragment instantiated(is_instantiated); instantiated += LoadLocal(closure_data); instantiated += LoadNativeField(Slot::ClosureData_default_type_arguments()); instantiated += StoreLocal(info.vars->function_type_args); instantiated += Drop(); instantiated += Goto(done); Fragment do_instantiation(needs_instantiation); // Load the instantiator type arguments. do_instantiation += LoadLocal(info.instantiator_type_args); // Load the parent function type arguments. (No local function type arguments // can be used within the defaults). do_instantiation += LoadLocal(info.parent_function_type_args); // Load the default type arguments to instantiate. do_instantiation += LoadLocal(closure_data); do_instantiation += LoadNativeField(Slot::ClosureData_default_type_arguments()); do_instantiation += InstantiateDynamicTypeArguments(); do_instantiation += StoreLocal(info.vars->function_type_args); do_instantiation += Drop(); do_instantiation += Goto(done); Fragment share_instantiator(can_share_instantiator); share_instantiator += LoadLocal(info.instantiator_type_args); share_instantiator += StoreLocal(info.vars->function_type_args); share_instantiator += Drop(); share_instantiator += Goto(done); Fragment share_function(can_share_function); // Since the defaults won't have local type parameters, these must all be // from the parent function type arguments, so we can just use it. share_function += LoadLocal(info.parent_function_type_args); share_function += StoreLocal(info.vars->function_type_args); share_function += Drop(); share_function += Goto(done); store_default.current = done; // Return here after branching. store_default += DropTemporary(&default_tav_kind); store_default += DropTemporary(&closure_data); Fragment store_delayed; store_delayed += LoadLocal(info.closure); store_delayed += LoadNativeField(Slot::Closure_delayed_type_arguments()); store_delayed += StoreLocal(info.vars->function_type_args); store_delayed += Drop(); // Use the delayed type args if present, else the default ones. return TestDelayedTypeArgs(info.closure, store_delayed, store_default); } Fragment FlowGraphBuilder::BuildClosureCallNamedArgumentsCheck( const ClosureCallInfo& info) { // When no named arguments are provided, we just need to check for possible // required named arguments. if (info.descriptor.NamedCount() == 0) { // No work to do if there are no possible required named parameters. if (!IG->use_strict_null_safety_checks()) { return Fragment(); } // If the below changes, we can no longer assume that flag slots existing // means there are required parameters. static_assert(compiler::target::kNumParameterFlags == 1, "IL builder assumes only one flag bit per parameter"); // No named args were provided, so check for any required named params. // Here, we assume that the only parameter flag saved is the required bit // for named parameters. If this changes, we'll need to check each flag // entry appropriately for any set required bits. Fragment has_any; has_any += LoadLocal(info.num_max_params); has_any += LoadLocal(info.parameter_names); has_any += LoadNativeField(Slot::Array_length()); TargetEntryInstr *no_required, *has_required; has_any += BranchIfEqual(&no_required, &has_required); Fragment(has_required) + Goto(info.throw_no_such_method); return Fragment(has_any.entry, no_required); } // Otherwise, we need to loop through the parameter names to check the names // of named arguments for validity (and possibly missing required ones). Fragment check_names; check_names += LoadLocal(info.vars->current_param_index); LocalVariable* old_index = MakeTemporary("old_index"); // Read-only. check_names += LoadLocal(info.vars->current_num_processed); LocalVariable* old_processed = MakeTemporary("old_processed"); // Read-only. // Two local stack values (old_index, old_processed) to drop after rejoining // at done. JoinEntryInstr* loop = BuildJoinEntry(); JoinEntryInstr* done = BuildJoinEntry(); check_names += IntConstant(0); check_names += StoreLocal(info.vars->current_num_processed); check_names += Drop(); check_names += LoadLocal(info.num_fixed_params); check_names += StoreLocal(info.vars->current_param_index); check_names += Drop(); check_names += Goto(loop); Fragment loop_check(loop); loop_check += LoadLocal(info.vars->current_param_index); loop_check += LoadLocal(info.num_max_params); loop_check += SmiRelationalOp(Token::kLT); TargetEntryInstr *no_more, *more; loop_check += BranchIfTrue(&more, &no_more); Fragment(no_more) + Goto(done); Fragment loop_body(more); // First load the name we need to check against. loop_body += LoadLocal(info.parameter_names); loop_body += LoadLocal(info.vars->current_param_index); loop_body += LoadIndexed(kArrayCid); LocalVariable* param_name = MakeTemporary("param_name"); // Read only. // One additional local value on the stack within the loop body (param_name) // that should be dropped after rejoining at loop_incr. JoinEntryInstr* loop_incr = BuildJoinEntry(); // Now iterate over the ArgumentsDescriptor names and check for a match. for (intptr_t i = 0; i < info.descriptor.NamedCount(); i++) { const auto& name = String::ZoneHandle(Z, info.descriptor.NameAt(i)); loop_body += Constant(name); loop_body += LoadLocal(param_name); TargetEntryInstr *match, *mismatch; loop_body += BranchIfEqual(&match, &mismatch); loop_body.current = mismatch; // We have a match, so go to the next name after storing the corresponding // parameter index on the stack and incrementing the number of matched // arguments. (No need to check the required bit for provided parameters.) Fragment matched(match); matched += LoadLocal(info.vars->current_param_index); matched += StoreLocal(info.vars->named_argument_parameter_indices.At(i)); matched += Drop(); matched += LoadLocal(info.vars->current_num_processed); matched += IntConstant(1); matched += SmiBinaryOp(Token::kADD, /*is_truncating=*/true); matched += StoreLocal(info.vars->current_num_processed); matched += Drop(); matched += Goto(loop_incr); } // None of the names in the arguments descriptor matched, so check if this // is a required parameter. loop_body += TestClosureFunctionNamedParameterRequired( info, /*set=*/Goto(info.throw_no_such_method), /*not_set=*/{}); loop_body += Goto(loop_incr); Fragment incr_index(loop_incr); incr_index += DropTemporary(¶m_name); incr_index += LoadLocal(info.vars->current_param_index); incr_index += IntConstant(1); incr_index += SmiBinaryOp(Token::kADD, /*is_truncating=*/true); incr_index += StoreLocal(info.vars->current_param_index); incr_index += Drop(); incr_index += Goto(loop); Fragment check_processed(done); check_processed += LoadLocal(info.vars->current_num_processed); check_processed += IntConstant(info.descriptor.NamedCount()); TargetEntryInstr *all_processed, *bad_name; check_processed += BranchIfEqual(&all_processed, &bad_name); // Didn't find a matching parameter name for at least one argument name. Fragment(bad_name) + Goto(info.throw_no_such_method); // Drop the temporaries at the end of the fragment. check_names.current = all_processed; check_names += LoadLocal(old_processed); check_names += StoreLocal(info.vars->current_num_processed); check_names += Drop(); check_names += DropTemporary(&old_processed); check_names += LoadLocal(old_index); check_names += StoreLocal(info.vars->current_param_index); check_names += Drop(); check_names += DropTemporary(&old_index); return check_names; } Fragment FlowGraphBuilder::BuildClosureCallArgumentsValidCheck( const ClosureCallInfo& info) { Fragment check_entry; // We only need to check the length of any explicitly provided type arguments. if (info.descriptor.TypeArgsLen() > 0) { Fragment check_type_args_length; check_type_args_length += LoadLocal(info.type_parameters); TargetEntryInstr *null, *not_null; check_type_args_length += BranchIfNull(&null, ¬_null); check_type_args_length.current = not_null; // Continue in non-error case. check_type_args_length += LoadLocal(info.type_parameters); check_type_args_length += LoadNativeField(Slot::TypeArguments_length()); check_type_args_length += IntConstant(info.descriptor.TypeArgsLen()); TargetEntryInstr *equal, *not_equal; check_type_args_length += BranchIfEqual(&equal, ¬_equal); check_type_args_length.current = equal; // Continue in non-error case. // The function is not generic. Fragment(null) + Goto(info.throw_no_such_method); // An incorrect number of type arguments were passed. Fragment(not_equal) + Goto(info.throw_no_such_method); // Type arguments should not be provided if there are delayed type // arguments, as then the closure itself is not generic. check_entry += TestDelayedTypeArgs( info.closure, /*present=*/Goto(info.throw_no_such_method), /*absent=*/check_type_args_length); } check_entry += LoadLocal(info.has_named_params); TargetEntryInstr *has_named, *has_positional; check_entry += BranchIfTrue(&has_named, &has_positional); JoinEntryInstr* join_after_optional = BuildJoinEntry(); check_entry.current = join_after_optional; if (info.descriptor.NamedCount() > 0) { // No reason to continue checking, as this function doesn't take named args. Fragment(has_positional) + Goto(info.throw_no_such_method); } else { Fragment check_pos(has_positional); check_pos += LoadLocal(info.num_fixed_params); check_pos += IntConstant(info.descriptor.PositionalCount()); check_pos += SmiRelationalOp(Token::kLTE); TargetEntryInstr *enough, *too_few; check_pos += BranchIfTrue(&enough, &too_few); check_pos.current = enough; Fragment(too_few) + Goto(info.throw_no_such_method); check_pos += IntConstant(info.descriptor.PositionalCount()); check_pos += LoadLocal(info.num_max_params); check_pos += SmiRelationalOp(Token::kLTE); TargetEntryInstr *valid, *too_many; check_pos += BranchIfTrue(&valid, &too_many); check_pos.current = valid; Fragment(too_many) + Goto(info.throw_no_such_method); check_pos += Goto(join_after_optional); } Fragment check_named(has_named); TargetEntryInstr *same, *different; check_named += LoadLocal(info.num_fixed_params); check_named += IntConstant(info.descriptor.PositionalCount()); check_named += BranchIfEqual(&same, &different); check_named.current = same; Fragment(different) + Goto(info.throw_no_such_method); if (info.descriptor.NamedCount() > 0) { check_named += IntConstant(info.descriptor.NamedCount()); check_named += LoadLocal(info.num_opt_params); check_named += SmiRelationalOp(Token::kLTE); TargetEntryInstr *valid, *too_many; check_named += BranchIfTrue(&valid, &too_many); check_named.current = valid; Fragment(too_many) + Goto(info.throw_no_such_method); } // Check the names for optional arguments. If applicable, also check that all // required named parameters are provided. check_named += BuildClosureCallNamedArgumentsCheck(info); check_named += Goto(join_after_optional); check_entry.current = join_after_optional; return check_entry; } Fragment FlowGraphBuilder::BuildClosureCallTypeArgumentsTypeCheck( const ClosureCallInfo& info) { JoinEntryInstr* done = BuildJoinEntry(); JoinEntryInstr* loop = BuildJoinEntry(); // We assume that the value stored in :t_type_parameters is not null (i.e., // the function stored in :t_function is generic). Fragment loop_init; // Loop over the type parameters array. loop_init += IntConstant(0); loop_init += StoreLocal(info.vars->current_param_index); loop_init += Drop(); loop_init += Goto(loop); Fragment loop_check(loop); loop_check += LoadLocal(info.vars->current_param_index); loop_check += LoadLocal(info.type_parameters); loop_check += LoadNativeField(Slot::TypeArguments_length()); loop_check += SmiRelationalOp(Token::kLT); TargetEntryInstr *more, *no_more; loop_check += BranchIfTrue(&more, &no_more); Fragment(no_more) + Goto(done); Fragment loop_body(more); loop_body += LoadLocal(info.type_parameters); loop_body += LoadLocal(info.vars->current_param_index); loop_body += LoadIndexed( kTypeArgumentsCid, /*index_scale*/ compiler::target::kCompressedWordSize); LocalVariable* current_param = MakeTemporary("current_param"); // Read-only. // One read-only local variable on stack (param) to drop after joining. JoinEntryInstr* next = BuildJoinEntry(); loop_body += LoadLocal(current_param); loop_body += LoadNativeField(Slot::TypeParameter_flags()); loop_body += Box(kUnboxedUint8); loop_body += IntConstant( UntaggedTypeParameter::GenericCovariantImplBit::mask_in_place()); loop_body += SmiBinaryOp(Token::kBIT_AND); loop_body += IntConstant(0); TargetEntryInstr *is_noncovariant, *is_covariant; loop_body += BranchIfEqual(&is_noncovariant, &is_covariant); Fragment(is_covariant) + Goto(next); // Continue if covariant. loop_body.current = is_noncovariant; // Type check if non-covariant. loop_body += LoadLocal(info.instantiator_type_args); loop_body += LoadLocal(info.vars->function_type_args); // Load parameter. loop_body += LoadLocal(current_param); // Load bounds from parameter. loop_body += LoadLocal(current_param); loop_body += LoadNativeField(Slot::TypeParameter_bound()); // Load name from parameter. loop_body += LoadLocal(current_param); loop_body += LoadNativeField(Slot::TypeParameter_name()); // Assert that the type the parameter is instantiated as is consistent with // the bounds of the parameter. loop_body += AssertSubtype(TokenPosition::kNoSource); loop_body += Goto(next); Fragment loop_incr(next); loop_incr += DropTemporary(¤t_param); loop_incr += LoadLocal(info.vars->current_param_index); loop_incr += IntConstant(1); loop_incr += SmiBinaryOp(Token::kADD, /*is_truncating=*/true); loop_incr += StoreLocal(info.vars->current_param_index); loop_incr += Drop(); loop_incr += Goto(loop); return Fragment(loop_init.entry, done); } Fragment FlowGraphBuilder::BuildClosureCallArgumentTypeCheck( const ClosureCallInfo& info, LocalVariable* param_index, intptr_t arg_index, const String& arg_name) { Fragment instructions; // Load value. instructions += LoadLocal(parsed_function_->ParameterVariable(arg_index)); // Load destination type. instructions += LoadLocal(info.parameter_types); instructions += LoadLocal(param_index); instructions += LoadIndexed(kArrayCid); // Load instantiator type arguments. instructions += LoadLocal(info.instantiator_type_args); // Load the full set of function type arguments. instructions += LoadLocal(info.vars->function_type_args); // Check that the value has the right type. instructions += AssertAssignable(TokenPosition::kNoSource, arg_name, AssertAssignableInstr::kParameterCheck); // Make sure to store the result to keep data dependencies accurate. instructions += StoreLocal(parsed_function_->ParameterVariable(arg_index)); instructions += Drop(); return instructions; } Fragment FlowGraphBuilder::BuildClosureCallArgumentTypeChecks( const ClosureCallInfo& info) { Fragment instructions; // Only check explicit arguments (i.e., skip the receiver), as the receiver // is always assignable to its type (stored as dynamic). for (intptr_t i = 1; i < info.descriptor.PositionalCount(); i++) { instructions += IntConstant(i); LocalVariable* param_index = MakeTemporary("param_index"); // We don't have a compile-time name, so this symbol signals the runtime // that it should recreate the type check using info from the stack. instructions += BuildClosureCallArgumentTypeCheck( info, param_index, i, Symbols::dynamic_assert_assignable_stc_check()); instructions += DropTemporary(¶m_index); } for (intptr_t i = 0; i < info.descriptor.NamedCount(); i++) { const intptr_t arg_index = info.descriptor.PositionAt(i); const auto& arg_name = String::ZoneHandle(Z, info.descriptor.NameAt(i)); auto const param_index = info.vars->named_argument_parameter_indices.At(i); instructions += BuildClosureCallArgumentTypeCheck(info, param_index, arg_index, arg_name); } return instructions; } Fragment FlowGraphBuilder::BuildDynamicClosureCallChecks( LocalVariable* closure) { ClosureCallInfo info(closure, BuildThrowNoSuchMethod(), saved_args_desc_array(), parsed_function_->dynamic_closure_call_vars()); Fragment body; body += LoadLocal(info.closure); body += LoadNativeField(Slot::Closure_function()); body += LoadNativeField(Slot::Function_signature()); info.signature = MakeTemporary("signature"); body += LoadLocal(info.signature); body += BuildExtractUnboxedSlotBitFieldIntoSmi< FunctionType::PackedNumFixedParameters>( Slot::FunctionType_packed_fields()); info.num_fixed_params = MakeTemporary("num_fixed_params"); body += LoadLocal(info.signature); body += BuildExtractUnboxedSlotBitFieldIntoSmi< FunctionType::PackedNumOptionalParameters>( Slot::FunctionType_packed_fields()); info.num_opt_params = MakeTemporary("num_opt_params"); body += LoadLocal(info.num_fixed_params); body += LoadLocal(info.num_opt_params); body += SmiBinaryOp(Token::kADD); info.num_max_params = MakeTemporary("num_max_params"); body += LoadLocal(info.signature); body += BuildExtractUnboxedSlotBitFieldIntoSmi< FunctionType::PackedHasNamedOptionalParameters>( Slot::FunctionType_packed_fields()); body += IntConstant(0); body += StrictCompare(Token::kNE_STRICT); info.has_named_params = MakeTemporary("has_named_params"); body += LoadLocal(info.signature); body += LoadNativeField(Slot::FunctionType_parameter_names()); info.parameter_names = MakeTemporary("parameter_names"); body += LoadLocal(info.signature); body += LoadNativeField(Slot::FunctionType_parameter_types()); info.parameter_types = MakeTemporary("parameter_types"); body += LoadLocal(info.signature); body += LoadNativeField(Slot::FunctionType_type_parameters()); info.type_parameters = MakeTemporary("type_parameters"); body += LoadLocal(info.closure); body += LoadNativeField(Slot::Closure_instantiator_type_arguments()); info.instantiator_type_args = MakeTemporary("instantiator_type_args"); body += LoadLocal(info.closure); body += LoadNativeField(Slot::Closure_function_type_arguments()); info.parent_function_type_args = MakeTemporary("parent_function_type_args"); // At this point, all the read-only temporaries stored in the ClosureCallInfo // should be either loaded or still nullptr, if not needed for this function. // Now we check that the arguments to the closure call have the right shape. body += BuildClosureCallArgumentsValidCheck(info); // If the closure function is not generic, there are no local function type // args. Thus, use whatever was stored for the parent function type arguments, // which has already been checked against any parent type parameter bounds. Fragment not_generic; not_generic += LoadLocal(info.parent_function_type_args); not_generic += StoreLocal(info.vars->function_type_args); not_generic += Drop(); // If the closure function is generic, then we first need to calculate the // full set of function type arguments, then check the local function type // arguments against the closure function's type parameter bounds. Fragment generic; // Calculate the local function type arguments and store them in // info.vars->function_type_args. generic += BuildClosureCallDefaultTypeHandling(info); // Load the local function type args. generic += LoadLocal(info.vars->function_type_args); // Load the parent function type args. generic += LoadLocal(info.parent_function_type_args); // Load the number of parent type parameters. generic += LoadLocal(info.signature); generic += BuildExtractUnboxedSlotBitFieldIntoSmi< UntaggedFunctionType::PackedNumParentTypeArguments>( Slot::FunctionType_packed_fields()); // Load the number of total type parameters. LocalVariable* num_parents = MakeTemporary(); generic += LoadLocal(info.type_parameters); generic += LoadNativeField(Slot::TypeArguments_length()); generic += LoadLocal(num_parents); generic += SmiBinaryOp(Token::kADD, /*is_truncating=*/true); // Call the static function for prepending type arguments. generic += StaticCall(TokenPosition::kNoSource, PrependTypeArgumentsFunction(), 4, ICData::kStatic); generic += StoreLocal(info.vars->function_type_args); generic += Drop(); // Now that we have the full set of function type arguments, check them // against the type parameter bounds. However, if the local function type // arguments are delayed type arguments, they have already been checked by // the type system and need not be checked again at the call site. auto const check_bounds = BuildClosureCallTypeArgumentsTypeCheck(info); if (FLAG_eliminate_type_checks) { generic += TestDelayedTypeArgs(info.closure, /*present=*/{}, /*absent=*/check_bounds); } else { generic += check_bounds; } // Call the appropriate fragment for setting up the function type arguments // and performing any needed type argument checking. body += TestClosureFunctionGeneric(info, generic, not_generic); // Check that the values provided as arguments are assignable to the types // of the corresponding closure function parameters. body += BuildClosureCallArgumentTypeChecks(info); // Drop all the read-only temporaries at the end of the fragment. body += DropTemporary(&info.parent_function_type_args); body += DropTemporary(&info.instantiator_type_args); body += DropTemporary(&info.type_parameters); body += DropTemporary(&info.parameter_types); body += DropTemporary(&info.parameter_names); body += DropTemporary(&info.has_named_params); body += DropTemporary(&info.num_max_params); body += DropTemporary(&info.num_opt_params); body += DropTemporary(&info.num_fixed_params); body += DropTemporary(&info.signature); return body; } FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher( const Function& function) { const ArgumentsDescriptor descriptor(saved_args_desc_array()); // Find the name of the field we should dispatch to. const Class& owner = Class::Handle(Z, function.Owner()); ASSERT(!owner.IsNull()); auto& field_name = String::Handle(Z, function.name()); // If the field name has a dyn: tag, then remove it. We don't add dynamic // invocation forwarders for field getters used for invoking, we just use // the tag in the name of the invoke field dispatcher to detect dynamic calls. const bool is_dynamic_call = Function::IsDynamicInvocationForwarderName(field_name); if (is_dynamic_call) { field_name = Function::DemangleDynamicInvocationForwarderName(field_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, IG->object_store()->closure_class()); const bool is_closure_call = (owner.ptr() == closure_class.ptr()) && field_name.Equals(Symbols::Call()); 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()); // Build any dynamic closure call checks before pushing arguments to the // final call on the stack to make debugging easier. LocalVariable* closure = nullptr; if (is_closure_call) { closure = parsed_function_->ParameterVariable(0); if (is_dynamic_call) { // The whole reason for making this invoke field dispatcher is that // this closure call needs checking, so we shouldn't inline a call to an // unchecked entry that can't tail call NSM. InlineBailout( "kernel::FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher"); body += BuildDynamicClosureCallChecks(closure); } } if (descriptor.TypeArgsLen() > 0) { LocalVariable* type_args = parsed_function_->function_type_arguments(); ASSERT(type_args != nullptr); body += LoadLocal(type_args); } if (is_closure_call) { // The closure itself is the first argument. body += LoadLocal(closure); } else { // Invoke the getter to get the field value. body += LoadLocal(parsed_function_->ParameterVariable(0)); const intptr_t kTypeArgsLen = 0; const intptr_t kNumArgsChecked = 1; body += InstanceCall(TokenPosition::kMinSource, getter_name, Token::kGET, kTypeArgsLen, 1, Array::null_array(), kNumArgsChecked); } // Push all arguments onto the stack. for (intptr_t pos = 1; pos < descriptor.Count(); pos++) { body += LoadLocal(parsed_function_->ParameterVariable(pos)); } // Construct argument names array if necessary. const Array* argument_names = &Object::null_array(); if (descriptor.NamedCount() > 0) { const auto& array_handle = Array::ZoneHandle(Z, Array::New(descriptor.NamedCount(), Heap::kNew)); String& string_handle = String::Handle(Z); for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) { const intptr_t named_arg_index = descriptor.PositionAt(i) - descriptor.PositionalCount(); string_handle = descriptor.NameAt(i); array_handle.SetAt(named_arg_index, string_handle); } argument_names = &array_handle; } 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, is_dynamic_call ? Symbols::DynamicCall() : Symbols::Call(), Token::kILLEGAL, descriptor.TypeArgsLen(), descriptor.Count(), *argument_names, kNumArgsChecked); } 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_size()); } else { ASSERT(function.NumOptionalParameters() == 0); body += IntConstant(function.NumParameters()); } body += LoadLocal(parsed_function_->current_context_var()); body += LoadNativeField(Slot::GetContextVariableSlotFor( thread_, *parsed_function_->receiver_var())); body += StoreFpRelativeSlot( kWordSize * compiler::target::frame_layout.param_end_from_fp); } if (function.NeedsTypeArgumentTypeChecks()) { BuildTypeArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds, &body); } if (function.NeedsArgumentTypeChecks()) { BuildArgumentTypeChecks(&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_size()); } 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(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(compiler::target::kWordSize * compiler::target::frame_layout.param_end_from_fp, CompileType::Dynamic()); 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)); type = ClassFinalizer::FinalizeType(type); body += Constant(type); } else { body += LoadLocal(parsed_function_->current_context_var()); body += LoadNativeField(Slot::GetContextVariableSlotFor( thread_, *parsed_function_->receiver_var())); } } else { body += LoadLocal(parsed_function_->ParameterVariable(0)); } body += Constant(String::ZoneHandle(Z, function.name())); 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::NewBoxed(0, function.NumParameters())); body += Constant(args_desc); } else { body += LoadArgDescriptor(); } body += LoadLocal(arguments); 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(); } // Push the number of delayed type arguments. if (function.IsClosureFunction()) { LocalVariable* closure = parsed_function_->ParameterVariable(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); } const Class& mirror_class = Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror())); ASSERT(!mirror_class.IsNull()); const auto& error = mirror_class.EnsureIsFinalized(H.thread()); ASSERT(error == Error::null()); 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); if (throw_no_such_method_error) { const Class& klass = Class::ZoneHandle( Z, Library::LookupCoreClass(Symbols::NoSuchMethodError())); ASSERT(!klass.IsNull()); const auto& error = klass.EnsureIsFinalized(H.thread()); ASSERT(error == Error::null()); 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); } 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.IsTopTypeForSubtyping()) { body += AssertAssignableLoadTypeArguments(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()) { auto& default_types = TypeArguments::ZoneHandle(Z, function.InstantiateToBounds(thread_)); 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(); } 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(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(UncheckedEntryPointStyle::kSharedWithVariable)); extra_entry += StoreLocal(TokenPosition::kNoSource, parsed_function_->entry_points_temp_var()); extra_entry += Drop(); extra_entry += Goto(join_entry); if (prologue_start != nullptr) { join_entry->LinkTo(prologue_start); } else { // Prologue is empty. shared_prologue_linked_in.current = join_entry; } 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(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(UncheckedEntryPointStyle::kNone)); normal += BuildEntryPointsIntrospection(); normal += normal_prologue; normal += Goto(join_entry); Fragment extra(extra_entry); extra += IntConstant(static_cast(UncheckedEntryPointStyle::kSeparate)); extra += BuildEntryPointsIntrospection(); extra += extra_prologue; extra += Goto(join_entry); Fragment(join_entry) + shared_prologue + body; return extra_entry; } FlowGraph* FlowGraphBuilder::BuildGraphOfImplicitClosureFunction( const Function& function) { const Function& parent = Function::ZoneHandle(Z, function.parent_function()); Function& target = Function::ZoneHandle(Z, function.ImplicitClosureTarget(Z)); 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); Fragment closure(instruction_cursor); closure += CheckStackOverflowInPrologue(function.token_pos()); closure += BuildDefaultTypeHandling(function); // For implicit closure functions, any non-covariant checks are either // performed by the type system or a dynamic invocation layer (dynamic closure // call dispatcher, mirror, etc.). Static targets never have covariant // arguments, and for non-static targets, they already perform the covariant // checks internally. Thus, no checks are needed and we just need to invoke // the target with the right receiver (unless static). // // TODO(dartbug.com/44195): Consider replacing the argument pushes + static // call with stack manipulation and a tail call instead. intptr_t type_args_len = 0; if (function.IsGeneric()) { type_args_len = function.NumTypeParameters(); ASSERT(parsed_function_->function_type_arguments() != NULL); closure += LoadLocal(parsed_function_->function_type_arguments()); } // Push receiver. if (!target.is_static()) { // The context has a fixed shape: a single variable which is the // closed-over receiver. closure += LoadLocal(parsed_function_->ParameterVariable(0)); closure += LoadNativeField(Slot::Closure_context()); closure += LoadNativeField(Slot::GetContextVariableSlotFor( thread_, *parsed_function_->receiver_var())); } closure += PushExplicitParameters(function); // Forward parameters to the target. intptr_t argument_count = function.NumParameters() - function.NumImplicitParameters() + (target.is_static() ? 0 : 1); ASSERT(argument_count == target.NumParameters()); Array& argument_names = Array::ZoneHandle(Z, GetOptionalParameterNames(function)); closure += StaticCall(TokenPosition::kNoSource, target, argument_count, argument_names, ICData::kNoRebind, /* result_type = */ NULL, type_args_len); // Return the result. closure += Return(function.end_token_pos()); return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, prologue_info); } FlowGraph* FlowGraphBuilder::BuildGraphOfFieldAccessor( const Function& function) { ASSERT(function.IsImplicitGetterOrSetter() || function.IsDynamicInvocationForwarder()); // Instead of building a dynamic invocation forwarder that checks argument // type and then invokes original setter we simply generate the type check // and inlined field store. Scope builder takes care of setting correct // type check mode in this case. const auto& target = Function::Handle( Z, function.IsDynamicInvocationForwarder() ? function.ForwardingTarget() : function.ptr()); ASSERT(target.IsImplicitGetterOrSetter()); const bool is_method = !function.IsStaticFunction(); const bool is_setter = target.IsImplicitSetterFunction(); const bool is_getter = target.IsImplicitGetterFunction() || target.IsImplicitStaticGetterFunction(); ASSERT(is_setter || is_getter); const auto& field = Field::ZoneHandle(Z, target.accessor_field()); 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); if (is_setter) { auto const setter_value = parsed_function_->ParameterVariable(is_method ? 1 : 0); if (is_method) { body += LoadLocal(parsed_function_->ParameterVariable(0)); } body += LoadLocal(setter_value); // The dyn:* forwarder has to check the parameters that the // actual target will not check. // Though here we manually inline the target, so the dyn:* forwarder has to // check all parameters. const bool needs_type_check = function.IsDynamicInvocationForwarder() || setter_value->needs_type_check(); if (needs_type_check) { body += CheckAssignable(setter_value->type(), setter_value->name(), AssertAssignableInstr::kParameterCheck); } body += BuildNullAssertions(); if (field.is_late()) { if (is_method) { body += Drop(); } body += Drop(); body += StoreLateField( field, is_method ? parsed_function_->ParameterVariable(0) : nullptr, setter_value); } else { if (is_method) { body += StoreInstanceFieldGuarded( field, StoreInstanceFieldInstr::Kind::kOther); } else { body += StoreStaticField(TokenPosition::kNoSource, field); } } body += NullConstant(); } else if (is_getter && is_method) { ASSERT(!field.needs_load_guard() NOT_IN_PRODUCT(|| IG->HasAttemptedReload())); body += LoadLocal(parsed_function_->ParameterVariable(0)); body += LoadField( field, /*calls_initializer=*/field.NeedsInitializationCheckOnLoad()); if (field.needs_load_guard()) { #if defined(PRODUCT) UNREACHABLE(); #else body += CheckAssignable(AbstractType::Handle(Z, field.type()), Symbols::FunctionResult()); #endif } } else if (field.is_const()) { const auto& value = Object::Handle(Z, field.StaticConstFieldValue()); if (value.IsError()) { Report::LongJump(Error::Cast(value)); } body += Constant(Instance::ZoneHandle(Z, Instance::RawCast(value.ptr()))); } else { // Static fields // - with trivial initializer // - without initializer if they are not late // are initialized eagerly and do not have implicit getters. // Static fields with non-trivial initializer need getter to perform // lazy initialization. Late fields without initializer need getter // to make sure they are already initialized. ASSERT(field.has_nontrivial_initializer() || (field.is_late() && !field.has_initializer())); body += LoadStaticField(field, /*calls_initializer=*/true); if (field.needs_load_guard()) { #if defined(PRODUCT) UNREACHABLE(); #else ASSERT(IsolateGroup::Current()->HasAttemptedReload()); body += CheckAssignable(AbstractType::Handle(Z, field.type()), Symbols::FunctionResult()); #endif } } body += Return(TokenPosition::kNoSource); PrologueInfo prologue_info(-1, -1); return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, prologue_info); } FlowGraph* FlowGraphBuilder::BuildGraphOfDynamicInvocationForwarder( const Function& function) { auto& name = String::Handle(Z, function.name()); name = Function::DemangleDynamicInvocationForwarderName(name); const auto& target = Function::ZoneHandle(Z, function.ForwardingTarget()); ASSERT(!target.IsNull()); if (target.IsImplicitSetterFunction() || target.IsImplicitGetterFunction()) { return BuildGraphOfFieldAccessor(function); } if (target.IsMethodExtractor()) { return BuildGraphOfMethodExtractor(target); } graph_entry_ = new (Z) GraphEntryInstr(*parsed_function_, osr_id_); auto normal_entry = BuildFunctionEntry(graph_entry_); graph_entry_->set_normal_entry(normal_entry); PrologueInfo prologue_info(-1, -1); auto instruction_cursor = BuildPrologue(normal_entry, &prologue_info); Fragment body; if (!function.is_native()) { body += CheckStackOverflowInPrologue(function.token_pos()); } ASSERT(parsed_function_->scope()->num_context_variables() == 0); // Should never build a dynamic invocation forwarder for equality // operator. ASSERT(function.name() != Symbols::EqualOperator().ptr()); // Even if the caller did not pass argument vector we would still // call the target with instantiate-to-bounds type arguments. body += BuildDefaultTypeHandling(function); // Build argument type checks that complement those that are emitted in the // target. BuildTypeArgumentTypeChecks( TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds, &body); BuildArgumentTypeChecks(&body, &body, nullptr); // Push all arguments and invoke the original method. intptr_t type_args_len = 0; if (function.IsGeneric()) { type_args_len = function.NumTypeParameters(); ASSERT(parsed_function_->function_type_arguments() != nullptr); body += LoadLocal(parsed_function_->function_type_arguments()); } // Push receiver. ASSERT(function.NumImplicitParameters() == 1); body += LoadLocal(parsed_function_->receiver_var()); body += PushExplicitParameters(function, target); const intptr_t argument_count = function.NumParameters(); const auto& argument_names = Array::ZoneHandle(Z, GetOptionalParameterNames(function)); body += StaticCall(TokenPosition::kNoSource, target, argument_count, argument_names, ICData::kNoRebind, nullptr, type_args_len); if (target.has_unboxed_integer_return()) { body += Box(kUnboxedInt64); } else if (target.has_unboxed_double_return()) { body += Box(kUnboxedDouble); } // Later optimization passes assume that result of a x.[]=(...) call is not // used. We must guarantee this invariant because violation will lead to an // illegal IL once we replace x.[]=(...) with a sequence that does not // actually produce any value. See http://dartbug.com/29135 for more details. if (name.ptr() == Symbols::AssignIndexToken().ptr()) { body += Drop(); body += NullConstant(); } body += Return(TokenPosition::kNoSource); instruction_cursor->LinkTo(body.entry); // When compiling for OSR, use a depth first search to find the OSR // entry and make graph entry jump to it instead of normal entry. // Catch entries are always considered reachable, even if they // become unreachable after OSR. if (IsCompiledForOsr()) { graph_entry_->RelinkToOsrEntry(Z, last_used_block_id_ + 1); } return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, prologue_info); } void FlowGraphBuilder::SetConstantRangeOfCurrentDefinition( const Fragment& fragment, int64_t min, int64_t max) { ASSERT(fragment.current->IsDefinition()); Range range(RangeBoundary::FromConstant(min), RangeBoundary::FromConstant(max)); fragment.current->AsDefinition()->set_range(range); } static classid_t TypedDataCidUnboxed(Representation unboxed_representation) { switch (unboxed_representation) { case kUnboxedFloat: // Note kTypedDataFloat32ArrayCid loads kUnboxedDouble. UNREACHABLE(); return kTypedDataFloat32ArrayCid; case kUnboxedInt32: return kTypedDataInt32ArrayCid; case kUnboxedUint32: return kTypedDataUint32ArrayCid; case kUnboxedInt64: return kTypedDataInt64ArrayCid; case kUnboxedDouble: return kTypedDataFloat64ArrayCid; default: UNREACHABLE(); } UNREACHABLE(); } Fragment FlowGraphBuilder::StoreIndexedTypedDataUnboxed( Representation unboxed_representation, intptr_t index_scale, bool index_unboxed) { ASSERT(unboxed_representation == kUnboxedInt32 || unboxed_representation == kUnboxedUint32 || unboxed_representation == kUnboxedInt64 || unboxed_representation == kUnboxedFloat || unboxed_representation == kUnboxedDouble); Fragment fragment; if (unboxed_representation == kUnboxedFloat) { fragment += BitCast(kUnboxedFloat, kUnboxedInt32); unboxed_representation = kUnboxedInt32; } fragment += StoreIndexedTypedData(TypedDataCidUnboxed(unboxed_representation), index_scale, index_unboxed); return fragment; } Fragment FlowGraphBuilder::LoadIndexedTypedDataUnboxed( Representation unboxed_representation, intptr_t index_scale, bool index_unboxed) { ASSERT(unboxed_representation == kUnboxedInt32 || unboxed_representation == kUnboxedUint32 || unboxed_representation == kUnboxedInt64 || unboxed_representation == kUnboxedFloat || unboxed_representation == kUnboxedDouble); Representation representation_for_load = unboxed_representation; if (unboxed_representation == kUnboxedFloat) { representation_for_load = kUnboxedInt32; } Fragment fragment; fragment += LoadIndexed(TypedDataCidUnboxed(representation_for_load), index_scale, index_unboxed); if (unboxed_representation == kUnboxedFloat) { fragment += BitCast(kUnboxedInt32, kUnboxedFloat); } return fragment; } Fragment FlowGraphBuilder::EnterHandleScope() { auto* instr = new (Z) EnterHandleScopeInstr(EnterHandleScopeInstr::Kind::kEnterHandleScope); Push(instr); return Fragment(instr); } Fragment FlowGraphBuilder::GetTopHandleScope() { auto* instr = new (Z) EnterHandleScopeInstr(EnterHandleScopeInstr::Kind::kGetTopHandleScope); Push(instr); return Fragment(instr); } Fragment FlowGraphBuilder::ExitHandleScope() { auto* instr = new (Z) ExitHandleScopeInstr(); return Fragment(instr); } Fragment FlowGraphBuilder::AllocateHandle(LocalVariable* api_local_scope) { Fragment code; if (api_local_scope != nullptr) { // Use the reference the scope we created in the trampoline. code += LoadLocal(api_local_scope); } else { // Or get a reference to the top handle scope. code += GetTopHandleScope(); } Value* api_local_scope_value = Pop(); auto* instr = new (Z) AllocateHandleInstr(api_local_scope_value); Push(instr); code <<= instr; return code; } Fragment FlowGraphBuilder::RawStoreField(int32_t offset) { Fragment code; Value* value = Pop(); Value* base = Pop(); auto* instr = new (Z) RawStoreFieldInstr(base, value, offset); code <<= instr; return code; } Fragment FlowGraphBuilder::WrapHandle(LocalVariable* api_local_scope) { Fragment code; LocalVariable* object = MakeTemporary(); code += AllocateHandle(api_local_scope); code += LoadLocal(MakeTemporary()); // Duplicate handle pointer. code += ConvertUnboxedToUntagged(kUnboxedIntPtr); code += LoadLocal(object); code += RawStoreField(compiler::target::LocalHandle::ptr_offset()); code += DropTempsPreserveTop(1); // Drop object below handle. return code; } Fragment FlowGraphBuilder::UnwrapHandle() { Fragment code; code += ConvertUnboxedToUntagged(kUnboxedIntPtr); code += IntConstant(compiler::target::LocalHandle::ptr_offset()); code += UnboxTruncate(kUnboxedIntPtr); code += LoadIndexed(kArrayCid, /*index_scale=*/1, /*index_unboxed=*/true); return code; } Fragment FlowGraphBuilder::UnhandledException() { const auto class_table = thread_->isolate_group()->class_table(); ASSERT(class_table->HasValidClassAt(kUnhandledExceptionCid)); const auto& klass = Class::ZoneHandle(H.zone(), class_table->At(kUnhandledExceptionCid)); ASSERT(!klass.IsNull()); Fragment body; body += AllocateObject(TokenPosition::kNoSource, klass, 0); LocalVariable* error_instance = MakeTemporary(); body += LoadLocal(error_instance); body += LoadLocal(CurrentException()); body += StoreInstanceField( TokenPosition::kNoSource, Slot::UnhandledException_exception(), StoreInstanceFieldInstr::Kind::kInitializing, kNoStoreBarrier); body += LoadLocal(error_instance); body += LoadLocal(CurrentStackTrace()); body += StoreInstanceField( TokenPosition::kNoSource, Slot::UnhandledException_stacktrace(), StoreInstanceFieldInstr::Kind::kInitializing, kNoStoreBarrier); return body; } Fragment FlowGraphBuilder::UnboxTruncate(Representation to) { auto* unbox = UnboxInstr::Create(to, Pop(), DeoptId::kNone, Instruction::kNotSpeculative); Push(unbox); return Fragment(unbox); } Fragment FlowGraphBuilder::NativeReturn( const compiler::ffi::CallbackMarshaller& marshaller) { auto* instr = new (Z) NativeReturnInstr(InstructionSource(), Pop(), marshaller, DeoptId::kNone); return Fragment(instr).closed(); } Fragment FlowGraphBuilder::FfiPointerFromAddress(const Type& result_type) { LocalVariable* address = MakeTemporary(); LocalVariable* result = parsed_function_->expression_temp_var(); Class& result_class = Class::ZoneHandle(Z, result_type.type_class()); // This class might only be instantiated as a return type of ffi calls. result_class.EnsureIsFinalized(thread_); TypeArguments& args = TypeArguments::ZoneHandle(Z, result_type.arguments()); // A kernel transform for FFI in the front-end ensures that type parameters // do not appear in the type arguments to a any Pointer classes in an FFI // signature. ASSERT(args.IsNull() || args.IsInstantiated()); args = args.Canonicalize(thread_, nullptr); Fragment code; code += Constant(args); code += AllocateObject(TokenPosition::kNoSource, result_class, 1); LocalVariable* pointer = MakeTemporary(); code += LoadLocal(pointer); code += LoadLocal(address); code += UnboxTruncate(kUnboxedFfiIntPtr); code += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr); code += StoreUntagged(compiler::target::Pointer::data_field_offset()); code += StoreLocal(TokenPosition::kNoSource, result); code += Drop(); // StoreLocal^ code += Drop(); // address code += LoadLocal(result); return code; } Fragment FlowGraphBuilder::BitCast(Representation from, Representation to) { BitCastInstr* instr = new (Z) BitCastInstr(from, to, Pop()); Push(instr); return Fragment(instr); } Fragment FlowGraphBuilder::WrapTypedDataBaseInCompound( const AbstractType& compound_type) { const auto& compound_sub_class = Class::ZoneHandle(Z, compound_type.type_class()); compound_sub_class.EnsureIsFinalized(thread_); const auto& lib_ffi = Library::Handle(Z, Library::FfiLibrary()); const auto& compound_class = Class::Handle(Z, lib_ffi.LookupClassAllowPrivate(Symbols::Compound())); const auto& compound_typed_data_base = Field::ZoneHandle(Z, compound_class.LookupInstanceFieldAllowPrivate( Symbols::_typedDataBase())); ASSERT(!compound_typed_data_base.IsNull()); Fragment body; LocalVariable* typed_data = MakeTemporary("typed_data_base"); body += AllocateObject(TokenPosition::kNoSource, compound_sub_class, 0); body += LoadLocal(MakeTemporary("compound")); // Duplicate Struct or Union. body += LoadLocal(typed_data); body += StoreInstanceField(compound_typed_data_base, StoreInstanceFieldInstr::Kind::kInitializing); body += DropTempsPreserveTop(1); // Drop TypedData. return body; } Fragment FlowGraphBuilder::LoadTypedDataBaseFromCompound() { const auto& lib_ffi = Library::Handle(Z, Library::FfiLibrary()); const auto& compound_class = Class::Handle(Z, lib_ffi.LookupClassAllowPrivate(Symbols::Compound())); const auto& compound_typed_data_base = Field::ZoneHandle(Z, compound_class.LookupInstanceFieldAllowPrivate( Symbols::_typedDataBase())); ASSERT(!compound_typed_data_base.IsNull()); Fragment body; body += LoadField(compound_typed_data_base, /*calls_initializer=*/false); return body; } Fragment FlowGraphBuilder::CopyFromCompoundToStack( LocalVariable* variable, const GrowableArray& representations) { Fragment body; const intptr_t num_defs = representations.length(); int offset_in_bytes = 0; for (intptr_t i = 0; i < num_defs; i++) { body += LoadLocal(variable); body += LoadTypedDataBaseFromCompound(); body += LoadUntagged(compiler::target::Pointer::data_field_offset()); body += IntConstant(offset_in_bytes); const Representation representation = representations[i]; offset_in_bytes += RepresentationUtils::ValueSize(representation); body += LoadIndexedTypedDataUnboxed(representation, /*index_scale=*/1, /*index_unboxed=*/false); } return body; } Fragment FlowGraphBuilder::PopFromStackToTypedDataBase( ZoneGrowableArray* definitions, const GrowableArray& representations) { Fragment body; const intptr_t num_defs = representations.length(); ASSERT(definitions->length() == num_defs); LocalVariable* uint8_list = MakeTemporary("uint8_list"); int offset_in_bytes = 0; for (intptr_t i = 0; i < num_defs; i++) { const Representation representation = representations[i]; body += LoadLocal(uint8_list); body += LoadUntagged(compiler::target::TypedDataBase::data_field_offset()); body += IntConstant(offset_in_bytes); body += LoadLocal(definitions->At(i)); body += StoreIndexedTypedDataUnboxed(representation, /*index_scale=*/1, /*index_unboxed=*/false); offset_in_bytes += RepresentationUtils::ValueSize(representation); } body += DropTempsPreserveTop(num_defs); // Drop chunck defs keep TypedData. return body; } static intptr_t chunk_size(intptr_t bytes_left) { ASSERT(bytes_left >= 1); if (bytes_left >= 8 && compiler::target::kWordSize == 8) { return 8; } if (bytes_left >= 4) { return 4; } if (bytes_left >= 2) { return 2; } return 1; } static classid_t typed_data_cid(intptr_t chunk_size) { switch (chunk_size) { case 8: return kTypedDataInt64ArrayCid; case 4: return kTypedDataInt32ArrayCid; case 2: return kTypedDataInt16ArrayCid; case 1: return kTypedDataInt8ArrayCid; } UNREACHABLE(); } Fragment FlowGraphBuilder::CopyFromTypedDataBaseToUnboxedAddress( intptr_t length_in_bytes) { Fragment body; Value* unboxed_address_value = Pop(); LocalVariable* typed_data_base = MakeTemporary("typed_data_base"); Push(unboxed_address_value->definition()); LocalVariable* unboxed_address = MakeTemporary("unboxed_address"); intptr_t offset_in_bytes = 0; while (offset_in_bytes < length_in_bytes) { const intptr_t bytes_left = length_in_bytes - offset_in_bytes; const intptr_t chunk_sizee = chunk_size(bytes_left); const classid_t typed_data_cidd = typed_data_cid(chunk_sizee); body += LoadLocal(typed_data_base); body += LoadUntagged(compiler::target::TypedDataBase::data_field_offset()); body += IntConstant(offset_in_bytes); body += LoadIndexed(typed_data_cidd, /*index_scale=*/1, /*index_unboxed=*/false); LocalVariable* chunk_value = MakeTemporary("chunk_value"); body += LoadLocal(unboxed_address); body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr); body += IntConstant(offset_in_bytes); body += LoadLocal(chunk_value); body += StoreIndexedTypedData(typed_data_cidd, /*index_scale=*/1, /*index_unboxed=*/false); body += DropTemporary(&chunk_value); offset_in_bytes += chunk_sizee; } ASSERT(offset_in_bytes == length_in_bytes); body += DropTemporary(&unboxed_address); body += DropTemporary(&typed_data_base); return body; } Fragment FlowGraphBuilder::CopyFromUnboxedAddressToTypedDataBase( intptr_t length_in_bytes) { Fragment body; Value* typed_data_base_value = Pop(); LocalVariable* unboxed_address = MakeTemporary("unboxed_address"); Push(typed_data_base_value->definition()); LocalVariable* typed_data_base = MakeTemporary("typed_data_base"); intptr_t offset_in_bytes = 0; while (offset_in_bytes < length_in_bytes) { const intptr_t bytes_left = length_in_bytes - offset_in_bytes; const intptr_t chunk_sizee = chunk_size(bytes_left); const classid_t typed_data_cidd = typed_data_cid(chunk_sizee); body += LoadLocal(unboxed_address); body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr); body += IntConstant(offset_in_bytes); body += LoadIndexed(typed_data_cidd, /*index_scale=*/1, /*index_unboxed=*/false); LocalVariable* chunk_value = MakeTemporary("chunk_value"); body += LoadLocal(typed_data_base); body += LoadUntagged(compiler::target::TypedDataBase::data_field_offset()); body += IntConstant(offset_in_bytes); body += LoadLocal(chunk_value); body += StoreIndexedTypedData(typed_data_cidd, /*index_scale=*/1, /*index_unboxed=*/false); body += DropTemporary(&chunk_value); offset_in_bytes += chunk_sizee; } ASSERT(offset_in_bytes == length_in_bytes); body += DropTemporary(&typed_data_base); body += DropTemporary(&unboxed_address); return body; } Fragment FlowGraphBuilder::FfiCallConvertCompoundArgumentToNative( LocalVariable* variable, const compiler::ffi::BaseMarshaller& marshaller, intptr_t arg_index) { Fragment body; const auto& native_loc = marshaller.Location(arg_index); if (native_loc.IsStack() || native_loc.IsMultiple()) { // Break struct in pieces to separate IL definitions to pass those // separate definitions into the FFI call. GrowableArray representations; marshaller.RepsInFfiCall(arg_index, &representations); body += CopyFromCompoundToStack(variable, representations); } else { ASSERT(native_loc.IsPointerToMemory()); // Only load the typed data, do copying in the FFI call machine code. body += LoadLocal(variable); // User-defined struct. body += LoadTypedDataBaseFromCompound(); } return body; } Fragment FlowGraphBuilder::FfiCallConvertCompoundReturnToDart( const compiler::ffi::BaseMarshaller& marshaller, intptr_t arg_index) { Fragment body; // The typed data is allocated before the FFI call, and is populated in // machine code. So, here, it only has to be wrapped in the struct class. const auto& compound_type = AbstractType::Handle(Z, marshaller.CType(arg_index)); body += WrapTypedDataBaseInCompound(compound_type); return body; } Fragment FlowGraphBuilder::FfiCallbackConvertCompoundArgumentToDart( const compiler::ffi::BaseMarshaller& marshaller, intptr_t arg_index, ZoneGrowableArray* definitions) { const intptr_t length_in_bytes = marshaller.Location(arg_index).payload_type().SizeInBytes(); Fragment body; if ((marshaller.Location(arg_index).IsMultiple() || marshaller.Location(arg_index).IsStack())) { // Allocate and populate a TypedData from the individual NativeParameters. body += IntConstant(length_in_bytes); body += AllocateTypedData(TokenPosition::kNoSource, kTypedDataUint8ArrayCid); GrowableArray representations; marshaller.RepsInFfiCall(arg_index, &representations); body += PopFromStackToTypedDataBase(definitions, representations); } else { ASSERT(marshaller.Location(arg_index).IsPointerToMemory()); // Allocate a TypedData and copy contents pointed to by an address into it. LocalVariable* address_of_compound = MakeTemporary("address_of_compound"); body += IntConstant(length_in_bytes); body += AllocateTypedData(TokenPosition::kNoSource, kTypedDataUint8ArrayCid); LocalVariable* typed_data_base = MakeTemporary("typed_data_base"); body += LoadLocal(address_of_compound); body += LoadLocal(typed_data_base); body += CopyFromUnboxedAddressToTypedDataBase(length_in_bytes); body += DropTempsPreserveTop(1); // address_of_compound. } // Wrap typed data in compound class. const auto& compound_type = AbstractType::Handle(Z, marshaller.CType(arg_index)); body += WrapTypedDataBaseInCompound(compound_type); return body; } Fragment FlowGraphBuilder::FfiCallbackConvertCompoundReturnToNative( const compiler::ffi::CallbackMarshaller& marshaller, intptr_t arg_index) { Fragment body; const auto& native_loc = marshaller.Location(arg_index); if (native_loc.IsMultiple()) { // We pass in typed data to native return instruction, and do the copying // in machine code. body += LoadTypedDataBaseFromCompound(); } else { ASSERT(native_loc.IsPointerToMemory()); // We copy the data into the right location in IL. const intptr_t length_in_bytes = marshaller.Location(arg_index).payload_type().SizeInBytes(); body += LoadTypedDataBaseFromCompound(); LocalVariable* typed_data_base = MakeTemporary("typed_data_base"); auto* pointer_to_return = new (Z) NativeParameterInstr(marshaller, compiler::ffi::kResultIndex); Push(pointer_to_return); // Address where return value should be stored. body <<= pointer_to_return; body += UnboxTruncate(kUnboxedFfiIntPtr); LocalVariable* unboxed_address = MakeTemporary("unboxed_address"); body += LoadLocal(typed_data_base); body += LoadLocal(unboxed_address); body += CopyFromTypedDataBaseToUnboxedAddress(length_in_bytes); body += DropTempsPreserveTop(1); // Keep address, drop typed_data_base. } return body; } Fragment FlowGraphBuilder::FfiConvertPrimitiveToDart( const compiler::ffi::BaseMarshaller& marshaller, intptr_t arg_index) { ASSERT(!marshaller.IsCompound(arg_index)); Fragment body; if (marshaller.IsPointer(arg_index)) { body += Box(kUnboxedFfiIntPtr); body += FfiPointerFromAddress( Type::CheckedHandle(Z, marshaller.CType(arg_index))); } else if (marshaller.IsHandle(arg_index)) { body += UnwrapHandle(); } else if (marshaller.IsVoid(arg_index)) { body += Drop(); body += NullConstant(); } else { if (marshaller.RequiresBitCast(arg_index)) { body += BitCast( marshaller.RepInFfiCall(marshaller.FirstDefinitionIndex(arg_index)), marshaller.RepInDart(arg_index)); } body += Box(marshaller.RepInDart(arg_index)); } return body; } Fragment FlowGraphBuilder::FfiConvertPrimitiveToNative( const compiler::ffi::BaseMarshaller& marshaller, intptr_t arg_index, LocalVariable* api_local_scope) { ASSERT(!marshaller.IsCompound(arg_index)); Fragment body; if (marshaller.IsPointer(arg_index)) { // This can only be Pointer, so it is always safe to LoadUntagged. body += LoadUntagged(compiler::target::Pointer::data_field_offset()); body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr); } else if (marshaller.IsHandle(arg_index)) { body += WrapHandle(api_local_scope); } else { body += UnboxTruncate(marshaller.RepInDart(arg_index)); } if (marshaller.RequiresBitCast(arg_index)) { body += BitCast( marshaller.RepInDart(arg_index), marshaller.RepInFfiCall(marshaller.FirstDefinitionIndex(arg_index))); } return body; } FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline( const Function& function) { if (function.FfiCallbackTarget() != Function::null()) { return BuildGraphOfFfiCallback(function); } else { return BuildGraphOfFfiNative(function); } } FlowGraph* FlowGraphBuilder::BuildGraphOfFfiNative(const Function& function) { const intptr_t kClosureParameterOffset = 0; const intptr_t kFirstArgumentParameterOffset = kClosureParameterOffset + 1; 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 function_body(instruction_cursor); function_body += CheckStackOverflowInPrologue(function.token_pos()); const auto& marshaller = *new (Z) compiler::ffi::CallMarshaller(Z, function); const bool signature_contains_handles = marshaller.ContainsHandles(); // FFI trampolines are accessed via closures, so non-covariant argument types // and type arguments are either statically checked by the type system or // dynamically checked via dynamic closure call dispatchers. // Null check arguments before we go into the try catch, so that we don't // catch our own null errors. const intptr_t num_args = marshaller.num_args(); for (intptr_t i = 0; i < num_args; i++) { if (marshaller.IsHandle(i)) { continue; } function_body += LoadLocal( parsed_function_->ParameterVariable(kFirstArgumentParameterOffset + i)); // Check for 'null'. // TODO(36780): Mention the param name instead of function reciever. function_body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, marshaller.function_name())); function_body += StoreLocal( TokenPosition::kNoSource, parsed_function_->ParameterVariable(kFirstArgumentParameterOffset + i)); function_body += Drop(); } Fragment body; intptr_t try_handler_index = -1; LocalVariable* api_local_scope = nullptr; if (signature_contains_handles) { // Wrap in Try catch to transition from Native to Generated on a throw from // the dart_api. try_handler_index = AllocateTryIndex(); body += TryCatch(try_handler_index); ++try_depth_; body += EnterHandleScope(); api_local_scope = MakeTemporary("api_local_scope"); } // Allocate typed data before FfiCall and pass it in to ffi call if needed. LocalVariable* typed_data = nullptr; if (marshaller.PassTypedData()) { body += IntConstant(marshaller.TypedDataSizeInBytes()); body += AllocateTypedData(TokenPosition::kNoSource, kTypedDataUint8ArrayCid); typed_data = MakeTemporary(); } // Unbox and push the arguments. for (intptr_t i = 0; i < marshaller.num_args(); i++) { if (marshaller.IsCompound(i)) { body += FfiCallConvertCompoundArgumentToNative( parsed_function_->ParameterVariable(kFirstArgumentParameterOffset + i), marshaller, i); } else { body += LoadLocal(parsed_function_->ParameterVariable( kFirstArgumentParameterOffset + i)); body += FfiConvertPrimitiveToNative(marshaller, i, api_local_scope); } } // Push the function pointer, which is stored (as Pointer object) in the // first slot of the context. body += LoadLocal(parsed_function_->ParameterVariable(kClosureParameterOffset)); body += LoadNativeField(Slot::Closure_context()); body += LoadNativeField(Slot::GetContextVariableSlotFor( thread_, *MakeImplicitClosureScope( Z, Class::Handle(IG->object_store()->ffi_pointer_class())) ->context_variables()[0])); // This can only be Pointer, so it is always safe to LoadUntagged. body += LoadUntagged(compiler::target::Pointer::data_field_offset()); body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr); if (marshaller.PassTypedData()) { body += LoadLocal(typed_data); } body += FfiCall(marshaller); for (intptr_t i = 0; i < marshaller.num_args(); i++) { if (marshaller.IsPointer(i)) { body += LoadLocal(parsed_function_->ParameterVariable( kFirstArgumentParameterOffset + i)); body += ReachabilityFence(); } } const intptr_t num_defs = marshaller.NumReturnDefinitions(); ASSERT(num_defs >= 1); auto defs = new (Z) ZoneGrowableArray(Z, num_defs); LocalVariable* def = MakeTemporary(); defs->Add(def); if (marshaller.PassTypedData()) { // Drop call result, typed data with contents is already on the stack. body += Drop(); } if (marshaller.IsCompound(compiler::ffi::kResultIndex)) { body += FfiCallConvertCompoundReturnToDart(marshaller, compiler::ffi::kResultIndex); } else { body += FfiConvertPrimitiveToDart(marshaller, compiler::ffi::kResultIndex); } if (signature_contains_handles) { body += DropTempsPreserveTop(1); // Drop api_local_scope. body += ExitHandleScope(); } body += Return(TokenPosition::kNoSource); if (signature_contains_handles) { --try_depth_; } function_body += body; if (signature_contains_handles) { ++catch_depth_; Fragment catch_body = CatchBlockEntry(Array::empty_array(), try_handler_index, /*needs_stacktrace=*/true, /*is_synthesized=*/true); // TODO(41984): If we want to pass in the handle scope, move it out // of the try catch. catch_body += ExitHandleScope(); catch_body += LoadLocal(CurrentException()); catch_body += LoadLocal(CurrentStackTrace()); catch_body += RethrowException(TokenPosition::kNoSource, try_handler_index); --catch_depth_; } return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, prologue_info); } FlowGraph* FlowGraphBuilder::BuildGraphOfFfiCallback(const Function& function) { const auto& marshaller = *new (Z) compiler::ffi::CallbackMarshaller(Z, function); graph_entry_ = new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId); auto* const native_entry = new (Z) NativeEntryInstr( marshaller, graph_entry_, AllocateBlockId(), CurrentTryIndex(), GetNextDeoptId(), function.FfiCallbackId()); graph_entry_->set_normal_entry(native_entry); Fragment function_body(native_entry); function_body += CheckStackOverflowInPrologue(function.token_pos()); // Wrap the entire method in a big try/catch. This is important to ensure that // the VM does not crash if the callback throws an exception. const intptr_t try_handler_index = AllocateTryIndex(); Fragment body = TryCatch(try_handler_index); ++try_depth_; // Box and push the arguments. for (intptr_t i = 0; i < marshaller.num_args(); i++) { const intptr_t num_defs = marshaller.NumDefinitions(i); auto defs = new (Z) ZoneGrowableArray(Z, num_defs); for (intptr_t j = 0; j < num_defs; j++) { const intptr_t def_index = marshaller.DefinitionIndex(j, i); auto* parameter = new (Z) NativeParameterInstr(marshaller, def_index); Push(parameter); body <<= parameter; LocalVariable* def = MakeTemporary(); defs->Add(def); } if (marshaller.IsCompound(i)) { body += FfiCallbackConvertCompoundArgumentToDart(marshaller, i, defs); } else { body += FfiConvertPrimitiveToDart(marshaller, i); } } // Call the target. // // TODO(36748): Determine the hot-reload semantics of callbacks and update the // rebind-rule accordingly. body += StaticCall(TokenPosition::kNoSource, Function::ZoneHandle(Z, function.FfiCallbackTarget()), marshaller.num_args(), Array::empty_array(), ICData::kNoRebind); if (marshaller.IsVoid(compiler::ffi::kResultIndex)) { body += Drop(); body += IntConstant(0); } else if (!marshaller.IsHandle(compiler::ffi::kResultIndex)) { body += CheckNullOptimized(TokenPosition::kNoSource, String::ZoneHandle(Z, marshaller.function_name())); } if (marshaller.IsCompound(compiler::ffi::kResultIndex)) { body += FfiCallbackConvertCompoundReturnToNative( marshaller, compiler::ffi::kResultIndex); } else { body += FfiConvertPrimitiveToNative(marshaller, compiler::ffi::kResultIndex, /*api_local_scope=*/nullptr); } body += NativeReturn(marshaller); --try_depth_; function_body += body; ++catch_depth_; Fragment catch_body = CatchBlockEntry(Array::empty_array(), try_handler_index, /*needs_stacktrace=*/false, /*is_synthesized=*/true); // Return the "exceptional return" value given in 'fromFunction'. // // For pointer and void return types, the exceptional return is always null -- // return 0 instead. if (marshaller.IsPointer(compiler::ffi::kResultIndex) || marshaller.IsVoid(compiler::ffi::kResultIndex)) { ASSERT(function.FfiCallbackExceptionalReturn() == Object::null()); catch_body += IntConstant(0); catch_body += UnboxTruncate(kUnboxedFfiIntPtr); } else if (marshaller.IsHandle(compiler::ffi::kResultIndex)) { catch_body += UnhandledException(); catch_body += FfiConvertPrimitiveToNative(marshaller, compiler::ffi::kResultIndex, /*api_local_scope=*/nullptr); } else if (marshaller.IsCompound(compiler::ffi::kResultIndex)) { ASSERT(function.FfiCallbackExceptionalReturn() == Object::null()); // Manufacture empty result. const intptr_t size = Utils::RoundUp(marshaller.Location(compiler::ffi::kResultIndex) .payload_type() .SizeInBytes(), compiler::target::kWordSize); catch_body += IntConstant(size); catch_body += AllocateTypedData(TokenPosition::kNoSource, kTypedDataUint8ArrayCid); catch_body += WrapTypedDataBaseInCompound( AbstractType::Handle(Z, marshaller.CType(compiler::ffi::kResultIndex))); catch_body += FfiCallbackConvertCompoundReturnToNative( marshaller, compiler::ffi::kResultIndex); } else { catch_body += Constant( Instance::ZoneHandle(Z, function.FfiCallbackExceptionalReturn())); catch_body += FfiConvertPrimitiveToNative(marshaller, compiler::ffi::kResultIndex, /*api_local_scope=*/nullptr); } catch_body += NativeReturn(marshaller); --catch_depth_; PrologueInfo prologue_info(-1, -1); 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()); } bool FlowGraphBuilder::NeedsNullAssertion(const AbstractType& type) { if (!type.IsNonNullable()) { return false; } if (type.IsTypeParameter()) { return NeedsNullAssertion( AbstractType::Handle(Z, TypeParameter::Cast(type).bound())); } if (type.IsFutureOrType()) { return NeedsNullAssertion(AbstractType::Handle(Z, type.UnwrapFutureOr())); } return true; } Fragment FlowGraphBuilder::NullAssertion(LocalVariable* variable) { Fragment code; if (!NeedsNullAssertion(variable->type())) { return code; } TargetEntryInstr* then; TargetEntryInstr* otherwise; code += LoadLocal(variable); code += NullConstant(); code += BranchIfEqual(&then, &otherwise); const Script& script = Script::Handle(Z, parsed_function_->function().script()); intptr_t line = -1; intptr_t column = -1; script.GetTokenLocation(variable->token_pos(), &line, &column); // Build equivalent of `throw _AssertionError._throwNewNullAssertion(name)` // expression. We build throw (even through _throwNewNullAssertion already // throws) because call is not a valid last instruction for the block. // Blocks can only terminate with explicit control flow instructions // (Branch, Goto, Return or Throw). Fragment null_code(then); null_code += Constant(variable->name()); null_code += IntConstant(line); null_code += IntConstant(column); null_code += StaticCall(variable->token_pos(), ThrowNewNullAssertionFunction(), 3, ICData::kStatic); null_code += ThrowException(TokenPosition::kNoSource); null_code += Drop(); return Fragment(code.entry, otherwise); } Fragment FlowGraphBuilder::BuildNullAssertions() { Fragment code; if (IG->null_safety() || !IG->asserts() || !FLAG_null_assertions) { return code; } const Function& dart_function = parsed_function_->function(); for (intptr_t i = dart_function.NumImplicitParameters(), n = dart_function.NumParameters(); i < n; ++i) { LocalVariable* variable = parsed_function_->ParameterVariable(i); code += NullAssertion(variable); } return code; } const Function& FlowGraphBuilder::ThrowNewNullAssertionFunction() { if (throw_new_null_assertion_.IsNull()) { const Class& klass = Class::ZoneHandle( Z, Library::LookupCoreClass(Symbols::AssertionError())); ASSERT(!klass.IsNull()); const auto& error = klass.EnsureIsFinalized(H.thread()); ASSERT(error == Error::null()); throw_new_null_assertion_ = klass.LookupStaticFunctionAllowPrivate( Symbols::ThrowNewNullAssertion()); ASSERT(!throw_new_null_assertion_.IsNull()); } return throw_new_null_assertion_; } const Function& FlowGraphBuilder::PrependTypeArgumentsFunction() { if (prepend_type_arguments_.IsNull()) { const auto& dart_internal = Library::Handle(Z, Library::InternalLibrary()); prepend_type_arguments_ = dart_internal.LookupFunctionAllowPrivate( Symbols::PrependTypeArguments()); ASSERT(!prepend_type_arguments_.IsNull()); } return prepend_type_arguments_; } } // namespace kernel } // namespace dart