| // 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<const ICData*>* ic_data_array, |
| ZoneGrowableArray<intptr_t>* context_level_array, |
| InlineExitCollector* exit_collector, |
| bool optimizing, |
| intptr_t osr_id, |
| intptr_t first_block_id, |
| bool inlining_unchecked_entry) |
| : BaseFlowGraphBuilder(parsed_function, |
| first_block_id - 1, |
| osr_id, |
| context_level_array, |
| exit_collector, |
| inlining_unchecked_entry), |
| translation_helper_(Thread::Current()), |
| thread_(translation_helper_.thread()), |
| zone_(translation_helper_.zone()), |
| parsed_function_(parsed_function), |
| optimizing_(optimizing), |
| ic_data_array_(*ic_data_array), |
| next_function_id_(0), |
| loop_depth_(0), |
| try_depth_(0), |
| catch_depth_(0), |
| for_in_depth_(0), |
| 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<T> { |
| // newList() => new List<T>(); |
| // } |
| // We just use the type argument vector from the [Foo] object and pass it |
| // directly to the `new List<T>()` factory constructor. |
| instructions += LoadInstantiatorTypeArguments(); |
| } else if (type_arguments.CanShareFunctionTypeArguments( |
| parsed_function_->function())) { |
| instructions += LoadFunctionTypeArguments(); |
| } else { |
| // Otherwise we need to resolve [TypeParameterType]s in the type |
| // expression based on the current instantiator type argument vector. |
| if (!type_arguments.IsInstantiated(kCurrentClass)) { |
| instructions += LoadInstantiatorTypeArguments(); |
| } else { |
| instructions += NullConstant(); |
| } |
| if (!type_arguments.IsInstantiated(kFunctions)) { |
| instructions += LoadFunctionTypeArguments(); |
| } else { |
| instructions += NullConstant(); |
| } |
| instructions += InstantiateTypeArguments(type_arguments); |
| } |
| } |
| return instructions; |
| } |
| |
| Fragment FlowGraphBuilder::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<const Slot*>& 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<const String> pieces(Z, 3); |
| pieces.Add(Symbols::TypeError()); |
| pieces.Add(Symbols::Dot()); |
| pieces.Add(H.DartSymbolObfuscate("_create")); |
| |
| const Function& constructor = Function::ZoneHandle( |
| Z, klass.LookupConstructorAllowPrivate( |
| String::ZoneHandle(Z, Symbols::FromConcatAll(thread_, pieces)))); |
| ASSERT(!constructor.IsNull()); |
| |
| const String& url = H.DartString( |
| parsed_function_->function().ToLibNamePrefixedQualifiedCString(), |
| Heap::kOld); |
| |
| Fragment instructions; |
| |
| // Create instance of _FallThroughError |
| instructions += AllocateObject(TokenPosition::kNoSource, klass, 0); |
| LocalVariable* instance = MakeTemporary(); |
| |
| // Call _TypeError._create constructor. |
| instructions += LoadLocal(instance); // 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::kFfiLoadDouble: |
| 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::kFfiStoreDouble: |
| 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<E>([int length]) { |
| // return (:arg_desc.positional_count == 2) ? new _List<E>(length) |
| // : new _GrowableList<E>(0); |
| // } |
| const Library& core_lib = Library::Handle(Z, Library::CoreLibrary()); |
| |
| TargetEntryInstr *allocate_non_growable, *allocate_growable; |
| |
| body += LoadArgDescriptor(); |
| body += LoadNativeField(Slot::ArgumentsDescriptor_positional_count()); |
| body += IntConstant(2); |
| body += BranchIfStrictEqual(&allocate_non_growable, &allocate_growable); |
| |
| JoinEntryInstr* join = BuildJoinEntry(); |
| |
| { |
| const Class& cls = Class::Handle( |
| Z, core_lib.LookupClass( |
| Library::PrivateCoreLibName(Symbols::_List()))); |
| ASSERT(!cls.IsNull()); |
| const Function& func = Function::ZoneHandle( |
| Z, cls.LookupFactoryAllowPrivate(Symbols::_ListFactory())); |
| ASSERT(!func.IsNull()); |
| |
| Fragment allocate(allocate_non_growable); |
| allocate += LoadLocal(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<int64_t>(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::kFfiLoadDouble: |
| case MethodRecognizer::kFfiLoadPointer: { |
| const classid_t ffi_type_arg_cid = |
| compiler::ffi::RecognizedMethodTypeArgCid(kind); |
| const classid_t typed_data_cid = |
| compiler::ffi::ElementTypedDataCid(ffi_type_arg_cid); |
| const auto& native_rep = compiler::ffi::NativeType::FromTypedDataClassId( |
| zone_, 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); |
| if (kind == MethodRecognizer::kFfiLoadFloat || |
| kind == MethodRecognizer::kFfiLoadDouble) { |
| if (kind == MethodRecognizer::kFfiLoadFloat) { |
| body += FloatToDouble(); |
| } |
| body += Box(kUnboxedDouble); |
| } else { |
| body += Box(native_rep.AsRepresentationOverApprox(zone_)); |
| 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<Pointer<X>> as RawParameterVariable(0). This function |
| // will return a Pointer<X> 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<Pointer<X>> 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::kFfiStoreDouble: |
| case MethodRecognizer::kFfiStorePointer: { |
| const classid_t ffi_type_arg_cid = |
| compiler::ffi::RecognizedMethodTypeArgCid(kind); |
| const classid_t typed_data_cid = |
| compiler::ffi::ElementTypedDataCid(ffi_type_arg_cid); |
| const auto& native_rep = compiler::ffi::NativeType::FromTypedDataClassId( |
| zone_, 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<Pointer<X>> as RawParameterVariable(0). This function |
| // will takes a Pointer<X> 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<Pointer<X>> 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 if (kind == MethodRecognizer::kFfiStoreFloat || |
| kind == MethodRecognizer::kFfiStoreDouble) { |
| body += UnboxTruncate(kUnboxedDouble); |
| if (kind == MethodRecognizer::kFfiStoreFloat) { |
| body += DoubleToFloat(); |
| } |
| } else { |
| body += UnboxTruncate(native_rep.AsRepresentationOverApprox(zone_)); |
| } |
| body += StoreIndexedTypedData(typed_data_cid, /*index_scale=*/1, |
| /*index_unboxed=*/true); |
| 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 signature cannot have uninstantiated function type parameters, |
| // because the function cannot be local and have parent generic functions. |
| ASSERT(target.HasInstantiatedSignature(kFunctions)); |
| |
| // Allocate a context that closes over `this`. |
| // Note: this must be kept in sync with ScopeBuilder::BuildScopes. |
| const LocalScope* implicit_closure_scope = |
| MakeImplicitClosureScope(Z, 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* function = 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* closure_data = nullptr; |
| LocalVariable* default_tav_info = 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.default_tav_info); |
| static_assert( |
| Function::DefaultTypeArgumentsKindField::shift() == 0, |
| "Need to generate shift for DefaultTypeArgumentsKindField bit field"); |
| store_default += IntConstant(Function::DefaultTypeArgumentsKindField::mask()); |
| store_default += SmiBinaryOp(Token::kBIT_AND); |
| LocalVariable* default_tav_kind = MakeTemporary("default_tav_kind"); |
| |
| // One read-only stack values (default_tav_kind) that must be dropped after |
| // rejoining at done. |
| JoinEntryInstr* done = BuildJoinEntry(); |
| |
| store_default += LoadLocal(default_tav_kind); |
| TargetEntryInstr *is_instantiated, *is_not_instantiated; |
| store_default += IntConstant(static_cast<intptr_t>( |
| Function::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<intptr_t>( |
| Function::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<intptr_t>( |
| Function::DefaultTypeArgumentsKind::kSharesInstantiatorTypeArguments)); |
| store_default += BranchIfEqual(&can_share_instantiator, &can_share_function); |
| |
| Fragment instantiated(is_instantiated); |
| instantiated += LoadLocal(info.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(info.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); |
| |
| 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); |
| 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()); |
| info.function = MakeTemporary("function"); |
| |
| body += LoadLocal(info.function); |
| body += BuildExtractUnboxedSlotBitFieldIntoSmi< |
| Function::PackedNumFixedParameters>(Slot::Function_packed_fields()); |
| info.num_fixed_params = MakeTemporary("num_fixed_params"); |
| |
| body += LoadLocal(info.function); |
| body += BuildExtractUnboxedSlotBitFieldIntoSmi< |
| Function::PackedNumOptionalParameters>(Slot::Function_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.function); |
| body += BuildExtractUnboxedSlotBitFieldIntoSmi< |
| Function::PackedHasNamedOptionalParameters>( |
| Slot::Function_packed_fields()); |
| |
| body += IntConstant(0); |
| body += StrictCompare(Token::kNE_STRICT); |
| info.has_named_params = MakeTemporary("has_named_params"); |
| |
| body += LoadLocal(info.function); |
| body += LoadNativeField(Slot::Function_parameter_names()); |
| info.parameter_names = MakeTemporary("parameter_names"); |
| |
| body += LoadLocal(info.function); |
| body += LoadNativeField(Slot::Function_signature()); |
| body += LoadNativeField(Slot::FunctionType_parameter_types()); |
| info.parameter_types = MakeTemporary("parameter_types"); |
| |
| body += LoadLocal(info.function); |
| body += LoadNativeField(Slot::Function_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; |
| generic += LoadLocal(info.function); |
| generic += LoadNativeField(Slot::Function_data()); |
| info.closure_data = MakeTemporary("closure_data"); |
| generic += LoadLocal(info.closure_data); |
| generic += LoadNativeField(Slot::ClosureData_default_type_arguments_info()); |
| info.default_tav_info = MakeTemporary("default_tav_info"); |
| // 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.default_tav_info); |
| static_assert(Function::NumParentTypeParametersField::shift() > 0, |
| "No need to shift for NumParentTypeParametersField bit field"); |
| generic += IntConstant(Function::NumParentTypeParametersField::shift()); |
| generic += SmiBinaryOp(Token::kSHR); |
| generic += IntConstant(Function::NumParentTypeParametersField::mask()); |
| generic += SmiBinaryOp(Token::kBIT_AND); |
| // 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(); |
| generic += DropTemporary(&info.default_tav_info); |
| generic += DropTemporary(&info.closure_data); |
| |
| // 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.function); |
| |
| 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<dynamic>(argument_count); |
| // |
| // int i = 0; |
| // if (any type arguments are passed) { |
| // arguments[0] = function_type_arguments; |
| // ++i; |
| // } |
| // |
| // for (; i < argument_count; ++i) { |
| // arguments[i] = LoadFpRelativeSlot( |
| // kWordSize * (frame_layout.param_end_from_fp + argument_count - i)); |
| // } |
| body += Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null())); |
| body += LoadLocal(argument_count); |
| body += CreateArray(); |
| LocalVariable* arguments = MakeTemporary(); |
| |
| { |
| // int i = 0 |
| LocalVariable* index = parsed_function_->expression_temp_var(); |
| body += IntConstant(0); |
| body += StoreLocal(TokenPosition::kNoSource, index); |
| body += Drop(); |
| |
| // if (any type arguments are passed) { |
| // arguments[0] = function_type_arguments; |
| // i = 1; |
| // } |
| if (function.IsGeneric()) { |
| Fragment store; |
| store += LoadLocal(arguments); |
| store += IntConstant(0); |
| store += LoadFunctionTypeArguments(); |
| store += StoreIndexed(kArrayCid); |
| store += IntConstant(1); |
| store += StoreLocal(TokenPosition::kNoSource, index); |
| store += Drop(); |
| body += TestAnyTypeArgs(store, Fragment()); |
| } |
| |
| TargetEntryInstr* body_entry; |
| TargetEntryInstr* loop_exit; |
| |
| Fragment condition; |
| // i < argument_count |
| condition += LoadLocal(index); |
| condition += LoadLocal(argument_count); |
| condition += SmiRelationalOp(Token::kLT); |
| condition += BranchIfTrue(&body_entry, &loop_exit, /*negate=*/false); |
| |
| Fragment loop_body(body_entry); |
| |
| // arguments[i] = LoadFpRelativeSlot( |
| // kWordSize * (frame_layout.param_end_from_fp + argument_count - i)); |
| loop_body += LoadLocal(arguments); |
| loop_body += LoadLocal(index); |
| loop_body += LoadLocal(argument_count); |
| loop_body += LoadLocal(index); |
| loop_body += SmiBinaryOp(Token::kSUB, /*truncate=*/true); |
| loop_body += |
| LoadFpRelativeSlot(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<intptr_t>(UncheckedEntryPointStyle::kNone)); |
| normal_entry += StoreLocal(TokenPosition::kNoSource, |
| parsed_function_->entry_points_temp_var()); |
| normal_entry += Drop(); |
| normal_entry += Goto(join_entry); |
| |
| auto* extra_target_entry = BuildFunctionEntry(graph_entry_); |
| Fragment extra_entry(extra_target_entry); |
| extra_entry += IntConstant( |
| static_cast<intptr_t>(UncheckedEntryPointStyle::kSharedWithVariable)); |
| extra_entry += StoreLocal(TokenPosition::kNoSource, |
| parsed_function_->entry_points_temp_var()); |
| extra_entry += Drop(); |
| extra_entry += Goto(join_entry); |
| |
| 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<intptr_t>(UncheckedEntryPointStyle::kSharedWithVariable)); |
| shared_prologue_linked_in += |
| BranchIfEqual(&skip_checks, &do_checks, /*negate=*/false); |
| |
| JoinEntryInstr* rest_entry = BuildJoinEntry(); |
| |
| Fragment(do_checks) + skippable_checks + Goto(rest_entry); |
| Fragment(skip_checks) + redefinitions_if_skipped + Goto(rest_entry); |
| Fragment(rest_entry) + body; |
| |
| return extra_target_entry; |
| } |
| |
| FunctionEntryInstr* FlowGraphBuilder::BuildSeparateUncheckedEntryPoint( |
| BlockEntryInstr* normal_entry, |
| Fragment normal_prologue, |
| Fragment extra_prologue, |
| Fragment shared_prologue, |
| Fragment body) { |
| auto* join_entry = BuildJoinEntry(); |
| auto* extra_entry = BuildFunctionEntry(graph_entry_); |
| |
| Fragment normal(normal_entry); |
| normal += IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kNone)); |
| normal += BuildEntryPointsIntrospection(); |
| normal += normal_prologue; |
| normal += Goto(join_entry); |
| |
| Fragment extra(extra_entry); |
| extra += |
| IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kSeparate)); |
| extra += BuildEntryPointsIntrospection(); |
| extra += extra_prologue; |
| extra += Goto(join_entry); |
| |
| Fragment(join_entry) + shared_prologue + body; |
| return extra_entry; |
| } |
| |
| 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::WrapTypedDataBaseInStruct( |
| const AbstractType& struct_type) { |
| const auto& struct_sub_class = Class::ZoneHandle(Z, struct_type.type_class()); |
| struct_sub_class.EnsureIsFinalized(thread_); |
| const auto& lib_ffi = Library::Handle(Z, Library::FfiLibrary()); |
| const auto& struct_class = |
| Class::Handle(Z, lib_ffi.LookupClass(Symbols::Struct())); |
| const auto& struct_addressof = Field::ZoneHandle( |
| Z, struct_class.LookupInstanceFieldAllowPrivate(Symbols::_addressOf())); |
| ASSERT(!struct_addressof.IsNull()); |
| |
| Fragment body; |
| LocalVariable* typed_data = MakeTemporary("typed_data_base"); |
| body += AllocateObject(TokenPosition::kNoSource, struct_sub_class, 0); |
| body += LoadLocal(MakeTemporary("struct")); // Duplicate Struct. |
| body += LoadLocal(typed_data); |
| body += StoreInstanceField(struct_addressof, |
| StoreInstanceFieldInstr::Kind::kInitializing); |
| body += DropTempsPreserveTop(1); // Drop TypedData. |
| return body; |
| } |
| |
| Fragment FlowGraphBuilder::LoadTypedDataBaseFromStruct() { |
| const Library& lib_ffi = Library::Handle(zone_, Library::FfiLibrary()); |
| const Class& struct_class = |
| Class::Handle(zone_, lib_ffi.LookupClass(Symbols::Struct())); |
| const Field& struct_addressof = Field::ZoneHandle( |
| zone_, |
| struct_class.LookupInstanceFieldAllowPrivate(Symbols::_addressOf())); |
| ASSERT(!struct_addressof.IsNull()); |
| |
| Fragment body; |
| body += LoadField(struct_addressof, /*calls_initializer=*/false); |
| return body; |
| } |
| |
| Fragment FlowGraphBuilder::CopyFromStructToStack( |
| LocalVariable* variable, |
| const GrowableArray<Representation>& 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 += LoadTypedDataBaseFromStruct(); |
| 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<LocalVariable*>* definitions, |
| const GrowableArray<Representation>& 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::FfiCallConvertStructArgumentToNative( |
| 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<Representation> representations; |
| marshaller.RepsInFfiCall(arg_index, &representations); |
| body += CopyFromStructToStack(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 += LoadTypedDataBaseFromStruct(); |
| } |
| return body; |
| } |
| |
| Fragment FlowGraphBuilder::FfiCallConvertStructReturnToDart( |
| 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& struct_type = |
| AbstractType::Handle(Z, marshaller.CType(arg_index)); |
| body += WrapTypedDataBaseInStruct(struct_type); |
| return body; |
| } |
| |
| Fragment FlowGraphBuilder::FfiCallbackConvertStructArgumentToDart( |
| const compiler::ffi::BaseMarshaller& marshaller, |
| intptr_t arg_index, |
| ZoneGrowableArray<LocalVariable*>* 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<Representation> 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_struct = MakeTemporary("address_of_struct"); |
| body += IntConstant(length_in_bytes); |
| body += |
| AllocateTypedData(TokenPosition::kNoSource, kTypedDataUint8ArrayCid); |
| LocalVariable* typed_data_base = MakeTemporary("typed_data_base"); |
| body += LoadLocal(address_of_struct); |
| body += LoadLocal(typed_data_base); |
| body += CopyFromUnboxedAddressToTypedDataBase(length_in_bytes); |
| body += DropTempsPreserveTop(1); // address_of_struct. |
| } |
| // Wrap typed data in struct class. |
| const auto& struct_type = |
| AbstractType::Handle(Z, marshaller.CType(arg_index)); |
| body += WrapTypedDataBaseInStruct(struct_type); |
| return body; |
| } |
| |
| Fragment FlowGraphBuilder::FfiCallbackConvertStructReturnToNative( |
| 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 += LoadTypedDataBaseFromStruct(); |
| } 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 += LoadTypedDataBaseFromStruct(); |
| 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.IsStruct(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.IsStruct(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.IsStruct(i)) { |
| body += FfiCallConvertStructArgumentToNative( |
| 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<LocalVariable*>(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.IsStruct(compiler::ffi::kResultIndex)) { |
| body += FfiCallConvertStructReturnToDart(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<LocalVariable*>(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.IsStruct(i)) { |
| body += FfiCallbackConvertStructArgumentToDart(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.IsStruct(compiler::ffi::kResultIndex)) { |
| body += FfiCallbackConvertStructReturnToNative(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.IsStruct(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 += WrapTypedDataBaseInStruct( |
| AbstractType::Handle(Z, marshaller.CType(compiler::ffi::kResultIndex))); |
| catch_body += FfiCallbackConvertStructReturnToNative( |
| 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 |