| // 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_binary_flowgraph.h" | |
| #include "vm/closure_functions_cache.h" | |
| #include "vm/compiler/ffi/callback.h" | |
| #include "vm/compiler/ffi/recognized_method.h" | |
| #include "vm/compiler/frontend/flow_graph_builder.h" // For dart::FlowGraphBuilder::SimpleInstanceOfType. | |
| #include "vm/compiler/frontend/prologue_builder.h" | |
| #include "vm/compiler/jit/compiler.h" | |
| #include "vm/kernel_binary.h" | |
| #include "vm/kernel_loader.h" | |
| #include "vm/object_store.h" | |
| #include "vm/resolver.h" | |
| #include "vm/stack_frame.h" | |
| namespace dart { | |
| namespace kernel { | |
| #define Z (zone_) | |
| #define H (translation_helper_) | |
| #define T (type_translator_) | |
| #define I Isolate::Current() | |
| #define IG IsolateGroup::Current() | |
| #define B (flow_graph_builder_) | |
| Class& StreamingFlowGraphBuilder::GetSuperOrDie() { | |
| Class& klass = Class::Handle(Z, parsed_function()->function().Owner()); | |
| ASSERT(!klass.IsNull()); | |
| klass = klass.SuperClass(); | |
| ASSERT(!klass.IsNull()); | |
| return klass; | |
| } | |
| FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFieldInitializer() { | |
| FieldHelper field_helper(this); | |
| field_helper.ReadUntilExcluding(FieldHelper::kInitializer); | |
| // Constants are directly accessed at use sites of Dart code. In C++ - if | |
| // we need to access static constants - we do so directly using the kernel | |
| // evaluation instead of invoking the initializer function in Dart code. | |
| // | |
| // If the field is marked as @pragma('vm:entry-point') then the embedder might | |
| // invoke the getter, so we'll generate the initializer function. | |
| ASSERT(!field_helper.IsConst() || | |
| Field::Handle(Z, parsed_function()->function().accessor_field()) | |
| .VerifyEntryPoint(EntryPointPragma::kGetterOnly) == | |
| Error::null()); | |
| Tag initializer_tag = ReadTag(); // read first part of initializer. | |
| if (initializer_tag != kSomething) { | |
| UNREACHABLE(); | |
| } | |
| B->graph_entry_ = new (Z) GraphEntryInstr(*parsed_function(), B->osr_id_); | |
| auto normal_entry = B->BuildFunctionEntry(B->graph_entry_); | |
| B->graph_entry_->set_normal_entry(normal_entry); | |
| Fragment body(normal_entry); | |
| body += B->CheckStackOverflowInPrologue(field_helper.position_); | |
| body += SetupCapturedParameters(parsed_function()->function()); | |
| body += BuildExpression(); // read initializer. | |
| body += Return(TokenPosition::kNoSource); | |
| PrologueInfo prologue_info(-1, -1); | |
| if (B->IsCompiledForOsr()) { | |
| auto result = B->graph_entry_->FindOsrEntry(Z, B->last_used_block_id_ + 1); | |
| flow_graph_builder_->RelinkToOsrEntry(result); | |
| } | |
| return new (Z) FlowGraph( | |
| *parsed_function(), B->graph_entry_, B->last_used_block_id_, | |
| prologue_info, | |
| FlowGraph::CompilationModeFrom(flow_graph_builder_->optimizing())); | |
| } | |
| void StreamingFlowGraphBuilder::SetupDefaultParameterValues() { | |
| intptr_t optional_parameter_count = | |
| parsed_function()->function().NumOptionalParameters(); | |
| if (optional_parameter_count > 0) { | |
| ZoneGrowableArray<const Instance*>* default_values = | |
| new ZoneGrowableArray<const Instance*>(Z, optional_parameter_count); | |
| AlternativeReadingScope alt(&reader_); | |
| FunctionNodeHelper function_node_helper(this); | |
| function_node_helper.ReadUntilExcluding( | |
| FunctionNodeHelper::kPositionalParameters); | |
| if (parsed_function()->function().HasOptionalNamedParameters()) { | |
| // List of positional. | |
| intptr_t list_length = ReadListLength(); // read list length. | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| SkipVariable(); // read ith variable. | |
| } | |
| // List of named. | |
| list_length = ReadListLength(); // read list length. | |
| ASSERT(optional_parameter_count == list_length); | |
| ASSERT(!parsed_function()->function().HasOptionalPositionalParameters()); | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| Instance* default_value; | |
| // Read ith variable | |
| VariableHelper helper(this); | |
| helper.ReadUntilExcluding(VariableHelper::kInitializer); | |
| Tag tag = ReadTag(); // read (first part of) initializer. | |
| if (tag == kSomething) { | |
| // This will read the initializer. | |
| default_value = &Instance::ZoneHandle( | |
| Z, constant_reader_.ReadConstantExpression()); | |
| } else { | |
| default_value = &Instance::ZoneHandle(Z, Instance::null()); | |
| } | |
| default_values->Add(default_value); | |
| } | |
| } else { | |
| // List of positional. | |
| intptr_t list_length = ReadListLength(); // read list length. | |
| ASSERT(list_length == function_node_helper.required_parameter_count_ + | |
| optional_parameter_count); | |
| ASSERT(parsed_function()->function().HasOptionalPositionalParameters()); | |
| for (intptr_t i = 0; i < function_node_helper.required_parameter_count_; | |
| ++i) { | |
| SkipVariable(); // read ith variable. | |
| } | |
| for (intptr_t i = 0; i < optional_parameter_count; ++i) { | |
| Instance* default_value; | |
| // Read ith variable | |
| VariableHelper helper(this); | |
| helper.ReadUntilExcluding(VariableHelper::kInitializer); | |
| Tag tag = ReadTag(); // read (first part of) initializer. | |
| if (tag == kSomething) { | |
| // This will read the initializer. | |
| default_value = &Instance::ZoneHandle( | |
| Z, constant_reader_.ReadConstantExpression()); | |
| } else { | |
| default_value = &Instance::ZoneHandle(Z, Instance::null()); | |
| } | |
| default_values->Add(default_value); | |
| } | |
| // List of named. | |
| list_length = ReadListLength(); // read list length. | |
| ASSERT(list_length == 0); | |
| } | |
| parsed_function()->set_default_parameter_values(default_values); | |
| } | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildFieldInitializer( | |
| const Field& field, | |
| bool only_for_side_effects) { | |
| ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull()); | |
| if (PeekTag() == kNullLiteral) { | |
| SkipExpression(); // read past the null literal. | |
| if (H.thread()->IsDartMutatorThread()) { | |
| ASSERT(field.IsOriginal()); | |
| LeaveCompilerScope cs(H.thread()); | |
| field.RecordStore(Object::null_object()); | |
| } else { | |
| ASSERT(field.is_nullable_unsafe()); | |
| } | |
| return Fragment(); | |
| } | |
| Fragment instructions; | |
| if (!only_for_side_effects) { | |
| instructions += LoadLocal(parsed_function()->receiver_var()); | |
| } | |
| // All closures created inside BuildExpression will have | |
| // field.RawOwner() as its owner. | |
| closure_owner_ = field.RawOwner(); | |
| instructions += BuildExpression(); | |
| closure_owner_ = Object::null(); | |
| if (only_for_side_effects) { | |
| instructions += Drop(); | |
| } else { | |
| const auto& klass = Class::Handle(field.Owner()); | |
| instructions += flow_graph_builder_->StoreFieldGuarded( | |
| field, StoreFieldInstr::Kind::kInitializing, | |
| klass.is_deeply_immutable() && !field.has_deeply_immutable_type()); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildLateFieldInitializer( | |
| const Field& field, | |
| bool has_initializer) { | |
| if (has_initializer && PeekTag() == kNullLiteral) { | |
| SkipExpression(); // read past the null literal. | |
| if (H.thread()->IsDartMutatorThread()) { | |
| LeaveCompilerScope cs(H.thread()); | |
| field.RecordStore(Object::null_object()); | |
| } else { | |
| ASSERT(field.is_nullable_unsafe()); | |
| } | |
| return Fragment(); | |
| } | |
| Fragment instructions; | |
| instructions += LoadLocal(parsed_function()->receiver_var()); | |
| instructions += flow_graph_builder_->Constant(Object::sentinel()); | |
| instructions += flow_graph_builder_->StoreField( | |
| field, StoreFieldInstr::Kind::kInitializing); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildInitializers( | |
| const Class& parent_class) { | |
| ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull()); | |
| Fragment instructions; | |
| // Start by getting the position of the constructors initializer. | |
| intptr_t initializers_offset = -1; | |
| { | |
| AlternativeReadingScope alt(&reader_); | |
| SkipFunctionNode(); // read constructors function node. | |
| initializers_offset = ReaderOffset(); | |
| } | |
| bool is_redirecting_constructor = false; | |
| // Field which will be initialized by the initializer with the given index. | |
| GrowableArray<const Field*> initializer_fields(5); | |
| // Check if this is a redirecting constructor and collect all fields which | |
| // will be initialized by the constructor initializer list. | |
| { | |
| AlternativeReadingScope alt(&reader_, initializers_offset); | |
| const intptr_t list_length = | |
| ReadListLength(); // read initializers list length. | |
| initializer_fields.EnsureLength(list_length, nullptr); | |
| bool has_field_initializers = false; | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| if (PeekTag() == kRedirectingInitializer) { | |
| is_redirecting_constructor = true; | |
| } else if (PeekTag() == kFieldInitializer) { | |
| has_field_initializers = true; | |
| ReadTag(); | |
| ReadPosition(); | |
| ReadBool(); | |
| const NameIndex field_name = ReadCanonicalNameReference(); | |
| const Field& field = | |
| Field::Handle(Z, H.LookupFieldByKernelField(field_name)); | |
| initializer_fields[i] = &field; | |
| SkipExpression(); | |
| continue; | |
| } | |
| SkipInitializer(); | |
| } | |
| ASSERT(!is_redirecting_constructor || !has_field_initializers); | |
| } | |
| // These come from: | |
| // | |
| // class A { | |
| // var x = (expr); | |
| // } | |
| // | |
| // We don't want to do that when this is a Redirecting Constructors though | |
| // (i.e. has a single initializer being of type kRedirectingInitializer). | |
| if (!is_redirecting_constructor) { | |
| // Sort list of fields (represented as their kernel offsets) which will | |
| // be initialized by the constructor initializer list. We will not emit | |
| // StoreField instructions for those initializers though we will | |
| // still evaluate initialization expression for its side effects. | |
| GrowableArray<intptr_t> constructor_initialized_field_offsets( | |
| initializer_fields.length()); | |
| for (auto field : initializer_fields) { | |
| if (field != nullptr) { | |
| constructor_initialized_field_offsets.Add(field->kernel_offset()); | |
| } | |
| } | |
| constructor_initialized_field_offsets.Sort( | |
| [](const intptr_t* a, const intptr_t* b) { | |
| return static_cast<int>(*a) - static_cast<int>(*b); | |
| }); | |
| constructor_initialized_field_offsets.Add(-1); | |
| auto& kernel_data = TypedDataView::Handle(Z); | |
| Array& class_fields = Array::Handle(Z, parent_class.fields()); | |
| Field& class_field = Field::Handle(Z); | |
| intptr_t next_constructor_initialized_field_index = 0; | |
| for (intptr_t i = 0; i < class_fields.Length(); ++i) { | |
| class_field ^= class_fields.At(i); | |
| if (!class_field.is_static()) { | |
| const intptr_t field_offset = class_field.kernel_offset(); | |
| // Check if this field will be initialized by the constructor | |
| // initializer list. | |
| // Note that both class_fields and the list of initialized fields | |
| // are sorted by their kernel offset (by construction) - | |
| // so we don't need to perform the search. | |
| bool is_constructor_initialized = false; | |
| const intptr_t constructor_initialized_field_offset = | |
| constructor_initialized_field_offsets | |
| [next_constructor_initialized_field_index]; | |
| if (constructor_initialized_field_offset == field_offset) { | |
| next_constructor_initialized_field_index++; | |
| is_constructor_initialized = true; | |
| } | |
| kernel_data = class_field.KernelLibrary(); | |
| ASSERT(!kernel_data.IsNull()); | |
| AlternativeReadingScopeWithNewData alt(&reader_, &kernel_data, | |
| field_offset); | |
| FieldHelper field_helper(this); | |
| field_helper.ReadUntilExcluding(FieldHelper::kInitializer); | |
| const Tag initializer_tag = ReadTag(); | |
| if (class_field.is_late()) { | |
| if (!is_constructor_initialized) { | |
| instructions += BuildLateFieldInitializer( | |
| Field::ZoneHandle(Z, class_field.ptr()), | |
| initializer_tag == kSomething); | |
| } | |
| } else if (initializer_tag == kSomething) { | |
| EnterScope(field_offset); | |
| // If this field is initialized in constructor then we can ignore the | |
| // value produced by the field initializer. However we still need to | |
| // execute it for its side effects. | |
| instructions += BuildFieldInitializer( | |
| Field::ZoneHandle(Z, class_field.ptr()), | |
| /*only_for_side_effects=*/is_constructor_initialized); | |
| ExitScope(field_offset); | |
| } | |
| } | |
| } | |
| } | |
| // These to come from: | |
| // class A { | |
| // var x; | |
| // var y; | |
| // A(this.x) : super(expr), y = (expr); | |
| // } | |
| { | |
| AlternativeReadingScope alt(&reader_, initializers_offset); | |
| intptr_t list_length = ReadListLength(); // read initializers list length. | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| Tag tag = ReadTag(); | |
| switch (tag) { | |
| case kInvalidInitializer: { | |
| ReadPosition(); | |
| const String& message = H.DartString(ReadStringReference()); | |
| ReadByte(); // read flags | |
| // Invalid initializer message has pointer to the source code, no | |
| // need to report it twice. | |
| const auto& script = Script::Handle(Z, Script()); | |
| H.ReportError(script, TokenPosition::kNoSource, "%s", | |
| message.ToCString()); | |
| return Fragment(); | |
| } | |
| case kFieldInitializer: { | |
| ReadPosition(); // read position. | |
| ReadBool(); // read isSynthetic flag. | |
| ReadCanonicalNameReference(); | |
| instructions += BuildFieldInitializer( | |
| Field::ZoneHandle(Z, initializer_fields[i]->ptr()), | |
| /*only_for_side_effects=*/false); | |
| break; | |
| } | |
| case kAssertInitializer: { | |
| ReadPosition(); // read position. | |
| instructions += BuildStatement(); | |
| break; | |
| } | |
| case kSuperInitializer: { | |
| TokenPosition position = ReadPosition(); // read position. | |
| bool isSynthetic = ReadBool(); // read isSynthetic flag. | |
| NameIndex canonical_target = | |
| ReadCanonicalNameReference(); // read target_reference. | |
| instructions += LoadLocal(parsed_function()->receiver_var()); | |
| // TODO(jensj): ASSERT(init->arguments()->types().length() == 0); | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| intptr_t argument_count; | |
| instructions += BuildArguments( | |
| &argument_names, &argument_count, | |
| /*positional_argument_count=*/nullptr); // read arguments. | |
| argument_count += 1; | |
| Class& parent_klass = GetSuperOrDie(); | |
| const Function& target = Function::ZoneHandle( | |
| Z, H.LookupConstructorByKernelConstructor( | |
| parent_klass, H.CanonicalNameString(canonical_target))); | |
| instructions += StaticCall( | |
| isSynthetic ? TokenPosition::kNoSource : position, target, | |
| argument_count, argument_names, ICData::kStatic); | |
| instructions += Drop(); | |
| break; | |
| } | |
| case kRedirectingInitializer: { | |
| TokenPosition position = ReadPosition(); // read position. | |
| NameIndex canonical_target = | |
| ReadCanonicalNameReference(); // read target_reference. | |
| instructions += LoadLocal(parsed_function()->receiver_var()); | |
| // TODO(jensj): ASSERT(init->arguments()->types().length() == 0); | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| intptr_t argument_count; | |
| instructions += BuildArguments( | |
| &argument_names, &argument_count, | |
| /*positional_argument_count=*/nullptr); // read arguments. | |
| argument_count += 1; | |
| const Function& target = Function::ZoneHandle( | |
| Z, H.LookupConstructorByKernelConstructor(canonical_target)); | |
| instructions += StaticCall(position, target, argument_count, | |
| argument_names, ICData::kStatic); | |
| instructions += Drop(); | |
| break; | |
| } | |
| case kLocalInitializer: { | |
| ReadPosition(); // read position. | |
| // The other initializers following this one might read the variable. | |
| // This is used e.g. for evaluating the arguments to a super call | |
| // first, run normal field initializers next and then make the actual | |
| // super call: | |
| // | |
| // The frontend converts | |
| // | |
| // class A { | |
| // var x; | |
| // A(a, b) : super(a + b), x = 2*b {} | |
| // } | |
| // | |
| // to | |
| // | |
| // class A { | |
| // var x; | |
| // A(a, b) : tmp = a + b, x = 2*b, super(tmp) {} | |
| // } | |
| // | |
| // (This is strictly speaking not what one should do in terms of the | |
| // specification but that is how it is currently implemented.) | |
| LocalVariable* variable = | |
| LookupVariable(ReaderOffset() + data_program_offset_); | |
| // Variable | |
| VariableHelper helper(this); | |
| helper.ReadUntilExcluding(VariableHelper::kInitializer); | |
| ASSERT(!helper.IsConst()); | |
| Tag tag = ReadTag(); // read (first part of) initializer. | |
| if (tag != kSomething) { | |
| UNREACHABLE(); | |
| } | |
| instructions += BuildExpression(); // read initializer. | |
| instructions += StoreLocal(TokenPosition::kNoSource, variable); | |
| instructions += Drop(); | |
| break; | |
| } | |
| default: | |
| ReportUnexpectedTag("initializer", tag); | |
| UNREACHABLE(); | |
| } | |
| } | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::DebugStepCheckInPrologue( | |
| const Function& dart_function, | |
| TokenPosition position) { | |
| if (!NeedsDebugStepCheck(dart_function, position)) { | |
| return {}; | |
| } | |
| // Place this check at the last parameter to ensure parameters | |
| // are in scope in the debugger at method entry. | |
| const int parameter_count = dart_function.NumParameters(); | |
| TokenPosition check_pos = TokenPosition::kNoSource; | |
| if (parameter_count > 0) { | |
| const LocalVariable& parameter = | |
| *parsed_function()->ParameterVariable(parameter_count - 1); | |
| check_pos = parameter.token_pos(); | |
| } | |
| if (!check_pos.IsDebugPause()) { | |
| // No parameters or synthetic parameters. | |
| check_pos = position; | |
| ASSERT(check_pos.IsDebugPause()); | |
| } | |
| return DebugStepCheck(check_pos); | |
| } | |
| Fragment StreamingFlowGraphBuilder::TypeArgumentsHandling( | |
| const Function& dart_function) { | |
| Fragment prologue = B->BuildDefaultTypeHandling(dart_function); | |
| if (dart_function.IsClosureFunction() && | |
| dart_function.NumParentTypeArguments() > 0) { | |
| LocalVariable* closure = parsed_function()->ParameterVariable(0); | |
| LocalVariable* fn_type_args = parsed_function()->function_type_arguments(); | |
| ASSERT(fn_type_args != nullptr && closure != nullptr); | |
| ASSERT(Closure::HasFunctionTypeArgumentsField(dart_function)); | |
| const bool has_instantiator_type_args = | |
| Closure::HasInstantiatorTypeArgumentsField(dart_function); | |
| if (dart_function.IsGeneric()) { | |
| ASSERT(Closure::HasDelayedTypeArgumentsField(dart_function)); | |
| const intptr_t function_type_args_index = | |
| UntaggedClosure::FunctionTypeArgumentsIndex( | |
| /*has_delayed_type_args=*/true, has_instantiator_type_args); | |
| prologue += LoadLocal(fn_type_args); | |
| prologue += LoadLocal(closure); | |
| prologue += LoadNativeField(Slot::GetClosureElementSlot( | |
| thread(), | |
| compiler::target::Closure::element_offset(function_type_args_index))); | |
| prologue += IntConstant(dart_function.NumParentTypeArguments()); | |
| prologue += IntConstant(dart_function.NumTypeArguments()); | |
| const auto& prepend_function = | |
| flow_graph_builder_->PrependTypeArgumentsFunction(); | |
| prologue += StaticCall(TokenPosition::kNoSource, prepend_function, 4, | |
| ICData::kStatic); | |
| prologue += StoreLocal(TokenPosition::kNoSource, fn_type_args); | |
| prologue += Drop(); | |
| } else { | |
| ASSERT(!Closure::HasDelayedTypeArgumentsField(dart_function)); | |
| const intptr_t function_type_args_index = | |
| UntaggedClosure::FunctionTypeArgumentsIndex( | |
| /*has_delayed_type_args=*/false, has_instantiator_type_args); | |
| prologue += LoadLocal(closure); | |
| prologue += LoadNativeField(Slot::GetClosureElementSlot( | |
| thread(), | |
| compiler::target::Closure::element_offset(function_type_args_index))); | |
| prologue += StoreLocal(TokenPosition::kNoSource, fn_type_args); | |
| prologue += Drop(); | |
| } | |
| } | |
| return prologue; | |
| } | |
| Fragment StreamingFlowGraphBuilder::CheckStackOverflowInPrologue( | |
| const Function& dart_function) { | |
| if (dart_function.is_native()) return {}; | |
| return B->CheckStackOverflowInPrologue(dart_function.token_pos()); | |
| } | |
| Fragment StreamingFlowGraphBuilder::SetupCapturedParameters( | |
| const Function& dart_function) { | |
| Fragment body; | |
| const LocalScope* scope = parsed_function()->scope(); | |
| if (scope->num_context_variables() > 0) { | |
| body += flow_graph_builder_->PushContext(scope); | |
| LocalVariable* context = MakeTemporary(); | |
| // Copy captured parameters from the stack into the context. | |
| LocalScope* scope = parsed_function()->scope(); | |
| intptr_t parameter_count = dart_function.NumParameters(); | |
| const ParsedFunction& pf = *flow_graph_builder_->parsed_function_; | |
| const Function& function = pf.function(); | |
| for (intptr_t i = 0; i < parameter_count; ++i) { | |
| LocalVariable* variable = pf.ParameterVariable(i); | |
| if (variable->is_captured()) { | |
| LocalVariable& raw_parameter = *pf.RawParameterVariable(i); | |
| ASSERT((function.MakesCopyOfParameters() && | |
| raw_parameter.owner() == scope) || | |
| (!function.MakesCopyOfParameters() && | |
| raw_parameter.owner() == nullptr)); | |
| ASSERT(!raw_parameter.is_captured()); | |
| // Copy the parameter from the stack to the context. | |
| body += LoadLocal(context); | |
| body += LoadLocal(&raw_parameter); | |
| body += flow_graph_builder_->StoreNativeField( | |
| Slot::GetContextVariableSlotFor(thread(), *variable), | |
| StoreFieldInstr::Kind::kInitializing); | |
| } | |
| } | |
| body += Drop(); // The context. | |
| } | |
| return body; | |
| } | |
| Fragment StreamingFlowGraphBuilder::InitSuspendableFunction( | |
| const Function& dart_function, | |
| const AbstractType* emitted_value_type) { | |
| Fragment body; | |
| if (dart_function.IsAsyncFunction()) { | |
| ASSERT(emitted_value_type != nullptr); | |
| auto& type_args = TypeArguments::ZoneHandle(Z, TypeArguments::New(1)); | |
| type_args.SetTypeAt(0, *emitted_value_type); | |
| type_args = Class::Handle(Z, IG->object_store()->future_class()) | |
| .GetInstanceTypeArguments(H.thread(), type_args); | |
| body += TranslateInstantiatedTypeArguments(type_args); | |
| body += B->Call1ArgStub(TokenPosition::kNoSource, | |
| Call1ArgStubInstr::StubId::kInitAsync); | |
| body += Drop(); | |
| } else if (dart_function.IsAsyncGenerator()) { | |
| ASSERT(emitted_value_type != nullptr); | |
| auto& type_args = TypeArguments::ZoneHandle(Z, TypeArguments::New(1)); | |
| type_args.SetTypeAt(0, *emitted_value_type); | |
| type_args = Class::Handle(Z, IG->object_store()->stream_class()) | |
| .GetInstanceTypeArguments(H.thread(), type_args); | |
| body += TranslateInstantiatedTypeArguments(type_args); | |
| body += B->Call1ArgStub(TokenPosition::kNoSource, | |
| Call1ArgStubInstr::StubId::kInitAsyncStar); | |
| body += Drop(); | |
| body += NullConstant(); | |
| body += B->Suspend(TokenPosition::kNoSource, | |
| SuspendInstr::StubId::kYieldAsyncStar); | |
| body += Drop(); | |
| } else if (dart_function.IsSyncGenerator()) { | |
| ASSERT(emitted_value_type != nullptr); | |
| auto& type_args = TypeArguments::ZoneHandle(Z, TypeArguments::New(1)); | |
| type_args.SetTypeAt(0, *emitted_value_type); | |
| type_args = Class::Handle(Z, IG->object_store()->iterable_class()) | |
| .GetInstanceTypeArguments(H.thread(), type_args); | |
| body += TranslateInstantiatedTypeArguments(type_args); | |
| body += B->Call1ArgStub(TokenPosition::kNoSource, | |
| Call1ArgStubInstr::StubId::kInitSyncStar); | |
| body += Drop(); | |
| body += NullConstant(); | |
| body += B->Suspend(TokenPosition::kNoSource, | |
| SuspendInstr::StubId::kSuspendSyncStarAtStart); | |
| body += Drop(); | |
| // Clone context if there are any captured parameter variables, so | |
| // each invocation of .iterator would get its own copy of parameters. | |
| const LocalScope* scope = parsed_function()->scope(); | |
| if (scope->num_context_variables() > 0) { | |
| body += CloneContext(scope->context_slots()); | |
| } | |
| } else { | |
| ASSERT(emitted_value_type == nullptr); | |
| } | |
| return body; | |
| } | |
| Fragment StreamingFlowGraphBuilder::ShortcutForUserDefinedEquals( | |
| const Function& dart_function, | |
| LocalVariable* first_parameter) { | |
| // The specification defines the result of `a == b` to be: | |
| // | |
| // a) if either side is `null` then the result is `identical(a, b)`. | |
| // b) else the result is `a.operator==(b)` | |
| // | |
| // For user-defined implementations of `operator==` we need therefore | |
| // implement the handling of a). | |
| // | |
| // The default `operator==` implementation in `Object` is implemented in terms | |
| // of identical (which we assume here!) which means that case a) is actually | |
| // included in b). So we just use the normal implementation in the body. | |
| Fragment body; | |
| if ((dart_function.NumParameters() == 2) && | |
| (dart_function.name() == Symbols::EqualOperator().ptr()) && | |
| (dart_function.Owner() != IG->object_store()->object_class())) { | |
| TargetEntryInstr* null_entry; | |
| TargetEntryInstr* non_null_entry; | |
| body += LoadLocal(first_parameter); | |
| body += BranchIfNull(&null_entry, &non_null_entry); | |
| // The argument was `null` and the receiver is not the null class (we only | |
| // go into this branch for user-defined == operators) so we can return | |
| // false. | |
| Fragment null_fragment(null_entry); | |
| null_fragment += Constant(Bool::False()); | |
| null_fragment += Return(dart_function.end_token_pos()); | |
| body = Fragment(body.entry, non_null_entry); | |
| } | |
| return body; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildFunctionBody( | |
| const Function& dart_function, | |
| LocalVariable* first_parameter, | |
| bool constructor) { | |
| Fragment body; | |
| // TODO(27590): Currently the [Variable]s from the initializers will be | |
| // visible inside the entire body of the constructor. We should make a | |
| // separate scope for them. | |
| if (constructor) { | |
| body += BuildInitializers(Class::Handle(Z, dart_function.Owner())); | |
| } | |
| if (body.is_closed()) return body; | |
| FunctionNodeHelper function_node_helper(this); | |
| function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kBody); | |
| const bool has_body = ReadTag() == kSomething; // read first part of body. | |
| if (dart_function.is_old_native()) { | |
| body += B->NativeFunctionBody(dart_function, first_parameter); | |
| } else if (dart_function.is_ffi_native()) { | |
| body += B->FfiNativeFunctionBody(dart_function); | |
| } else if (dart_function.is_external()) { | |
| body += ThrowNoSuchMethodError(TokenPosition::kNoSource, dart_function, | |
| /*incompatible_arguments=*/false); | |
| ASSERT(body.is_closed()); | |
| } else if (has_body) { | |
| body += BuildStatement(); | |
| } | |
| if (body.is_open()) { | |
| if (parsed_function()->function().IsSyncGenerator()) { | |
| // Return false from sync* function to indicate the end of iteration. | |
| body += Constant(Bool::False()); | |
| } else { | |
| body += NullConstant(); | |
| } | |
| body += Return(dart_function.end_token_pos()); | |
| } | |
| return body; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildRegularFunctionPrologue( | |
| const Function& dart_function, | |
| TokenPosition token_position, | |
| LocalVariable* first_parameter) { | |
| Fragment F; | |
| F += CheckStackOverflowInPrologue(dart_function); | |
| F += DebugStepCheckInPrologue(dart_function, token_position); | |
| F += B->InitConstantParameters(); | |
| F += SetupCapturedParameters(dart_function); | |
| F += ShortcutForUserDefinedEquals(dart_function, first_parameter); | |
| return F; | |
| } | |
| Fragment StreamingFlowGraphBuilder::ClearRawParameters( | |
| const Function& dart_function) { | |
| const ParsedFunction& pf = *flow_graph_builder_->parsed_function_; | |
| Fragment code; | |
| for (intptr_t i = 0; i < dart_function.NumParameters(); ++i) { | |
| LocalVariable* variable = pf.ParameterVariable(i); | |
| if (!variable->is_captured()) continue; | |
| // Captured 'this' is immutable, so within the outer method we don't need to | |
| // load it from the context. Therefore we don't reset it to null. | |
| if (pf.function().HasThisParameter() && pf.has_receiver_var() && | |
| variable == pf.receiver_var()) { | |
| ASSERT(i == 0); | |
| continue; | |
| } | |
| variable = pf.RawParameterVariable(i); | |
| code += NullConstant(); | |
| code += StoreLocal(TokenPosition::kNoSource, variable); | |
| code += Drop(); | |
| } | |
| return code; | |
| } | |
| UncheckedEntryPointStyle StreamingFlowGraphBuilder::ChooseEntryPointStyle( | |
| const Function& dart_function, | |
| const Fragment& implicit_type_checks, | |
| const Fragment& regular_function_prologue, | |
| const Fragment& type_args_handling) { | |
| ASSERT(!dart_function.IsImplicitClosureFunction()); | |
| if (!dart_function.MayHaveUncheckedEntryPoint() || | |
| implicit_type_checks.is_empty()) { | |
| return UncheckedEntryPointStyle::kNone; | |
| } | |
| // Record which entry-point was taken into a variable and test it later if | |
| // either: | |
| // | |
| // 1. There is a non-empty PrologueBuilder-prologue. | |
| // | |
| // 2. The regular function prologue has more than two instructions | |
| // (DebugStepCheck and CheckStackOverflow). | |
| // | |
| if (!PrologueBuilder::HasEmptyPrologue(dart_function) || | |
| !type_args_handling.is_empty()) { | |
| return UncheckedEntryPointStyle::kSharedWithVariable; | |
| } | |
| Instruction* instr = regular_function_prologue.entry; | |
| if (instr != nullptr && instr->IsCheckStackOverflow()) { | |
| instr = instr->next(); | |
| } | |
| if (instr != nullptr && instr->IsDebugStepCheck()) { | |
| instr = instr->next(); | |
| } | |
| if (instr != nullptr) { | |
| return UncheckedEntryPointStyle::kSharedWithVariable; | |
| } | |
| return UncheckedEntryPointStyle::kSeparate; | |
| } | |
| FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction( | |
| bool is_constructor) { | |
| const Function& dart_function = parsed_function()->function(); | |
| LocalVariable* first_parameter = nullptr; | |
| TokenPosition token_position = TokenPosition::kNoSource; | |
| const AbstractType* emitted_value_type = nullptr; | |
| { | |
| AlternativeReadingScope alt(&reader_); | |
| FunctionNodeHelper function_node_helper(this); | |
| function_node_helper.ReadUntilExcluding( | |
| FunctionNodeHelper::kPositionalParameters); | |
| { | |
| AlternativeReadingScope alt2(&reader_); | |
| intptr_t list_length = ReadListLength(); // read number of positionals. | |
| if (list_length > 0) { | |
| intptr_t first_parameter_offset = ReaderOffset() + data_program_offset_; | |
| first_parameter = LookupVariable(first_parameter_offset); | |
| } | |
| } | |
| token_position = function_node_helper.position_; | |
| if (dart_function.IsSuspendableFunction()) { | |
| function_node_helper.ReadUntilExcluding( | |
| FunctionNodeHelper::kEmittedValueType); | |
| if (ReadTag() == kSomething) { | |
| emitted_value_type = &T.BuildType(); // read emitted value type. | |
| } else { | |
| UNREACHABLE(); | |
| } | |
| } | |
| } | |
| auto graph_entry = flow_graph_builder_->graph_entry_ = | |
| new (Z) GraphEntryInstr(*parsed_function(), flow_graph_builder_->osr_id_); | |
| auto normal_entry = flow_graph_builder_->BuildFunctionEntry(graph_entry); | |
| graph_entry->set_normal_entry(normal_entry); | |
| PrologueInfo prologue_info(-1, -1); | |
| BlockEntryInstr* instruction_cursor = | |
| flow_graph_builder_->BuildPrologue(normal_entry, &prologue_info); | |
| const Fragment regular_prologue = BuildRegularFunctionPrologue( | |
| dart_function, token_position, first_parameter); | |
| // TODO(#34162): We can remove the default type handling (and | |
| // shorten the prologue type handling sequence) for non-dynamic invocations of | |
| // regular methods. | |
| const Fragment type_args_handling = TypeArgumentsHandling(dart_function); | |
| Fragment implicit_type_checks; | |
| if (dart_function.NeedsTypeArgumentTypeChecks()) { | |
| B->BuildTypeArgumentTypeChecks( | |
| TypeChecksToBuild::kCheckCovariantTypeParameterBounds, | |
| &implicit_type_checks); | |
| } | |
| Fragment explicit_type_checks; | |
| Fragment implicit_redefinitions; | |
| if (dart_function.NeedsArgumentTypeChecks()) { | |
| B->BuildArgumentTypeChecks(&explicit_type_checks, &implicit_type_checks, | |
| &implicit_redefinitions); | |
| } | |
| // The RawParameter variables should be set to null to avoid retaining more | |
| // objects than necessary during GC. | |
| const Fragment body = | |
| ClearRawParameters(dart_function) + | |
| InitSuspendableFunction(dart_function, emitted_value_type) + | |
| BuildFunctionBody(dart_function, first_parameter, is_constructor); | |
| auto extra_entry_point_style = | |
| ChooseEntryPointStyle(dart_function, implicit_type_checks, | |
| regular_prologue, type_args_handling); | |
| Fragment function(instruction_cursor); | |
| FunctionEntryInstr* extra_entry = nullptr; | |
| switch (extra_entry_point_style) { | |
| case UncheckedEntryPointStyle::kNone: { | |
| function += regular_prologue + type_args_handling + implicit_type_checks + | |
| explicit_type_checks + body; | |
| break; | |
| } | |
| case UncheckedEntryPointStyle::kSeparate: { | |
| ASSERT(instruction_cursor == normal_entry); | |
| ASSERT(type_args_handling.is_empty()); | |
| const Fragment prologue_copy = BuildRegularFunctionPrologue( | |
| dart_function, token_position, first_parameter); | |
| extra_entry = B->BuildSeparateUncheckedEntryPoint( | |
| normal_entry, | |
| /*normal_prologue=*/regular_prologue + implicit_type_checks, | |
| /*extra_prologue=*/prologue_copy, | |
| /*shared_prologue=*/explicit_type_checks, | |
| /*body=*/body); | |
| break; | |
| } | |
| case UncheckedEntryPointStyle::kSharedWithVariable: { | |
| Fragment prologue(normal_entry, instruction_cursor); | |
| prologue += regular_prologue; | |
| prologue += type_args_handling; | |
| prologue += explicit_type_checks; | |
| extra_entry = B->BuildSharedUncheckedEntryPoint( | |
| /*prologue_from_normal_entry=*/prologue, | |
| /*skippable_checks=*/implicit_type_checks, | |
| /*redefinitions_if_skipped=*/implicit_redefinitions, | |
| /*body=*/body); | |
| break; | |
| } | |
| } | |
| if (extra_entry != nullptr) { | |
| B->RecordUncheckedEntryPoint(graph_entry, extra_entry); | |
| } | |
| auto flow_graph = new (Z) FlowGraph( | |
| *parsed_function(), graph_entry, flow_graph_builder_->last_used_block_id_, | |
| prologue_info, | |
| FlowGraph::CompilationModeFrom(flow_graph_builder_->optimizing())); | |
| // 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. | |
| // Include enclosing try blocks with corresponding catch blocks. | |
| if (flow_graph_builder_->IsCompiledForOsr()) { | |
| auto result = graph_entry->FindOsrEntry( | |
| Z, flow_graph_builder_->last_used_block_id_ + 1); | |
| flow_graph_builder_->RelinkToOsrEntry(result); | |
| flow_graph = new (Z) FlowGraph( | |
| *parsed_function(), graph_entry, | |
| flow_graph_builder_->last_used_block_id_, prologue_info, | |
| FlowGraph::CompilationModeFrom(flow_graph_builder_->optimizing_)); | |
| } | |
| return flow_graph; | |
| } | |
| FlowGraph* StreamingFlowGraphBuilder::BuildGraph() { | |
| ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull()); | |
| ASSERT(flow_graph_builder_ != nullptr); | |
| const Function& function = parsed_function()->function(); | |
| // Setup an [ActiveClassScope] and an [ActiveMemberScope] which will be used | |
| // e.g. for type translation. | |
| const Class& klass = | |
| Class::Handle(zone_, parsed_function()->function().Owner()); | |
| Function& outermost_function = | |
| Function::Handle(Z, function.GetOutermostFunction()); | |
| ActiveClassScope active_class_scope(active_class(), &klass); | |
| ActiveMemberScope active_member(active_class(), &outermost_function); | |
| FunctionType& signature = FunctionType::Handle(Z, function.signature()); | |
| ActiveTypeParametersScope active_type_params(active_class(), function, | |
| &signature, Z); | |
| ParseKernelASTFunction(); | |
| switch (function.kind()) { | |
| case UntaggedFunction::kRegularFunction: | |
| case UntaggedFunction::kGetterFunction: | |
| case UntaggedFunction::kSetterFunction: | |
| case UntaggedFunction::kClosureFunction: | |
| case UntaggedFunction::kConstructor: { | |
| if (FlowGraphBuilder::IsRecognizedMethodForFlowGraph(function)) { | |
| return B->BuildGraphOfRecognizedMethod(function); | |
| } | |
| return BuildGraphOfFunction(function.IsGenerativeConstructor()); | |
| } | |
| case UntaggedFunction::kImplicitGetter: | |
| case UntaggedFunction::kImplicitStaticGetter: | |
| case UntaggedFunction::kImplicitSetter: { | |
| return B->BuildGraphOfFieldAccessor(function); | |
| } | |
| case UntaggedFunction::kFieldInitializer: | |
| return BuildGraphOfFieldInitializer(); | |
| case UntaggedFunction::kDynamicInvocationForwarder: | |
| return B->BuildGraphOfDynamicInvocationForwarder(function); | |
| case UntaggedFunction::kMethodExtractor: | |
| return flow_graph_builder_->BuildGraphOfMethodExtractor(function); | |
| case UntaggedFunction::kNoSuchMethodDispatcher: | |
| return flow_graph_builder_->BuildGraphOfNoSuchMethodDispatcher(function); | |
| case UntaggedFunction::kInvokeFieldDispatcher: | |
| return flow_graph_builder_->BuildGraphOfInvokeFieldDispatcher(function); | |
| case UntaggedFunction::kImplicitClosureFunction: | |
| return flow_graph_builder_->BuildGraphOfImplicitClosureFunction(function); | |
| case UntaggedFunction::kFfiTrampoline: | |
| return flow_graph_builder_->BuildGraphOfFfiTrampoline(function); | |
| case UntaggedFunction::kRecordFieldGetter: | |
| return flow_graph_builder_->BuildGraphOfRecordFieldGetter(function); | |
| } | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| void StreamingFlowGraphBuilder::ParseKernelASTFunction() { | |
| const Function& function = parsed_function()->function(); | |
| if (!function.IsNoSuchMethodDispatcher() && | |
| !function.IsInvokeFieldDispatcher() && | |
| !function.IsFfiCallbackTrampoline()) { | |
| const intptr_t kernel_offset = function.kernel_offset(); | |
| ASSERT(kernel_offset >= 0); | |
| SetOffset(kernel_offset); | |
| } | |
| set_scopes(parsed_function()->EnsureKernelScopes()); | |
| switch (function.kind()) { | |
| case UntaggedFunction::kRegularFunction: | |
| case UntaggedFunction::kGetterFunction: | |
| case UntaggedFunction::kSetterFunction: | |
| case UntaggedFunction::kClosureFunction: | |
| case UntaggedFunction::kConstructor: | |
| case UntaggedFunction::kImplicitClosureFunction: | |
| ReadUntilFunctionNode(); | |
| SetupDefaultParameterValues(); | |
| break; | |
| case UntaggedFunction::kImplicitGetter: | |
| case UntaggedFunction::kImplicitStaticGetter: | |
| case UntaggedFunction::kImplicitSetter: | |
| case UntaggedFunction::kFieldInitializer: | |
| case UntaggedFunction::kMethodExtractor: | |
| case UntaggedFunction::kNoSuchMethodDispatcher: | |
| case UntaggedFunction::kInvokeFieldDispatcher: | |
| case UntaggedFunction::kFfiTrampoline: | |
| case UntaggedFunction::kRecordFieldGetter: | |
| break; | |
| case UntaggedFunction::kDynamicInvocationForwarder: | |
| if (PeekTag() != kField) { | |
| ReadUntilFunctionNode(); | |
| SetupDefaultParameterValues(); | |
| } | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildStatementAt(intptr_t kernel_offset) { | |
| SetOffset(kernel_offset); | |
| return BuildStatement(); // read statement. | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildStatementAtWithBranchCoverage( | |
| intptr_t kernel_offset) { | |
| SetOffset(kernel_offset); | |
| return BuildStatementWithBranchCoverage(); // read statement. | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildExpression(TokenPosition* position) { | |
| ++num_ast_nodes_; | |
| uint8_t payload = 0; | |
| Tag tag = ReadTag(&payload); // read tag. | |
| switch (tag) { | |
| case kInvalidExpression: | |
| return BuildInvalidExpression(position); | |
| case kVariableGet: | |
| return BuildVariableGet(position); | |
| case kSpecializedVariableGet: | |
| return BuildVariableGet(payload, position); | |
| case kVariableSet: | |
| return BuildVariableSet(position); | |
| case kSpecializedVariableSet: | |
| return BuildVariableSet(payload, position); | |
| case kInstanceGet: | |
| return BuildInstanceGet(position); | |
| case kDynamicGet: | |
| return BuildDynamicGet(position); | |
| case kInstanceTearOff: | |
| return BuildInstanceTearOff(position); | |
| case kFunctionTearOff: | |
| // Removed by lowering kernel transformation. | |
| UNREACHABLE(); | |
| break; | |
| case kInstanceSet: | |
| return BuildInstanceSet(position); | |
| case kDynamicSet: | |
| return BuildDynamicSet(position); | |
| case kAbstractSuperPropertyGet: | |
| // Abstract super property getters must be converted into super property | |
| // getters during mixin transformation. | |
| UNREACHABLE(); | |
| break; | |
| case kAbstractSuperPropertySet: | |
| // Abstract super property setters must be converted into super property | |
| // setters during mixin transformation. | |
| UNREACHABLE(); | |
| break; | |
| case kSuperPropertyGet: | |
| return BuildSuperPropertyGet(position); | |
| case kSuperPropertySet: | |
| return BuildSuperPropertySet(position); | |
| case kStaticGet: | |
| return BuildStaticGet(position); | |
| case kStaticSet: | |
| return BuildStaticSet(position); | |
| case kInstanceInvocation: | |
| return BuildMethodInvocation(position, /*is_dynamic=*/false); | |
| case kDynamicInvocation: | |
| return BuildMethodInvocation(position, /*is_dynamic=*/true); | |
| case kLocalFunctionInvocation: | |
| return BuildLocalFunctionInvocation(position); | |
| case kFunctionInvocation: | |
| return BuildFunctionInvocation(position); | |
| case kEqualsCall: | |
| return BuildEqualsCall(position); | |
| case kEqualsNull: | |
| return BuildEqualsNull(position); | |
| case kAbstractSuperMethodInvocation: | |
| // Abstract super method invocations must be converted into super | |
| // method invocations during mixin transformation. | |
| UNREACHABLE(); | |
| break; | |
| case kSuperMethodInvocation: | |
| return BuildSuperMethodInvocation(position); | |
| case kStaticInvocation: | |
| return BuildStaticInvocation(position); | |
| case kConstructorInvocation: | |
| return BuildConstructorInvocation(position); | |
| case kNot: | |
| return BuildNot(position); | |
| case kNullCheck: | |
| return BuildNullCheck(position); | |
| case kLogicalExpression: | |
| return BuildLogicalExpression(position); | |
| case kConditionalExpression: | |
| return BuildConditionalExpression(position); | |
| case kStringConcatenation: | |
| return BuildStringConcatenation(position); | |
| case kIsExpression: | |
| return BuildIsExpression(position); | |
| case kAsExpression: | |
| return BuildAsExpression(position); | |
| case kTypeLiteral: | |
| return BuildTypeLiteral(position); | |
| case kThisExpression: | |
| return BuildThisExpression(position); | |
| case kRethrow: | |
| return BuildRethrow(position); | |
| case kThrow: | |
| return BuildThrow(position); | |
| case kListLiteral: | |
| return BuildListLiteral(position); | |
| case kSetLiteral: | |
| // Set literals are currently desugared in the frontend and will not | |
| // reach the VM. See http://dartbug.com/35124 for discussion. | |
| UNREACHABLE(); | |
| break; | |
| case kMapLiteral: | |
| return BuildMapLiteral(position); | |
| case kRecordLiteral: | |
| return BuildRecordLiteral(position); | |
| case kRecordIndexGet: | |
| return BuildRecordFieldGet(position, /*is_named=*/false); | |
| case kRecordNameGet: | |
| return BuildRecordFieldGet(position, /*is_named=*/true); | |
| case kFunctionExpression: | |
| return BuildFunctionExpression(); | |
| case kLet: | |
| return BuildLet(position); | |
| case kBlockExpression: | |
| return BuildBlockExpression(); | |
| case kBigIntLiteral: | |
| return BuildBigIntLiteral(position); | |
| case kStringLiteral: | |
| return BuildStringLiteral(position); | |
| case kSpecializedIntLiteral: | |
| return BuildIntLiteral(payload, position); | |
| case kNegativeIntLiteral: | |
| return BuildIntLiteral(true, position); | |
| case kPositiveIntLiteral: | |
| return BuildIntLiteral(false, position); | |
| case kDoubleLiteral: | |
| return BuildDoubleLiteral(position); | |
| case kTrueLiteral: | |
| return BuildBoolLiteral(true, position); | |
| case kFalseLiteral: | |
| return BuildBoolLiteral(false, position); | |
| case kNullLiteral: | |
| return BuildNullLiteral(position); | |
| case kConstantExpression: | |
| case kFileUriConstantExpression: | |
| return BuildConstantExpression(position, tag); | |
| case kInstantiation: | |
| return BuildPartialTearoffInstantiation(position); | |
| case kLoadLibrary: | |
| return BuildLibraryPrefixAction(position, Symbols::LoadLibrary()); | |
| case kCheckLibraryIsLoaded: | |
| return BuildLibraryPrefixAction(position, Symbols::CheckLoaded()); | |
| case kAwaitExpression: | |
| return BuildAwaitExpression(position); | |
| case kFileUriExpression: | |
| return BuildFileUriExpression(position); | |
| case kConstStaticInvocation: | |
| case kConstConstructorInvocation: | |
| case kConstListLiteral: | |
| case kConstSetLiteral: | |
| case kConstMapLiteral: | |
| case kSymbolLiteral: | |
| case kListConcatenation: | |
| case kSetConcatenation: | |
| case kMapConcatenation: | |
| case kInstanceCreation: | |
| case kStaticTearOff: | |
| case kSwitchExpression: | |
| case kPatternAssignment: | |
| case kRedirectingFactoryInvocation: | |
| // These nodes are internal to the front end and | |
| // removed by the constant evaluator. | |
| default: | |
| ReportUnexpectedTag("expression", tag); | |
| UNREACHABLE(); | |
| } | |
| return Fragment(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildStatement(TokenPosition* position) { | |
| ++num_ast_nodes_; | |
| Tag tag = ReadTag(); // read tag. | |
| switch (tag) { | |
| case kExpressionStatement: | |
| return BuildExpressionStatement(position); | |
| case kBlock: | |
| return BuildBlock(position); | |
| case kEmptyStatement: | |
| return BuildEmptyStatement(); | |
| case kAssertBlock: | |
| return BuildAssertBlock(position); | |
| case kAssertStatement: | |
| return BuildAssertStatement(position); | |
| case kLabeledStatement: | |
| return BuildLabeledStatement(position); | |
| case kBreakStatement: | |
| return BuildBreakStatement(position); | |
| case kWhileStatement: | |
| return BuildWhileStatement(position); | |
| case kDoStatement: | |
| return BuildDoStatement(position); | |
| case kForStatement: | |
| return BuildForStatement(position); | |
| case kSwitchStatement: | |
| return BuildSwitchStatement(position); | |
| case kContinueSwitchStatement: | |
| return BuildContinueSwitchStatement(position); | |
| case kIfStatement: | |
| return BuildIfStatement(position); | |
| case kReturnStatement: | |
| return BuildReturnStatement(position); | |
| case kTryCatch: | |
| return BuildTryCatch(position); | |
| case kTryFinally: | |
| return BuildTryFinally(position); | |
| case kYieldStatement: | |
| return BuildYieldStatement(position); | |
| case kVariableStatement: | |
| return BuildVariableStatement(position); | |
| case kFunctionDeclaration: | |
| return BuildFunctionDeclaration(position); | |
| case kForInStatement: | |
| case kAsyncForInStatement: | |
| case kIfCaseStatement: | |
| case kPatternSwitchStatement: | |
| case kPatternVariableDeclaration: | |
| // These nodes are internal to the front end and | |
| // removed by the constant evaluator. | |
| default: | |
| ReportUnexpectedTag("statement", tag); | |
| UNREACHABLE(); | |
| } | |
| return Fragment(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildStatementWithBranchCoverage( | |
| TokenPosition* position) { | |
| TokenPosition pos = TokenPosition::kNoSource; | |
| Fragment statement = BuildStatement(&pos); | |
| if (position != nullptr) *position = pos; | |
| Fragment covered_statement = flow_graph_builder_->RecordBranchCoverage(pos); | |
| covered_statement += statement; | |
| return covered_statement; | |
| } | |
| void StreamingFlowGraphBuilder::ReportUnexpectedTag(const char* variant, | |
| Tag tag) { | |
| if ((flow_graph_builder_ == nullptr) || (parsed_function() == nullptr)) { | |
| KernelReaderHelper::ReportUnexpectedTag(variant, tag); | |
| } else { | |
| const auto& script = Script::Handle(Z, Script()); | |
| H.ReportError(script, TokenPosition::kNoSource, | |
| "Unexpected tag %d (%s) in %s, expected %s", tag, | |
| Reader::TagName(tag), | |
| parsed_function()->function().ToQualifiedCString(), variant); | |
| } | |
| } | |
| Tag KernelReaderHelper::ReadTag(uint8_t* payload) { | |
| return reader_.ReadTag(payload); | |
| } | |
| Tag KernelReaderHelper::PeekTag(uint8_t* payload) { | |
| return reader_.PeekTag(payload); | |
| } | |
| Nullability KernelReaderHelper::ReadNullability() { | |
| return reader_.ReadNullability(); | |
| } | |
| Variance KernelReaderHelper::ReadVariance() { | |
| return reader_.ReadVariance(); | |
| } | |
| void StreamingFlowGraphBuilder::loop_depth_inc() { | |
| ++flow_graph_builder_->loop_depth_; | |
| } | |
| void StreamingFlowGraphBuilder::loop_depth_dec() { | |
| --flow_graph_builder_->loop_depth_; | |
| } | |
| void StreamingFlowGraphBuilder::catch_depth_inc() { | |
| ++flow_graph_builder_->catch_depth_; | |
| } | |
| void StreamingFlowGraphBuilder::catch_depth_dec() { | |
| --flow_graph_builder_->catch_depth_; | |
| } | |
| void StreamingFlowGraphBuilder::try_depth_inc() { | |
| ++flow_graph_builder_->try_depth_; | |
| } | |
| void StreamingFlowGraphBuilder::try_depth_dec() { | |
| --flow_graph_builder_->try_depth_; | |
| } | |
| intptr_t StreamingFlowGraphBuilder::block_expression_depth() { | |
| return flow_graph_builder_->block_expression_depth_; | |
| } | |
| void StreamingFlowGraphBuilder::block_expression_depth_inc() { | |
| ++flow_graph_builder_->block_expression_depth_; | |
| } | |
| void StreamingFlowGraphBuilder::block_expression_depth_dec() { | |
| --flow_graph_builder_->block_expression_depth_; | |
| } | |
| void StreamingFlowGraphBuilder::synthetic_error_handler_depth_inc() { | |
| ++synthetic_error_handler_depth_; | |
| } | |
| void StreamingFlowGraphBuilder::synthetic_error_handler_depth_dec() { | |
| --synthetic_error_handler_depth_; | |
| } | |
| intptr_t StreamingFlowGraphBuilder::CurrentTryIndex() { | |
| return flow_graph_builder_->CurrentTryIndex(); | |
| } | |
| intptr_t StreamingFlowGraphBuilder::AllocateTryIndex() { | |
| return flow_graph_builder_->AllocateTryIndex(); | |
| } | |
| LocalVariable* StreamingFlowGraphBuilder::CurrentException() { | |
| return flow_graph_builder_->CurrentException(); | |
| } | |
| LocalVariable* StreamingFlowGraphBuilder::CurrentStackTrace() { | |
| return flow_graph_builder_->CurrentStackTrace(); | |
| } | |
| CatchBlock* StreamingFlowGraphBuilder::catch_block() { | |
| return flow_graph_builder_->catch_block_; | |
| } | |
| ActiveClass* StreamingFlowGraphBuilder::active_class() { | |
| return active_class_; | |
| } | |
| ScopeBuildingResult* StreamingFlowGraphBuilder::scopes() { | |
| return flow_graph_builder_->scopes_; | |
| } | |
| void StreamingFlowGraphBuilder::set_scopes(ScopeBuildingResult* scope) { | |
| flow_graph_builder_->scopes_ = scope; | |
| } | |
| ParsedFunction* StreamingFlowGraphBuilder::parsed_function() { | |
| return flow_graph_builder_->parsed_function_; | |
| } | |
| TryFinallyBlock* StreamingFlowGraphBuilder::try_finally_block() { | |
| return flow_graph_builder_->try_finally_block_; | |
| } | |
| SwitchBlock* StreamingFlowGraphBuilder::switch_block() { | |
| return flow_graph_builder_->switch_block_; | |
| } | |
| BreakableBlock* StreamingFlowGraphBuilder::breakable_block() { | |
| return flow_graph_builder_->breakable_block_; | |
| } | |
| Value* StreamingFlowGraphBuilder::stack() { | |
| return flow_graph_builder_->stack_; | |
| } | |
| void StreamingFlowGraphBuilder::set_stack(Value* top) { | |
| flow_graph_builder_->stack_ = top; | |
| } | |
| void StreamingFlowGraphBuilder::Push(Definition* definition) { | |
| flow_graph_builder_->Push(definition); | |
| } | |
| Value* StreamingFlowGraphBuilder::Pop() { | |
| return flow_graph_builder_->Pop(); | |
| } | |
| Tag StreamingFlowGraphBuilder::PeekArgumentsFirstPositionalTag() { | |
| // read parts of arguments, then go back to before doing so. | |
| AlternativeReadingScope alt(&reader_); | |
| ReadUInt(); // read number of arguments. | |
| SkipListOfDartTypes(); // Read list of types. | |
| // List of positional. | |
| intptr_t list_length = ReadListLength(); // read list length. | |
| if (list_length > 0) { | |
| return ReadTag(); // read first tag. | |
| } | |
| UNREACHABLE(); | |
| return kNothing; | |
| } | |
| const TypeArguments& StreamingFlowGraphBuilder::PeekArgumentsInstantiatedType( | |
| const Class& klass) { | |
| // read parts of arguments, then go back to before doing so. | |
| AlternativeReadingScope alt(&reader_); | |
| ReadUInt(); // read argument count. | |
| intptr_t list_length = ReadListLength(); // read types list length. | |
| return T.BuildInstantiatedTypeArguments(klass, list_length); // read types. | |
| } | |
| intptr_t StreamingFlowGraphBuilder::PeekArgumentsCount() { | |
| return PeekUInt(); | |
| } | |
| TokenPosition StreamingFlowGraphBuilder::ReadPosition() { | |
| TokenPosition position = KernelReaderHelper::ReadPosition(); | |
| if (synthetic_error_handler_depth_ > 0 && position.IsReal()) { | |
| position = TokenPosition::Synthetic(position.Pos()); | |
| } | |
| return position; | |
| } | |
| LocalVariable* StreamingFlowGraphBuilder::LookupVariable( | |
| intptr_t kernel_offset) { | |
| return flow_graph_builder_->LookupVariable(kernel_offset); | |
| } | |
| LocalVariable* StreamingFlowGraphBuilder::MakeTemporary(const char* suffix) { | |
| return flow_graph_builder_->MakeTemporary(suffix); | |
| } | |
| Fragment StreamingFlowGraphBuilder::DropTemporary(LocalVariable** variable) { | |
| return flow_graph_builder_->DropTemporary(variable); | |
| } | |
| Function& StreamingFlowGraphBuilder::FindMatchingFunction( | |
| const Class& klass, | |
| const String& name, | |
| int type_args_len, | |
| int argument_count, | |
| const Array& argument_names) { | |
| // Search the superclass chain for the selector. | |
| ArgumentsDescriptor args_desc( | |
| Array::Handle(Z, ArgumentsDescriptor::NewBoxed( | |
| type_args_len, argument_count, argument_names))); | |
| return Function::Handle(Z, Resolver::ResolveDynamicForReceiverClass( | |
| klass, name, args_desc, /*allow_add=*/false)); | |
| } | |
| bool StreamingFlowGraphBuilder::NeedsDebugStepCheck(const Function& function, | |
| TokenPosition position) { | |
| return flow_graph_builder_->NeedsDebugStepCheck(function, position); | |
| } | |
| bool StreamingFlowGraphBuilder::NeedsDebugStepCheck(Value* value, | |
| TokenPosition position) { | |
| return flow_graph_builder_->NeedsDebugStepCheck(value, position); | |
| } | |
| void StreamingFlowGraphBuilder::InlineBailout(const char* reason) { | |
| flow_graph_builder_->InlineBailout(reason); | |
| } | |
| Fragment StreamingFlowGraphBuilder::DebugStepCheck(TokenPosition position) { | |
| return flow_graph_builder_->DebugStepCheck(position); | |
| } | |
| Fragment StreamingFlowGraphBuilder::LoadLocal(LocalVariable* variable) { | |
| return flow_graph_builder_->LoadLocal(variable); | |
| } | |
| IndirectGotoInstr* StreamingFlowGraphBuilder::IndirectGoto( | |
| intptr_t target_count) { | |
| return flow_graph_builder_->IndirectGoto(target_count); | |
| } | |
| Fragment StreamingFlowGraphBuilder::Return(TokenPosition position) { | |
| return flow_graph_builder_->Return(position, | |
| /*omit_result_type_check=*/false); | |
| } | |
| Fragment StreamingFlowGraphBuilder::RethrowException(TokenPosition position, | |
| int catch_try_index) { | |
| return flow_graph_builder_->RethrowException(position, catch_try_index); | |
| } | |
| Fragment StreamingFlowGraphBuilder::ThrowNoSuchMethodError( | |
| TokenPosition position, | |
| const Function& target, | |
| bool incompatible_arguments) { | |
| return flow_graph_builder_->ThrowNoSuchMethodError(position, target, | |
| incompatible_arguments); | |
| } | |
| Fragment StreamingFlowGraphBuilder::Constant(const Object& value) { | |
| return flow_graph_builder_->Constant(value); | |
| } | |
| Fragment StreamingFlowGraphBuilder::IntConstant(int64_t value) { | |
| return flow_graph_builder_->IntConstant(value); | |
| } | |
| Fragment StreamingFlowGraphBuilder::LoadStaticField(const Field& field, | |
| bool calls_initializer) { | |
| return flow_graph_builder_->LoadStaticField(field, calls_initializer); | |
| } | |
| Fragment StreamingFlowGraphBuilder::RedefinitionWithType( | |
| const AbstractType& type) { | |
| return flow_graph_builder_->RedefinitionWithType(type); | |
| } | |
| Fragment StreamingFlowGraphBuilder::CheckNull(TokenPosition position, | |
| LocalVariable* receiver, | |
| const String& function_name) { | |
| return flow_graph_builder_->CheckNull(position, receiver, function_name); | |
| } | |
| Fragment StreamingFlowGraphBuilder::StaticCall(TokenPosition position, | |
| const Function& target, | |
| intptr_t argument_count, | |
| ICData::RebindRule rebind_rule) { | |
| if (!target.AreValidArgumentCounts(0, argument_count, 0, nullptr)) { | |
| Fragment instructions; | |
| instructions += DropArguments(argument_count, /*type_args_count=*/0); | |
| instructions += ThrowNoSuchMethodError(position, target, | |
| /*incompatible_arguments=*/true); | |
| return instructions; | |
| } | |
| return flow_graph_builder_->StaticCall(position, target, argument_count, | |
| rebind_rule); | |
| } | |
| Fragment StreamingFlowGraphBuilder::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) { | |
| if (!target.AreValidArguments(type_args_count, argument_count, argument_names, | |
| nullptr)) { | |
| Fragment instructions; | |
| instructions += DropArguments(argument_count, type_args_count); | |
| instructions += ThrowNoSuchMethodError(position, target, | |
| /*incompatible_arguments=*/true); | |
| return instructions; | |
| } | |
| return flow_graph_builder_->StaticCall( | |
| position, target, argument_count, argument_names, rebind_rule, | |
| result_type, type_args_count, use_unchecked_entry); | |
| } | |
| Fragment StreamingFlowGraphBuilder::StaticCallMissing( | |
| TokenPosition position, | |
| const String& selector, | |
| intptr_t argument_count, | |
| InvocationMirror::Level level, | |
| InvocationMirror::Kind kind) { | |
| Fragment instructions; | |
| instructions += DropArguments(argument_count, /*type_args_count=*/0); | |
| instructions += flow_graph_builder_->ThrowNoSuchMethodError( | |
| position, selector, level, kind); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::InstanceCall( | |
| TokenPosition position, | |
| const String& name, | |
| Token::Kind kind, | |
| intptr_t argument_count, | |
| intptr_t checked_argument_count) { | |
| const intptr_t kTypeArgsLen = 0; | |
| return flow_graph_builder_->InstanceCall(position, name, kind, kTypeArgsLen, | |
| argument_count, Array::null_array(), | |
| checked_argument_count); | |
| } | |
| Fragment StreamingFlowGraphBuilder::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, | |
| bool is_call_on_this) { | |
| return flow_graph_builder_->InstanceCall( | |
| position, name, kind, type_args_len, argument_count, argument_names, | |
| checked_argument_count, interface_target, tearoff_interface_target, | |
| result_type, use_unchecked_entry, call_site_attrs, receiver_is_not_smi, | |
| is_call_on_this); | |
| } | |
| Fragment StreamingFlowGraphBuilder::ThrowException(TokenPosition position) { | |
| return flow_graph_builder_->ThrowException(position); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BooleanNegate() { | |
| return flow_graph_builder_->BooleanNegate(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::TranslateInstantiatedTypeArguments( | |
| const TypeArguments& type_arguments) { | |
| return flow_graph_builder_->TranslateInstantiatedTypeArguments( | |
| type_arguments); | |
| } | |
| Fragment StreamingFlowGraphBuilder::StrictCompare(TokenPosition position, | |
| Token::Kind kind, | |
| bool number_check) { | |
| return flow_graph_builder_->StrictCompare(position, kind, number_check); | |
| } | |
| Fragment StreamingFlowGraphBuilder::AllocateObject(TokenPosition position, | |
| const Class& klass, | |
| intptr_t argument_count) { | |
| return flow_graph_builder_->AllocateObject(position, klass, argument_count); | |
| } | |
| Fragment StreamingFlowGraphBuilder::AllocateContext( | |
| const ZoneGrowableArray<const Slot*>& context_slots) { | |
| return flow_graph_builder_->AllocateContext(context_slots); | |
| } | |
| Fragment StreamingFlowGraphBuilder::LoadNativeField( | |
| const Slot& field, | |
| InnerPointerAccess loads_inner_pointer) { | |
| return flow_graph_builder_->LoadNativeField(field, loads_inner_pointer); | |
| } | |
| Fragment StreamingFlowGraphBuilder::StoreLocal(TokenPosition position, | |
| LocalVariable* variable) { | |
| return flow_graph_builder_->StoreLocal(position, variable); | |
| } | |
| Fragment StreamingFlowGraphBuilder::StoreStaticField(TokenPosition position, | |
| const Field& field) { | |
| return flow_graph_builder_->StoreStaticField(position, field); | |
| } | |
| Fragment StreamingFlowGraphBuilder::StringInterpolate(TokenPosition position) { | |
| return flow_graph_builder_->StringInterpolate(position); | |
| } | |
| Fragment StreamingFlowGraphBuilder::StringInterpolateSingle( | |
| TokenPosition position) { | |
| return flow_graph_builder_->StringInterpolateSingle(position); | |
| } | |
| Fragment StreamingFlowGraphBuilder::LoadInstantiatorTypeArguments() { | |
| return flow_graph_builder_->LoadInstantiatorTypeArguments(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::LoadFunctionTypeArguments() { | |
| return flow_graph_builder_->LoadFunctionTypeArguments(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::InstantiateType(const AbstractType& type) { | |
| return flow_graph_builder_->InstantiateType(type); | |
| } | |
| Fragment StreamingFlowGraphBuilder::CreateArray() { | |
| return flow_graph_builder_->CreateArray(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::StoreIndexed(intptr_t class_id) { | |
| return flow_graph_builder_->StoreIndexed(class_id); | |
| } | |
| Fragment StreamingFlowGraphBuilder::CheckStackOverflow(TokenPosition position) { | |
| return flow_graph_builder_->CheckStackOverflow( | |
| position, flow_graph_builder_->GetStackDepth(), | |
| flow_graph_builder_->loop_depth_); | |
| } | |
| Fragment StreamingFlowGraphBuilder::CloneContext( | |
| const ZoneGrowableArray<const Slot*>& context_slots) { | |
| return flow_graph_builder_->CloneContext(context_slots); | |
| } | |
| Fragment StreamingFlowGraphBuilder::TranslateFinallyFinalizers( | |
| TryFinallyBlock* outer_finally, | |
| intptr_t target_context_depth) { | |
| // TranslateFinallyFinalizers can move the readers offset. | |
| // Save the current position and restore it afterwards. | |
| AlternativeReadingScope alt(&reader_); | |
| // Save context. | |
| TryFinallyBlock* const saved_finally_block = B->try_finally_block_; | |
| TryCatchBlock* const saved_try_catch_block = B->CurrentTryCatchBlock(); | |
| const intptr_t saved_context_depth = B->context_depth_; | |
| const ProgramState state(B->breakable_block_, B->switch_block_, | |
| B->loop_depth_, B->try_depth_, B->catch_depth_, | |
| B->block_expression_depth_); | |
| Fragment instructions; | |
| // While translating the body of a finalizer we need to set the try-finally | |
| // block which is active when translating the body. | |
| while (B->try_finally_block_ != outer_finally) { | |
| ASSERT(B->try_finally_block_ != nullptr); | |
| // Adjust program context to finalizer's position. | |
| B->try_finally_block_->state().assignTo(B); | |
| // Potentially restore the context to what is expected for the finally | |
| // block. | |
| instructions += B->AdjustContextTo(B->try_finally_block_->context_depth()); | |
| // The to-be-translated finalizer has to have the correct try-index (namely | |
| // the one outside the try-finally block). | |
| bool changed_try_index = false; | |
| intptr_t target_try_index = B->try_finally_block_->try_index(); | |
| while (B->CurrentTryIndex() != target_try_index) { | |
| B->SetCurrentTryCatchBlock(B->CurrentTryCatchBlock()->outer()); | |
| changed_try_index = true; | |
| } | |
| if (changed_try_index) { | |
| JoinEntryInstr* entry = BuildJoinEntry(); | |
| instructions += Goto(entry); | |
| instructions = Fragment(instructions.entry, entry); | |
| } | |
| intptr_t finalizer_kernel_offset = | |
| B->try_finally_block_->finalizer_kernel_offset(); | |
| B->try_finally_block_ = B->try_finally_block_->outer(); | |
| instructions += BuildStatementAtWithBranchCoverage(finalizer_kernel_offset); | |
| // We only need to make sure that if the finalizer ended normally, we | |
| // continue towards the next outer try-finally. | |
| if (!instructions.is_open()) break; | |
| } | |
| if (instructions.is_open() && target_context_depth != -1) { | |
| // A target context depth of -1 indicates that the code after this | |
| // will not care about the context chain so we can leave it any way we | |
| // want after the last finalizer. That is used when returning. | |
| instructions += B->AdjustContextTo(target_context_depth); | |
| } | |
| // Restore. | |
| B->try_finally_block_ = saved_finally_block; | |
| B->SetCurrentTryCatchBlock(saved_try_catch_block); | |
| B->context_depth_ = saved_context_depth; | |
| state.assignTo(B); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BranchIfTrue( | |
| TargetEntryInstr** then_entry, | |
| TargetEntryInstr** otherwise_entry, | |
| bool negate) { | |
| return flow_graph_builder_->BranchIfTrue(then_entry, otherwise_entry, negate); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BranchIfEqual( | |
| TargetEntryInstr** then_entry, | |
| TargetEntryInstr** otherwise_entry, | |
| bool negate) { | |
| return flow_graph_builder_->BranchIfEqual(then_entry, otherwise_entry, | |
| negate); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BranchIfNull( | |
| TargetEntryInstr** then_entry, | |
| TargetEntryInstr** otherwise_entry, | |
| bool negate) { | |
| return flow_graph_builder_->BranchIfNull(then_entry, otherwise_entry, negate); | |
| } | |
| Fragment StreamingFlowGraphBuilder::CatchBlockEntry(const Array& handler_types, | |
| intptr_t handler_index, | |
| bool needs_stacktrace, | |
| bool is_synthesized) { | |
| return B->CatchBlockEntry(handler_types, handler_index, needs_stacktrace, | |
| is_synthesized); | |
| } | |
| Fragment StreamingFlowGraphBuilder::TryEntry(int try_handler_index) { | |
| return B->TryEntry(try_handler_index); | |
| } | |
| Fragment StreamingFlowGraphBuilder::Drop() { | |
| return flow_graph_builder_->Drop(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::DropArguments(intptr_t argument_count, | |
| intptr_t type_args_count) { | |
| Fragment instructions; | |
| for (intptr_t i = 0; i < argument_count; i++) { | |
| instructions += Drop(); | |
| } | |
| if (type_args_count != 0) { | |
| instructions += Drop(); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::DropTempsPreserveTop( | |
| intptr_t num_temps_to_drop) { | |
| return flow_graph_builder_->DropTempsPreserveTop(num_temps_to_drop); | |
| } | |
| Fragment StreamingFlowGraphBuilder::MakeTemp() { | |
| return flow_graph_builder_->MakeTemp(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::NullConstant() { | |
| return flow_graph_builder_->NullConstant(); | |
| } | |
| JoinEntryInstr* StreamingFlowGraphBuilder::BuildJoinEntry() { | |
| return flow_graph_builder_->BuildJoinEntry(); | |
| } | |
| JoinEntryInstr* StreamingFlowGraphBuilder::BuildJoinEntry(intptr_t try_index) { | |
| return flow_graph_builder_->BuildJoinEntry(try_index); | |
| } | |
| Fragment StreamingFlowGraphBuilder::Goto(JoinEntryInstr* destination) { | |
| return flow_graph_builder_->Goto(destination); | |
| } | |
| Fragment StreamingFlowGraphBuilder::CheckArgumentType( | |
| LocalVariable* variable, | |
| const AbstractType& type) { | |
| return flow_graph_builder_->CheckAssignable( | |
| type, variable->name(), AssertAssignableInstr::kParameterCheck); | |
| } | |
| Fragment StreamingFlowGraphBuilder::RecordCoverage(TokenPosition position) { | |
| return flow_graph_builder_->RecordCoverage(position); | |
| } | |
| Fragment StreamingFlowGraphBuilder::EnterScope( | |
| intptr_t kernel_offset, | |
| const LocalScope** scope /* = nullptr */) { | |
| return flow_graph_builder_->EnterScope(kernel_offset, scope); | |
| } | |
| Fragment StreamingFlowGraphBuilder::ExitScope(intptr_t kernel_offset) { | |
| return flow_graph_builder_->ExitScope(kernel_offset); | |
| } | |
| TestFragment StreamingFlowGraphBuilder::TranslateConditionForControl() { | |
| // Skip all negations and go directly to the expression. | |
| bool negate = false; | |
| while (PeekTag() == kNot) { | |
| SkipBytes(1); | |
| ReadPosition(); | |
| negate = !negate; | |
| } | |
| TestFragment result; | |
| if (PeekTag() == kLogicalExpression) { | |
| // Handle '&&' and '||' operators specially to implement short circuit | |
| // evaluation. | |
| SkipBytes(1); // tag. | |
| ReadPosition(); | |
| TestFragment left = TranslateConditionForControl(); | |
| LogicalOperator op = static_cast<LogicalOperator>(ReadByte()); | |
| TestFragment right = TranslateConditionForControl(); | |
| result.entry = left.entry; | |
| if (op == kAnd) { | |
| left.CreateTrueSuccessor(flow_graph_builder_)->LinkTo(right.entry); | |
| result.true_successor_addresses = right.true_successor_addresses; | |
| result.false_successor_addresses = left.false_successor_addresses; | |
| result.false_successor_addresses->AddArray( | |
| *right.false_successor_addresses); | |
| } else { | |
| ASSERT(op == kOr); | |
| left.CreateFalseSuccessor(flow_graph_builder_)->LinkTo(right.entry); | |
| result.true_successor_addresses = left.true_successor_addresses; | |
| result.true_successor_addresses->AddArray( | |
| *right.true_successor_addresses); | |
| result.false_successor_addresses = right.false_successor_addresses; | |
| } | |
| } else { | |
| // Other expressions. | |
| TokenPosition position = TokenPosition::kNoSource; | |
| Fragment instructions = BuildExpression(&position); // read expression. | |
| // Check if the top of the stack is already a StrictCompare that | |
| // can be merged with a branch. Otherwise compare TOS with | |
| // true value and branch on that. | |
| BranchInstr* branch; | |
| if (stack()->definition()->IsStrictCompare() && | |
| stack()->definition() == instructions.current) { | |
| StrictCompareInstr* compare = Pop()->definition()->AsStrictCompare(); | |
| if (negate) { | |
| compare->NegateCondition(); | |
| negate = false; | |
| } | |
| branch = | |
| new (Z) BranchInstr(compare, flow_graph_builder_->GetNextDeoptId()); | |
| branch->condition()->ClearTempIndex(); | |
| ASSERT(instructions.current->previous() != nullptr); | |
| instructions.current = instructions.current->previous(); | |
| } else { | |
| if (NeedsDebugStepCheck(stack(), position)) { | |
| instructions = DebugStepCheck(position) + instructions; | |
| } | |
| instructions += Constant(Bool::True()); | |
| Value* right_value = Pop(); | |
| Value* left_value = Pop(); | |
| StrictCompareInstr* compare = new (Z) StrictCompareInstr( | |
| InstructionSource(), negate ? Token::kNE_STRICT : Token::kEQ_STRICT, | |
| left_value, right_value, false, | |
| flow_graph_builder_->GetNextDeoptId()); | |
| branch = | |
| new (Z) BranchInstr(compare, flow_graph_builder_->GetNextDeoptId()); | |
| negate = false; | |
| } | |
| instructions <<= branch; | |
| result = TestFragment(instructions.entry, branch); | |
| } | |
| return result.Negate(negate); | |
| } | |
| const TypeArguments& StreamingFlowGraphBuilder::BuildTypeArguments() { | |
| ReadUInt(); // read arguments count. | |
| intptr_t type_count = ReadListLength(); // read type count. | |
| return T.BuildTypeArguments(type_count); // read types. | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildArguments(Array* argument_names, | |
| intptr_t* argument_count, | |
| intptr_t* positional_count) { | |
| intptr_t dummy; | |
| if (argument_count == nullptr) argument_count = &dummy; | |
| *argument_count = ReadUInt(); // read arguments count. | |
| // List of types. | |
| SkipListOfDartTypes(); // read list of types. | |
| { | |
| AlternativeReadingScope _(&reader_); | |
| if (positional_count == nullptr) positional_count = &dummy; | |
| *positional_count = ReadListLength(); // read length of expression list | |
| } | |
| return BuildArgumentsFromActualArguments(argument_names); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildArgumentsFromActualArguments( | |
| Array* argument_names) { | |
| Fragment instructions; | |
| // List of positional. | |
| intptr_t list_length = ReadListLength(); // read list length. | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| instructions += BuildExpression(); // read ith expression. | |
| } | |
| // List of named. | |
| list_length = ReadListLength(); // read list length. | |
| if (argument_names != nullptr && list_length > 0) { | |
| *argument_names = Array::New(list_length, Heap::kOld); | |
| } | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| String& name = | |
| H.DartSymbolObfuscate(ReadStringReference()); // read ith name index. | |
| instructions += BuildExpression(); // read ith expression. | |
| if (argument_names != nullptr) { | |
| argument_names->SetAt(i, name); | |
| } | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildInvalidExpression( | |
| TokenPosition* position) { | |
| // The frontend will take care of emitting normal errors (like | |
| // [NoSuchMethodError]s) and only emit [InvalidExpression]s in very special | |
| // situations (e.g. an invalid annotation). | |
| TokenPosition pos = ReadPosition(); | |
| if (position != nullptr) *position = pos; | |
| const String& message = H.DartString(ReadStringReference()); | |
| Tag tag = ReadTag(); // read (first part of) expression. | |
| if (tag == kSomething) { | |
| SkipExpression(); // read (rest of) expression. | |
| } | |
| // Invalid expression message has pointer to the source code, no need to | |
| // report it twice. | |
| const auto& script = Script::Handle(Z, Script()); | |
| H.ReportError(script, TokenPosition::kNoSource, "%s", message.ToCString()); | |
| return Fragment(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildVariableGet(TokenPosition* position) { | |
| const TokenPosition pos = ReadPosition(); | |
| if (position != nullptr) *position = pos; | |
| intptr_t variable_kernel_position = ReadUInt(); // read kernel position. | |
| ReadUInt(); // read relative variable index. | |
| SkipOptionalDartType(); // read promoted type. | |
| return BuildVariableGetImpl(variable_kernel_position, pos); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildVariableGet(uint8_t payload, | |
| TokenPosition* position) { | |
| const TokenPosition pos = ReadPosition(); | |
| if (position != nullptr) *position = pos; | |
| intptr_t variable_kernel_position = ReadUInt(); // read kernel position. | |
| return BuildVariableGetImpl(variable_kernel_position, pos); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildVariableGetImpl( | |
| intptr_t variable_kernel_position, | |
| TokenPosition position) { | |
| LocalVariable* variable = LookupVariable(variable_kernel_position); | |
| if (!variable->is_late()) { | |
| return LoadLocal(variable); | |
| } | |
| // Late variable, so check whether it has been initialized already. | |
| Fragment instructions = LoadLocal(variable); | |
| TargetEntryInstr* is_uninitialized; | |
| TargetEntryInstr* is_initialized; | |
| instructions += Constant(Object::sentinel()); | |
| instructions += flow_graph_builder_->BranchIfStrictEqual(&is_uninitialized, | |
| &is_initialized); | |
| JoinEntryInstr* join = BuildJoinEntry(); | |
| { | |
| AlternativeReadingScope alt(&reader_, variable->late_init_offset()); | |
| const bool has_initializer = (ReadTag() != kNothing); | |
| if (has_initializer) { | |
| // If the variable isn't initialized, call the initializer and set it. | |
| Fragment initialize(is_uninitialized); | |
| initialize += BuildExpression(); | |
| if (variable->is_final()) { | |
| // Late final variable, so check whether it has been assigned | |
| // during initialization. | |
| initialize += LoadLocal(variable); | |
| TargetEntryInstr* is_uninitialized_after_init; | |
| TargetEntryInstr* is_initialized_after_init; | |
| initialize += Constant(Object::sentinel()); | |
| initialize += flow_graph_builder_->BranchIfStrictEqual( | |
| &is_uninitialized_after_init, &is_initialized_after_init); | |
| { | |
| // The variable is uninitialized, so store the initializer result. | |
| Fragment store_result(is_uninitialized_after_init); | |
| store_result += StoreLocal(position, variable); | |
| store_result += Drop(); | |
| store_result += Goto(join); | |
| } | |
| { | |
| // Already initialized, so throw a LateInitializationError. | |
| Fragment already_assigned(is_initialized_after_init); | |
| already_assigned += flow_graph_builder_->ThrowLateInitializationError( | |
| position, "_throwLocalAssignedDuringInitialization", | |
| variable->name()); | |
| ASSERT(already_assigned.is_closed()); | |
| } | |
| } else { | |
| // Late non-final variable. Store the initializer result. | |
| initialize += StoreLocal(position, variable); | |
| initialize += Drop(); | |
| initialize += Goto(join); | |
| } | |
| } else { | |
| // The variable has no initializer, so throw a late initialization error. | |
| Fragment initialize(is_uninitialized); | |
| initialize += flow_graph_builder_->ThrowLateInitializationError( | |
| position, "_throwLocalNotInitialized", variable->name()); | |
| ASSERT(initialize.is_closed()); | |
| } | |
| } | |
| { | |
| // Already initialized, so there's nothing to do. | |
| Fragment already_initialized(is_initialized); | |
| already_initialized += Goto(join); | |
| } | |
| Fragment done = Fragment(instructions.entry, join); | |
| done += LoadLocal(variable); | |
| return done; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildVariableSet(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| intptr_t variable_kernel_position = ReadUInt(); // read kernel position. | |
| ReadUInt(); // read relative variable index. | |
| return BuildVariableSetImpl(position, variable_kernel_position); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildVariableSet(uint8_t payload, | |
| TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| intptr_t variable_kernel_position = ReadUInt(); // read kernel position. | |
| return BuildVariableSetImpl(position, variable_kernel_position); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildVariableSetImpl( | |
| TokenPosition position, | |
| intptr_t variable_kernel_position) { | |
| Fragment instructions = BuildExpression(); // read expression. | |
| if (NeedsDebugStepCheck(stack(), position)) { | |
| instructions = DebugStepCheck(position) + instructions; | |
| } | |
| LocalVariable* variable = LookupVariable(variable_kernel_position); | |
| if (variable->is_late() && variable->is_final()) { | |
| // Late final variable, so check whether it has been initialized. | |
| LocalVariable* expr_temp = MakeTemporary(); | |
| instructions += LoadLocal(variable); | |
| TargetEntryInstr* is_uninitialized; | |
| TargetEntryInstr* is_initialized; | |
| instructions += Constant(Object::sentinel()); | |
| instructions += flow_graph_builder_->BranchIfStrictEqual(&is_uninitialized, | |
| &is_initialized); | |
| JoinEntryInstr* join = BuildJoinEntry(); | |
| { | |
| // The variable is uninitialized, so store the expression value. | |
| Fragment initialize(is_uninitialized); | |
| initialize += LoadLocal(expr_temp); | |
| initialize += StoreLocal(position, variable); | |
| initialize += Drop(); | |
| initialize += Goto(join); | |
| } | |
| { | |
| // Already initialized, so throw a LateInitializationError. | |
| Fragment already_initialized(is_initialized); | |
| already_initialized += flow_graph_builder_->ThrowLateInitializationError( | |
| position, "_throwLocalAlreadyInitialized", variable->name()); | |
| ASSERT(already_initialized.is_closed()); | |
| } | |
| instructions = Fragment(instructions.entry, join); | |
| } else { | |
| instructions += StoreLocal(position, variable); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildInstanceGet(TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| ReadByte(); // read kind. | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const DirectCallMetadata direct_call = | |
| direct_call_metadata_helper_.GetDirectTargetForPropertyGet(offset); | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| Fragment instructions = BuildExpression(); // read receiver. | |
| const String& getter_name = ReadNameAsGetterName(); // read name. | |
| SkipDartType(); // read result_type. | |
| const NameIndex itarget_name = | |
| ReadInterfaceMemberNameReference(); // read interface_target_reference. | |
| ASSERT(!H.IsRoot(itarget_name) && H.IsGetter(itarget_name)); | |
| const auto& interface_target = Function::ZoneHandle( | |
| Z, H.LookupMethodByMember(itarget_name, H.DartGetterName(itarget_name))); | |
| ASSERT(getter_name.ptr() == interface_target.name()); | |
| if (direct_call.check_receiver_for_null_) { | |
| auto receiver = MakeTemporary(); | |
| instructions += CheckNull(position, receiver, getter_name); | |
| } | |
| if (!direct_call.target_.IsNull()) { | |
| ASSERT(CompilerState::Current().is_aot()); | |
| instructions += | |
| StaticCall(position, direct_call.target_, 1, Array::null_array(), | |
| ICData::kNoRebind, &result_type); | |
| } else { | |
| const intptr_t kTypeArgsLen = 0; | |
| const intptr_t kNumArgsChecked = 1; | |
| instructions += | |
| InstanceCall(position, getter_name, Token::kGET, kTypeArgsLen, 1, | |
| Array::null_array(), kNumArgsChecked, interface_target, | |
| Function::null_function(), &result_type); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildDynamicGet(TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| ReadByte(); // read kind. | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const DirectCallMetadata direct_call = | |
| direct_call_metadata_helper_.GetDirectTargetForPropertyGet(offset); | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| Fragment instructions = BuildExpression(); // read receiver. | |
| const String& getter_name = ReadNameAsGetterName(); // read name. | |
| const auto& mangled_name = String::ZoneHandle( | |
| Z, Function::CreateDynamicInvocationForwarderName(getter_name)); | |
| const Function* direct_call_target = &direct_call.target_; | |
| if (!direct_call_target->IsNull()) { | |
| direct_call_target = &Function::ZoneHandle( | |
| direct_call.target_.GetDynamicInvocationForwarder(mangled_name)); | |
| } | |
| if (direct_call.check_receiver_for_null_) { | |
| auto receiver = MakeTemporary(); | |
| instructions += CheckNull(position, receiver, getter_name); | |
| } | |
| if (!direct_call_target->IsNull()) { | |
| ASSERT(CompilerState::Current().is_aot()); | |
| instructions += | |
| StaticCall(position, *direct_call_target, 1, Array::null_array(), | |
| ICData::kNoRebind, &result_type); | |
| } else { | |
| const intptr_t kTypeArgsLen = 0; | |
| const intptr_t kNumArgsChecked = 1; | |
| instructions += InstanceCall(position, mangled_name, Token::kGET, | |
| kTypeArgsLen, 1, Array::null_array(), | |
| kNumArgsChecked, Function::null_function(), | |
| Function::null_function(), &result_type); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildInstanceTearOff(TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| ReadByte(); // read kind. | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const DirectCallMetadata direct_call = | |
| direct_call_metadata_helper_.GetDirectTargetForPropertyGet(offset); | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| Fragment instructions = BuildExpression(); // read receiver. | |
| const String& getter_name = ReadNameAsGetterName(); // read name. | |
| SkipDartType(); // read result_type. | |
| const NameIndex itarget_name = | |
| ReadInterfaceMemberNameReference(); // read interface_target_reference. | |
| ASSERT(!H.IsRoot(itarget_name) && H.IsMethod(itarget_name)); | |
| const auto& tearoff_interface_target = Function::ZoneHandle( | |
| Z, H.LookupMethodByMember(itarget_name, H.DartMethodName(itarget_name))); | |
| if (direct_call.check_receiver_for_null_) { | |
| const auto receiver = MakeTemporary(); | |
| instructions += CheckNull(position, receiver, getter_name); | |
| } | |
| if (!direct_call.target_.IsNull()) { | |
| ASSERT(CompilerState::Current().is_aot()); | |
| instructions += | |
| StaticCall(position, direct_call.target_, 1, Array::null_array(), | |
| ICData::kNoRebind, &result_type); | |
| } else { | |
| const intptr_t kTypeArgsLen = 0; | |
| const intptr_t kNumArgsChecked = 1; | |
| instructions += InstanceCall(position, getter_name, Token::kGET, | |
| kTypeArgsLen, 1, Array::null_array(), | |
| kNumArgsChecked, Function::null_function(), | |
| tearoff_interface_target, &result_type); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildInstanceSet(TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| ReadByte(); // read kind. | |
| const DirectCallMetadata direct_call = | |
| direct_call_metadata_helper_.GetDirectTargetForPropertySet(offset); | |
| const CallSiteAttributesMetadata call_site_attributes = | |
| call_site_attributes_metadata_helper_.GetCallSiteAttributes(offset); | |
| const InferredTypeMetadata inferred_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| // True if callee can skip argument type checks. | |
| bool is_unchecked_call = inferred_type.IsSkipCheck(); | |
| if (call_site_attributes.receiver_type != nullptr && | |
| call_site_attributes.receiver_type->HasTypeClass() && | |
| !Class::Handle(call_site_attributes.receiver_type->type_class()) | |
| .IsGeneric()) { | |
| is_unchecked_call = true; | |
| } | |
| Fragment instructions(MakeTemp()); | |
| LocalVariable* variable = MakeTemporary(); | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const bool is_call_on_this = PeekTag() == kThisExpression; | |
| if (is_call_on_this) { | |
| is_unchecked_call = true; | |
| } | |
| instructions += BuildExpression(); // read receiver. | |
| LocalVariable* receiver = nullptr; | |
| if (direct_call.check_receiver_for_null_) { | |
| receiver = MakeTemporary(); | |
| } | |
| const String& setter_name = ReadNameAsSetterName(); // read name. | |
| instructions += BuildExpression(); // read value. | |
| instructions += StoreLocal(TokenPosition::kNoSource, variable); | |
| const NameIndex itarget_name = | |
| ReadInterfaceMemberNameReference(); // read interface_target_reference. | |
| ASSERT(!H.IsRoot(itarget_name)); | |
| const auto& interface_target = Function::ZoneHandle( | |
| Z, H.LookupMethodByMember(itarget_name, H.DartSetterName(itarget_name))); | |
| ASSERT(setter_name.ptr() == interface_target.name()); | |
| if (direct_call.check_receiver_for_null_) { | |
| instructions += CheckNull(position, receiver, setter_name); | |
| } | |
| if (!direct_call.target_.IsNull()) { | |
| ASSERT(CompilerState::Current().is_aot()); | |
| instructions += | |
| StaticCall(position, direct_call.target_, 2, Array::null_array(), | |
| ICData::kNoRebind, /*result_type=*/nullptr, | |
| /*type_args_len=*/0, | |
| /*use_unchecked_entry=*/is_unchecked_call); | |
| } else { | |
| const intptr_t kTypeArgsLen = 0; | |
| const intptr_t kNumArgsChecked = 1; | |
| instructions += InstanceCall( | |
| position, setter_name, Token::kSET, kTypeArgsLen, 2, | |
| Array::null_array(), kNumArgsChecked, interface_target, | |
| Function::null_function(), | |
| /*result_type=*/nullptr, | |
| /*use_unchecked_entry=*/is_unchecked_call, &call_site_attributes, | |
| /*receiver_is_not_smi=*/false, is_call_on_this); | |
| } | |
| instructions += Drop(); // Drop result of the setter invocation. | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildDynamicSet(TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| ReadByte(); // read kind. | |
| const DirectCallMetadata direct_call = | |
| direct_call_metadata_helper_.GetDirectTargetForPropertySet(offset); | |
| const InferredTypeMetadata inferred_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| // True if callee can skip argument type checks. | |
| const bool is_unchecked_call = inferred_type.IsSkipCheck(); | |
| Fragment instructions(MakeTemp()); | |
| LocalVariable* variable = MakeTemporary(); | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| instructions += BuildExpression(); // read receiver. | |
| LocalVariable* receiver = nullptr; | |
| if (direct_call.check_receiver_for_null_) { | |
| receiver = MakeTemporary(); | |
| } | |
| const String& setter_name = ReadNameAsSetterName(); // read name. | |
| instructions += BuildExpression(); // read value. | |
| instructions += StoreLocal(TokenPosition::kNoSource, variable); | |
| if (direct_call.check_receiver_for_null_) { | |
| instructions += CheckNull(position, receiver, setter_name); | |
| } | |
| const Function* direct_call_target = &direct_call.target_; | |
| const auto& mangled_name = String::ZoneHandle( | |
| Z, Function::CreateDynamicInvocationForwarderName(setter_name)); | |
| if (!direct_call_target->IsNull()) { | |
| direct_call_target = &Function::ZoneHandle( | |
| direct_call.target_.GetDynamicInvocationForwarder(mangled_name)); | |
| } | |
| if (!direct_call_target->IsNull()) { | |
| ASSERT(CompilerState::Current().is_aot()); | |
| instructions += | |
| StaticCall(position, *direct_call_target, 2, Array::null_array(), | |
| ICData::kNoRebind, /*result_type=*/nullptr, | |
| /*type_args_len=*/0, | |
| /*use_unchecked_entry=*/is_unchecked_call); | |
| } else { | |
| const intptr_t kTypeArgsLen = 0; | |
| const intptr_t kNumArgsChecked = 1; | |
| instructions += InstanceCall( | |
| position, mangled_name, Token::kSET, kTypeArgsLen, 2, | |
| Array::null_array(), kNumArgsChecked, Function::null_function(), | |
| Function::null_function(), | |
| /*result_type=*/nullptr, | |
| /*use_unchecked_entry=*/is_unchecked_call, /*call_site_attrs=*/nullptr); | |
| } | |
| instructions += Drop(); // Drop result of the setter invocation. | |
| return instructions; | |
| } | |
| static Function& GetNoSuchMethodOrDie(Thread* thread, | |
| Zone* zone, | |
| const Class& klass) { | |
| Function& nsm_function = Function::Handle(zone); | |
| Class& iterate_klass = Class::Handle(zone, klass.ptr()); | |
| if (!iterate_klass.IsNull() && | |
| iterate_klass.EnsureIsFinalized(thread) == Error::null()) { | |
| while (!iterate_klass.IsNull()) { | |
| nsm_function = Resolver::ResolveDynamicFunction(zone, iterate_klass, | |
| Symbols::NoSuchMethod()); | |
| if (!nsm_function.IsNull() && nsm_function.NumParameters() == 2 && | |
| nsm_function.NumTypeParameters() == 0) { | |
| break; | |
| } | |
| iterate_klass = iterate_klass.SuperClass(); | |
| } | |
| } | |
| // We are guaranteed to find noSuchMethod of class Object. | |
| ASSERT(!nsm_function.IsNull()); | |
| return nsm_function; | |
| } | |
| // Note, that this will always mark `super` flag to true. | |
| Fragment StreamingFlowGraphBuilder::BuildAllocateInvocationMirrorCall( | |
| TokenPosition position, | |
| const String& name, | |
| intptr_t num_type_arguments, | |
| intptr_t num_arguments, | |
| const Array& argument_names, | |
| LocalVariable* actuals_array, | |
| Fragment build_rest_of_actuals) { | |
| Fragment instructions; | |
| // Populate array containing the actual arguments. Just add [this] here. | |
| instructions += LoadLocal(actuals_array); // array | |
| instructions += IntConstant(num_type_arguments == 0 ? 0 : 1); // index | |
| instructions += LoadLocal(parsed_function()->receiver_var()); // receiver | |
| instructions += StoreIndexed(kArrayCid); | |
| instructions += build_rest_of_actuals; | |
| // First argument is receiver. | |
| instructions += LoadLocal(parsed_function()->receiver_var()); | |
| // Push the arguments for allocating the invocation mirror: | |
| // - the name. | |
| instructions += Constant(String::ZoneHandle(Z, name.ptr())); | |
| // - the arguments descriptor. | |
| const Array& args_descriptor = | |
| Array::Handle(Z, ArgumentsDescriptor::NewBoxed( | |
| num_type_arguments, num_arguments, argument_names)); | |
| instructions += Constant(Array::ZoneHandle(Z, args_descriptor.ptr())); | |
| // - an array containing the actual arguments. | |
| instructions += LoadLocal(actuals_array); | |
| // - [true] indicating this is a `super` NoSuchMethod. | |
| instructions += Constant(Bool::True()); | |
| const Class& mirror_class = | |
| Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror())); | |
| ASSERT(!mirror_class.IsNull()); | |
| const auto& error = mirror_class.EnsureIsFinalized(thread()); | |
| ASSERT(error == Error::null()); | |
| const Function& allocation_function = Function::ZoneHandle( | |
| Z, mirror_class.LookupStaticFunction( | |
| Library::PrivateCoreLibName(Symbols::AllocateInvocationMirror()))); | |
| ASSERT(!allocation_function.IsNull()); | |
| instructions += StaticCall(position, allocation_function, | |
| /* argument_count = */ 4, ICData::kStatic); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildSuperPropertyGet(TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| Class& klass = GetSuperOrDie(); | |
| const String* getter_name = nullptr; | |
| const String* method_name = nullptr; | |
| { | |
| AlternativeReadingScope alt(&reader_); | |
| SkipExpression(); // skip receiver | |
| StringIndex name_index = ReadStringReference(); // read name index. | |
| NameIndex library_reference = | |
| ((H.StringSize(name_index) >= 1) && H.CharacterAt(name_index, 0) == '_') | |
| ? ReadCanonicalNameReference() // read library index. | |
| : NameIndex(); | |
| getter_name = &H.DartGetterName(library_reference, name_index); | |
| method_name = &H.DartMethodName(library_reference, name_index); | |
| } | |
| // Search the superclass chain for the selector looking for either getter or | |
| // method. | |
| Function& function = Function::Handle(Z); | |
| if (!klass.IsNull() && klass.EnsureIsFinalized(thread()) == Error::null()) { | |
| while (!klass.IsNull()) { | |
| function = Resolver::ResolveDynamicFunction(Z, klass, *method_name); | |
| if (!function.IsNull()) { | |
| SkipExpression(); // skip receiver | |
| SkipName(); // skip name | |
| SkipInterfaceMemberNameReference(); // skip target_reference. | |
| Function& target = | |
| Function::ZoneHandle(Z, function.ImplicitClosureFunction()); | |
| ASSERT(!target.IsNull()); | |
| // Generate inline code for allocation closure object | |
| // which captures `this`. | |
| return B->BuildImplicitClosureCreation(position, target); | |
| } | |
| function = Resolver::ResolveDynamicFunction(Z, klass, *getter_name); | |
| if (!function.IsNull()) break; | |
| klass = klass.SuperClass(); | |
| } | |
| } | |
| Fragment instructions; | |
| if (klass.IsNull()) { | |
| SkipExpression(); // skip receiver | |
| SkipName(); // skip name | |
| SkipInterfaceMemberNameReference(); // skip target_reference. | |
| instructions += | |
| Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null())); | |
| instructions += IntConstant(1); // array size | |
| instructions += CreateArray(); | |
| LocalVariable* actuals_array = MakeTemporary(); | |
| Class& parent_klass = GetSuperOrDie(); | |
| instructions += BuildAllocateInvocationMirrorCall( | |
| position, *getter_name, | |
| /* num_type_arguments = */ 0, | |
| /* num_arguments = */ 1, | |
| /* argument_names = */ Object::empty_array(), actuals_array, | |
| /* build_rest_of_actuals = */ Fragment()); | |
| Function& nsm_function = GetNoSuchMethodOrDie(thread(), Z, parent_klass); | |
| instructions += | |
| StaticCall(position, Function::ZoneHandle(Z, nsm_function.ptr()), | |
| /* argument_count = */ 2, ICData::kNSMDispatch); | |
| instructions += DropTempsPreserveTop(1); // Drop array | |
| } else { | |
| ASSERT(!klass.IsNull()); | |
| ASSERT(!function.IsNull()); | |
| instructions += BuildExpression(); // read receiver. | |
| SkipName(); // skip name | |
| SkipInterfaceMemberNameReference(); // skip target_reference. | |
| instructions += | |
| StaticCall(position, Function::ZoneHandle(Z, function.ptr()), | |
| /* argument_count = */ 1, Array::null_array(), | |
| ICData::kSuper, &result_type); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildSuperPropertySet(TokenPosition* p) { | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| Class& klass = GetSuperOrDie(); | |
| const String* setter_name = nullptr; | |
| { | |
| AlternativeReadingScope alt(&reader_); | |
| SkipExpression(); // skip receiver | |
| setter_name = &ReadNameAsSetterName(); // read name | |
| } | |
| Function& function = Function::Handle(Z); | |
| if (klass.EnsureIsFinalized(thread()) == Error::null()) { | |
| function = Resolver::ResolveDynamicFunction(Z, klass, *setter_name); | |
| } | |
| Fragment instructions(MakeTemp()); | |
| LocalVariable* value = MakeTemporary(); // this holds RHS value | |
| if (function.IsNull()) { | |
| SkipExpression(); // skip receiver | |
| SkipName(); // skip name | |
| instructions += | |
| Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null())); | |
| instructions += IntConstant(2); // array size | |
| instructions += CreateArray(); | |
| LocalVariable* actuals_array = MakeTemporary(); | |
| Fragment build_rest_of_actuals; | |
| build_rest_of_actuals += LoadLocal(actuals_array); // array | |
| build_rest_of_actuals += IntConstant(1); // index | |
| build_rest_of_actuals += BuildExpression(); // value. | |
| build_rest_of_actuals += StoreLocal(position, value); | |
| build_rest_of_actuals += StoreIndexed(kArrayCid); | |
| instructions += BuildAllocateInvocationMirrorCall( | |
| position, *setter_name, /* num_type_arguments = */ 0, | |
| /* num_arguments = */ 2, | |
| /* argument_names = */ Object::empty_array(), actuals_array, | |
| build_rest_of_actuals); | |
| SkipInterfaceMemberNameReference(); // skip target_reference. | |
| Function& nsm_function = GetNoSuchMethodOrDie(thread(), Z, klass); | |
| instructions += | |
| StaticCall(position, Function::ZoneHandle(Z, nsm_function.ptr()), | |
| /* argument_count = */ 2, ICData::kNSMDispatch); | |
| instructions += Drop(); // Drop result of NoSuchMethod invocation | |
| instructions += Drop(); // Drop array | |
| } else { | |
| instructions += BuildExpression(); // read receiver | |
| SkipName(); // skip name | |
| instructions += BuildExpression(); // read value. | |
| instructions += StoreLocal(position, value); | |
| SkipInterfaceMemberNameReference(); // skip target_reference. | |
| instructions += StaticCall( | |
| position, Function::ZoneHandle(Z, function.ptr()), | |
| /* argument_count = */ 2, Array::null_array(), ICData::kSuper, | |
| /*result_type=*/nullptr, /*type_args_len=*/0, | |
| /*use_unchecked_entry=*/true); | |
| instructions += Drop(); // Drop result of the setter invocation. | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildStaticGet(TokenPosition* p) { | |
| ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull()); | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| NameIndex target = ReadCanonicalNameReference(); // read target_reference. | |
| ASSERT(H.IsGetter(target)); | |
| const Field& field = Field::ZoneHandle( | |
| Z, H.LookupFieldByKernelGetterOrSetter(target, /*required=*/false)); | |
| if (!field.IsNull()) { | |
| if (field.is_const()) { | |
| // Since the CFE inlines all references to const variables and fields, | |
| // it never emits a StaticGet of a const field. | |
| // This situation only arises because of the static const fields in | |
| // the ClassID class, which are generated internally in the VM | |
| // during loading. See also Class::InjectCIDFields. | |
| ASSERT(Class::Handle(field.Owner()).library() == | |
| Library::InternalLibrary() && | |
| Class::Handle(field.Owner()).Name() == Symbols::ClassID().ptr()); | |
| return Constant(Instance::ZoneHandle( | |
| Z, Instance::RawCast(field.StaticConstFieldValue()))); | |
| } else if (field.is_final() && field.has_trivial_initializer()) { | |
| // Final fields with trivial initializers are effectively constant. | |
| return Constant(Instance::ZoneHandle( | |
| Z, Instance::RawCast(field.StaticConstFieldValue()))); | |
| } else { | |
| const Class& owner = Class::Handle(Z, field.Owner()); | |
| const String& getter_name = H.DartGetterName(target); | |
| const Function& getter = | |
| Function::ZoneHandle(Z, owner.LookupStaticFunction(getter_name)); | |
| if (!getter.IsNull() && field.NeedsGetter()) { | |
| return StaticCall(position, getter, 0, Array::null_array(), | |
| ICData::kStatic, &result_type); | |
| } else { | |
| if (result_type.IsConstant()) { | |
| return Constant(result_type.constant_value); | |
| } | |
| return LoadStaticField(field, /*calls_initializer=*/false); | |
| } | |
| } | |
| } | |
| const Function& function = Function::ZoneHandle( | |
| Z, H.LookupStaticMethodByKernelProcedure(target, /*required=*/false)); | |
| if (!function.IsNull()) { | |
| if (H.IsGetter(target)) { | |
| return StaticCall(position, function, 0, Array::null_array(), | |
| ICData::kStatic, &result_type); | |
| } else if (H.IsMethod(target)) { | |
| const auto& closure_function = | |
| Function::Handle(Z, function.ImplicitClosureFunction()); | |
| const auto& static_closure = | |
| Instance::Handle(Z, closure_function.ImplicitStaticClosure()); | |
| return Constant(Instance::ZoneHandle(Z, H.Canonicalize(static_closure))); | |
| } else { | |
| UNIMPLEMENTED(); | |
| } | |
| } | |
| return StaticCallMissing( | |
| position, H.DartSymbolPlain(H.CanonicalNameString(target)), | |
| /* argument_count */ 0, | |
| H.IsLibrary(H.EnclosingName(target)) ? InvocationMirror::Level::kTopLevel | |
| : InvocationMirror::Level::kStatic, | |
| InvocationMirror::Kind::kGetter); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildStaticSet(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| NameIndex target = ReadCanonicalNameReference(); // read target_reference. | |
| ASSERT(H.IsSetter(target)); | |
| // Evaluate the expression on the right hand side. | |
| Fragment instructions = BuildExpression(); // read expression. | |
| // Look up the target as a setter first and, if not present, as a field | |
| // second. This order is needed to avoid looking up a final field as the | |
| // target. | |
| const Function& function = Function::ZoneHandle( | |
| Z, H.LookupStaticMethodByKernelProcedure(target, /*required=*/false)); | |
| if (!function.IsNull()) { | |
| LocalVariable* variable = MakeTemporary(); | |
| // Prepare argument. | |
| instructions += LoadLocal(variable); | |
| // Invoke the setter function. | |
| instructions += StaticCall(position, function, 1, ICData::kStatic); | |
| // Drop the unused result & leave the stored value on the stack. | |
| return instructions + Drop(); | |
| } | |
| const Field& field = Field::ZoneHandle( | |
| Z, H.LookupFieldByKernelGetterOrSetter(target, /*required=*/false)); | |
| if (!field.IsNull()) { | |
| if (NeedsDebugStepCheck(stack(), position)) { | |
| instructions = DebugStepCheck(position) + instructions; | |
| } | |
| LocalVariable* variable = MakeTemporary(); | |
| instructions += LoadLocal(variable); | |
| instructions += StoreStaticField(position, field); | |
| return instructions; | |
| } | |
| instructions += StaticCallMissing( | |
| position, H.DartSymbolPlain(H.CanonicalNameString(target)), | |
| /* argument_count */ 1, | |
| H.IsLibrary(H.EnclosingName(target)) ? InvocationMirror::Level::kTopLevel | |
| : InvocationMirror::Level::kStatic, | |
| InvocationMirror::Kind::kSetter); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildMethodInvocation(TokenPosition* p, | |
| bool is_dynamic) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| ReadByte(); // read kind. | |
| // read flags. | |
| const uint8_t flags = ReadFlags(); | |
| bool is_invariant = false; | |
| bool is_implicit_call = false; | |
| if (is_dynamic) { | |
| is_implicit_call = (flags & kDynamicInvocationFlagImplicitCall) != 0; | |
| } else { | |
| is_invariant = (flags & kInstanceInvocationFlagInvariant) != 0; | |
| } | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const DirectCallMetadata direct_call = | |
| direct_call_metadata_helper_.GetDirectTargetForMethodInvocation(offset); | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| const CallSiteAttributesMetadata call_site_attributes = | |
| call_site_attributes_metadata_helper_.GetCallSiteAttributes(offset); | |
| const Tag receiver_tag = PeekTag(); // peek tag for receiver. | |
| bool is_unchecked_call = is_invariant || result_type.IsSkipCheck(); | |
| if (!is_dynamic && (call_site_attributes.receiver_type != nullptr) && | |
| call_site_attributes.receiver_type->HasTypeClass() && | |
| !call_site_attributes.receiver_type->IsDynamicType() && | |
| !Class::Handle(call_site_attributes.receiver_type->type_class()) | |
| .IsGeneric()) { | |
| is_unchecked_call = true; | |
| } | |
| Fragment instructions; | |
| intptr_t type_args_len = 0; | |
| { | |
| AlternativeReadingScope alt(&reader_); | |
| SkipExpression(); // skip receiver | |
| SkipName(); // skip method name | |
| ReadUInt(); // read argument count. | |
| intptr_t list_length = ReadListLength(); // read types list length. | |
| if (list_length > 0) { | |
| const TypeArguments& type_arguments = | |
| T.BuildTypeArguments(list_length); // read types. | |
| instructions += TranslateInstantiatedTypeArguments(type_arguments); | |
| } | |
| type_args_len = list_length; | |
| } | |
| // Take note of whether the invocation is against the receiver of the current | |
| // function: in this case, we may skip some type checks in the callee. | |
| const bool is_call_on_this = (PeekTag() == kThisExpression) && !is_dynamic; | |
| if (is_call_on_this) { | |
| is_unchecked_call = true; | |
| } | |
| instructions += BuildExpression(); // read receiver. | |
| const String& name = ReadNameAsMethodName(); // read name. | |
| const Token::Kind token_kind = | |
| MethodTokenRecognizer::RecognizeTokenKind(name); | |
| // Detect comparison with null. | |
| if ((token_kind == Token::kEQ || token_kind == Token::kNE) && | |
| PeekArgumentsCount() == 1 && | |
| (receiver_tag == kNullLiteral || | |
| PeekArgumentsFirstPositionalTag() == kNullLiteral)) { | |
| ASSERT(type_args_len == 0); | |
| // "==" or "!=" with null on either side. | |
| instructions += | |
| BuildArguments(nullptr /* named */, nullptr /* arg count */, | |
| nullptr /* positional arg count */); // read arguments. | |
| SkipInterfaceMemberNameReference(); // read interface_target_reference. | |
| Token::Kind strict_cmp_kind = | |
| token_kind == Token::kEQ ? Token::kEQ_STRICT : Token::kNE_STRICT; | |
| return instructions + | |
| StrictCompare(position, strict_cmp_kind, /*number_check = */ true); | |
| } | |
| LocalVariable* receiver_temp = nullptr; | |
| if (direct_call.check_receiver_for_null_) { | |
| receiver_temp = MakeTemporary(); | |
| } | |
| intptr_t argument_count; | |
| intptr_t positional_argument_count; | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| instructions += | |
| BuildArguments(&argument_names, &argument_count, | |
| &positional_argument_count); // read arguments. | |
| ++argument_count; // include receiver | |
| intptr_t checked_argument_count = 1; | |
| // If we have a special operation (e.g. +/-/==) we mark both arguments as | |
| // to be checked. | |
| if (token_kind != Token::kILLEGAL) { | |
| ASSERT(argument_count <= 2); | |
| checked_argument_count = argument_count; | |
| } | |
| if (!is_dynamic) { | |
| SkipDartType(); // read function_type. | |
| SkipDartType(); // read result_type. | |
| } | |
| const Function* interface_target = &Function::null_function(); | |
| // read interface_target_reference. | |
| const NameIndex itarget_name = | |
| is_dynamic ? NameIndex() : ReadInterfaceMemberNameReference(); | |
| // TODO(dartbug.com/34497): Once front-end desugars calls via | |
| // fields/getters, filtering of field and getter interface targets here | |
| // can be turned into assertions. | |
| if (!H.IsRoot(itarget_name) && !H.IsGetter(itarget_name)) { | |
| interface_target = &Function::ZoneHandle( | |
| Z, H.LookupMethodByMember(itarget_name, | |
| H.DartProcedureName(itarget_name))); | |
| ASSERT(name.ptr() == interface_target->name()); | |
| ASSERT(!interface_target->IsGetterFunction()); | |
| } | |
| if (direct_call.check_receiver_for_null_) { | |
| instructions += CheckNull(position, receiver_temp, name); | |
| } | |
| const String* mangled_name = &name; | |
| // Do not mangle ==: | |
| // * operator == takes an Object so its either not checked or checked | |
| // at the entry because the parameter is marked covariant, neither of | |
| // those cases require a dynamic invocation forwarder. | |
| const Function* direct_call_target = &direct_call.target_; | |
| if (is_dynamic && (name.ptr() != Symbols::EqualOperator().ptr())) { | |
| mangled_name = &String::ZoneHandle( | |
| Z, Function::CreateDynamicInvocationForwarderName(name)); | |
| if (!direct_call_target->IsNull()) { | |
| direct_call_target = &Function::ZoneHandle( | |
| direct_call_target->GetDynamicInvocationForwarder(*mangled_name)); | |
| } | |
| if (is_implicit_call) { | |
| ASSERT(mangled_name->ptr() == Symbols::DynamicCall().ptr()); | |
| mangled_name = &Symbols::DynamicImplicitCall(); | |
| } | |
| } | |
| if (!direct_call_target->IsNull()) { | |
| // Even if TFA infers a concrete receiver type, the static type of the | |
| // call-site may still be dynamic and we need to call the dynamic invocation | |
| // forwarder to ensure type-checks are performed. | |
| ASSERT(CompilerState::Current().is_aot()); | |
| instructions += | |
| StaticCall(position, *direct_call_target, argument_count, | |
| argument_names, ICData::kNoRebind, &result_type, | |
| type_args_len, /*use_unchecked_entry=*/is_unchecked_call); | |
| } else { | |
| instructions += InstanceCall( | |
| position, *mangled_name, token_kind, type_args_len, argument_count, | |
| argument_names, checked_argument_count, *interface_target, | |
| Function::null_function(), &result_type, | |
| /*use_unchecked_entry=*/is_unchecked_call, &call_site_attributes, | |
| result_type.ReceiverNotInt(), is_call_on_this); | |
| } | |
| // 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()) { | |
| instructions += Drop(); | |
| instructions += NullConstant(); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildLocalFunctionInvocation( | |
| TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| const TokenPosition position = ReadPosition(); | |
| if (p != nullptr) *p = position; | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| // read variable kernel position. | |
| const intptr_t variable_kernel_position = ReadUInt(); | |
| ReadUInt(); // read relative variable index. | |
| LocalVariable* variable = LookupVariable(variable_kernel_position); | |
| ASSERT(!variable->is_late()); | |
| auto& target_function = Function::ZoneHandle(Z); | |
| { | |
| AlternativeReadingScope alt( | |
| &reader_, variable_kernel_position - data_program_offset_); | |
| SkipVariable(); | |
| const intptr_t local_function_id = ReadUInt(); // read id. | |
| ASSERT(local_function_id > 0); | |
| target_function = ClosureFunctionsCache::LookupClosureFunction( | |
| Function::Handle(Z, | |
| parsed_function()->function().GetOutermostFunction()), | |
| local_function_id); | |
| RELEASE_ASSERT(!target_function.IsNull()); | |
| } | |
| Fragment instructions; | |
| // Type arguments. | |
| intptr_t type_args_len = 0; | |
| { | |
| AlternativeReadingScope alt(&reader_); | |
| ReadUInt(); // read argument count. | |
| intptr_t list_length = ReadListLength(); // read types list length. | |
| if (list_length > 0) { | |
| const TypeArguments& type_arguments = | |
| T.BuildTypeArguments(list_length); // read types. | |
| instructions += TranslateInstantiatedTypeArguments(type_arguments); | |
| } | |
| type_args_len = list_length; | |
| } | |
| // Receiver (closure). | |
| instructions += LoadLocal(variable); | |
| intptr_t argument_count; | |
| intptr_t positional_argument_count; | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| instructions += | |
| BuildArguments(&argument_names, &argument_count, | |
| &positional_argument_count); // read arguments. | |
| ++argument_count; // include receiver | |
| SkipDartType(); // read function_type. | |
| // Lookup the function in the closure. | |
| instructions += LoadLocal(variable); | |
| if (!FLAG_precompiled_mode) { | |
| instructions += LoadNativeField(Slot::Closure_function()); | |
| } | |
| if (parsed_function()->function().is_debuggable()) { | |
| ASSERT(!parsed_function()->function().is_native()); | |
| instructions += DebugStepCheck(position); | |
| } | |
| instructions += B->ClosureCall(target_function, position, type_args_len, | |
| argument_count, argument_names, &result_type); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildFunctionInvocation(TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| const FunctionAccessKind function_access_kind = | |
| static_cast<FunctionAccessKind>(ReadByte()); // read kind. | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const DirectCallMetadata direct_call = | |
| direct_call_metadata_helper_.GetDirectTargetForFunctionInvocation(offset); | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| RELEASE_ASSERT((function_access_kind == FunctionAccessKind::kFunction) || | |
| (function_access_kind == FunctionAccessKind::kFunctionType)); | |
| const bool is_unchecked_closure_call = | |
| (function_access_kind == FunctionAccessKind::kFunctionType); | |
| Fragment instructions; | |
| instructions += BuildExpression(); // read receiver. | |
| LocalVariable* receiver_temp = MakeTemporary(); | |
| // Type arguments. | |
| intptr_t type_args_len = 0; | |
| { | |
| AlternativeReadingScope alt(&reader_); | |
| ReadUInt(); // read argument count. | |
| intptr_t list_length = ReadListLength(); // read types list length. | |
| if (list_length > 0) { | |
| const TypeArguments& type_arguments = | |
| T.BuildTypeArguments(list_length); // read types. | |
| instructions += TranslateInstantiatedTypeArguments(type_arguments); | |
| } | |
| type_args_len = list_length; | |
| } | |
| // Receiver (closure). | |
| instructions += LoadLocal(receiver_temp); | |
| intptr_t argument_count; | |
| intptr_t positional_argument_count; | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| instructions += | |
| BuildArguments(&argument_names, &argument_count, | |
| &positional_argument_count); // read arguments. | |
| ++argument_count; // include receiver | |
| SkipDartType(); // read function_type. | |
| if (is_unchecked_closure_call) { | |
| instructions += CheckNull(position, receiver_temp, Symbols::call()); | |
| // Lookup the function in the closure. | |
| instructions += LoadLocal(receiver_temp); | |
| if (!FLAG_precompiled_mode) { | |
| instructions += LoadNativeField(Slot::Closure_function()); | |
| } | |
| if (parsed_function()->function().is_debuggable()) { | |
| ASSERT(!parsed_function()->function().is_native()); | |
| instructions += DebugStepCheck(position); | |
| } | |
| instructions += | |
| B->ClosureCall(direct_call.target_, position, type_args_len, | |
| argument_count, argument_names, &result_type); | |
| } else { | |
| instructions += InstanceCall( | |
| position, Symbols::DynamicCall(), Token::kILLEGAL, type_args_len, | |
| argument_count, argument_names, 1, Function::null_function(), | |
| Function::null_function(), &result_type, | |
| /*use_unchecked_entry=*/false, /*call_site_attrs=*/nullptr, | |
| result_type.ReceiverNotInt()); | |
| } | |
| instructions += DropTempsPreserveTop(1); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildEqualsCall(TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const DirectCallMetadata direct_call = | |
| direct_call_metadata_helper_.GetDirectTargetForMethodInvocation(offset); | |
| ASSERT(!direct_call.check_receiver_for_null_); | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| const CallSiteAttributesMetadata call_site_attributes = | |
| call_site_attributes_metadata_helper_.GetCallSiteAttributes(offset); | |
| Fragment instructions; | |
| instructions += BuildExpression(); // read left. | |
| instructions += BuildExpression(); // read right. | |
| SkipDartType(); // read function_type. | |
| const NameIndex itarget_name = | |
| ReadInterfaceMemberNameReference(); // read interface_target_reference. | |
| const auto& interface_target = Function::ZoneHandle( | |
| Z, | |
| H.LookupMethodByMember(itarget_name, H.DartProcedureName(itarget_name))); | |
| ASSERT(interface_target.name() == Symbols::EqualOperator().ptr()); | |
| const intptr_t kTypeArgsLen = 0; | |
| const intptr_t kNumArgs = 2; | |
| const intptr_t kNumCheckedArgs = 2; | |
| if (!direct_call.target_.IsNull()) { | |
| ASSERT(CompilerState::Current().is_aot()); | |
| instructions += | |
| StaticCall(position, direct_call.target_, kNumArgs, Array::null_array(), | |
| ICData::kNoRebind, &result_type, kTypeArgsLen, | |
| /*use_unchecked_entry=*/true); | |
| } else { | |
| instructions += InstanceCall( | |
| position, Symbols::EqualOperator(), Token::kEQ, kTypeArgsLen, kNumArgs, | |
| Array::null_array(), kNumCheckedArgs, interface_target, | |
| Function::null_function(), &result_type, | |
| /*use_unchecked_entry=*/true, &call_site_attributes, | |
| result_type.ReceiverNotInt()); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildEqualsNull(TokenPosition* p) { | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| Fragment instructions; | |
| instructions += BuildExpression(); // read expression. | |
| instructions += NullConstant(); | |
| if (parsed_function()->function().is_debuggable()) { | |
| instructions += DebugStepCheck(position); | |
| } | |
| instructions += | |
| StrictCompare(position, Token::kEQ_STRICT, /*number_check=*/false); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildSuperMethodInvocation( | |
| TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| Class& klass = GetSuperOrDie(); | |
| intptr_t type_args_len = 0; | |
| const String* method_name = nullptr; | |
| intptr_t argument_count = 0; | |
| Array& argument_names = Array::Handle(Z); | |
| { | |
| AlternativeReadingScope alt(&reader_); | |
| SkipExpression(); // skip receiver | |
| method_name = &ReadNameAsMethodName(); // read name. | |
| argument_count = ReadUInt(); // read argument count. | |
| type_args_len = PeekListLength(); // read types list length. | |
| SkipListOfDartTypes(); | |
| SkipListOfExpressions(); | |
| intptr_t named_list_length = ReadListLength(); | |
| argument_names = Array::New(named_list_length, H.allocation_space()); | |
| for (intptr_t i = 0; i < named_list_length; i++) { | |
| const String& arg_name = H.DartSymbolObfuscate(ReadStringReference()); | |
| argument_names.SetAt(i, arg_name); | |
| SkipExpression(); | |
| } | |
| } | |
| // Search the superclass chain for the selector. | |
| Function& function = FindMatchingFunction( | |
| klass, *method_name, type_args_len, | |
| argument_count + 1 /* account for 'this' */, argument_names); | |
| if (function.IsNull()) { | |
| SkipExpression(); // skip receiver | |
| SkipName(); // skip name | |
| ReadUInt(); // argument count | |
| intptr_t type_list_length = ReadListLength(); | |
| Fragment instructions; | |
| instructions += | |
| Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null())); | |
| instructions += IntConstant(argument_count + 1 /* this */ + | |
| (type_list_length == 0 ? 0 : 1)); // array size | |
| instructions += CreateArray(); | |
| LocalVariable* actuals_array = MakeTemporary(); | |
| // Call allocationInvocationMirror to get instance of Invocation. | |
| Fragment build_rest_of_actuals; | |
| intptr_t actuals_array_index = 0; | |
| if (type_list_length > 0) { | |
| const TypeArguments& type_arguments = | |
| T.BuildTypeArguments(type_list_length); | |
| build_rest_of_actuals += LoadLocal(actuals_array); | |
| build_rest_of_actuals += IntConstant(actuals_array_index); | |
| build_rest_of_actuals += | |
| TranslateInstantiatedTypeArguments(type_arguments); | |
| build_rest_of_actuals += StoreIndexed(kArrayCid); | |
| ++actuals_array_index; | |
| } | |
| ++actuals_array_index; // account for 'this'. | |
| // Read arguments | |
| intptr_t list_length = ReadListLength(); | |
| intptr_t i = 0; | |
| while (i < list_length) { | |
| build_rest_of_actuals += LoadLocal(actuals_array); // array | |
| build_rest_of_actuals += IntConstant(actuals_array_index + i); // index | |
| build_rest_of_actuals += BuildExpression(); // value. | |
| build_rest_of_actuals += StoreIndexed(kArrayCid); | |
| ++i; | |
| } | |
| // Read named arguments | |
| intptr_t named_list_length = ReadListLength(); | |
| if (named_list_length > 0) { | |
| ASSERT(argument_count == list_length + named_list_length); | |
| while ((i - list_length) < named_list_length) { | |
| SkipStringReference(); | |
| build_rest_of_actuals += LoadLocal(actuals_array); // array | |
| build_rest_of_actuals += IntConstant(i + actuals_array_index); // index | |
| build_rest_of_actuals += BuildExpression(); // value. | |
| build_rest_of_actuals += StoreIndexed(kArrayCid); | |
| ++i; | |
| } | |
| } | |
| instructions += BuildAllocateInvocationMirrorCall( | |
| position, *method_name, type_list_length, | |
| /* num_arguments = */ argument_count + 1, argument_names, actuals_array, | |
| build_rest_of_actuals); | |
| SkipInterfaceMemberNameReference(); // skip target_reference. | |
| Function& nsm_function = GetNoSuchMethodOrDie(thread(), Z, klass); | |
| instructions += StaticCall(TokenPosition::kNoSource, | |
| Function::ZoneHandle(Z, nsm_function.ptr()), | |
| /* argument_count = */ 2, ICData::kNSMDispatch); | |
| instructions += DropTempsPreserveTop(1); // Drop actuals_array temp. | |
| return instructions; | |
| } else { | |
| Fragment instructions; | |
| { | |
| AlternativeReadingScope alt(&reader_); | |
| SkipExpression(); // skip receiver | |
| SkipName(); // skip name | |
| ReadUInt(); // read argument count. | |
| intptr_t list_length = ReadListLength(); // read types list length. | |
| if (list_length > 0) { | |
| const TypeArguments& type_arguments = | |
| T.BuildTypeArguments(list_length); // read types. | |
| instructions += TranslateInstantiatedTypeArguments(type_arguments); | |
| } | |
| } | |
| instructions += BuildExpression(); // read receiver | |
| SkipName(); // skip name | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| intptr_t argument_count; | |
| instructions += BuildArguments( | |
| &argument_names, &argument_count, | |
| /* positional_argument_count = */ nullptr); // read arguments. | |
| ++argument_count; // include receiver | |
| SkipInterfaceMemberNameReference(); // interfaceTargetReference | |
| return instructions + | |
| StaticCall(position, Function::ZoneHandle(Z, function.ptr()), | |
| argument_count, argument_names, ICData::kSuper, | |
| &result_type, type_args_len, | |
| /*use_unchecked_entry=*/true); | |
| } | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const InferredTypeMetadata result_type = | |
| inferred_type_metadata_helper_.GetInferredType(offset); | |
| NameIndex procedure_reference = | |
| ReadCanonicalNameReference(); // read procedure reference. | |
| intptr_t argument_count = PeekArgumentsCount(); | |
| const Function& target = | |
| Function::ZoneHandle(Z, H.LookupStaticMethodByKernelProcedure( | |
| procedure_reference, /*required=*/false)); | |
| if (target.IsNull()) { | |
| Fragment instructions; | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| instructions += | |
| BuildArguments(&argument_names, nullptr /* arg count */, | |
| nullptr /* positional arg count */); // read arguments. | |
| instructions += StaticCallMissing( | |
| position, H.DartSymbolPlain(H.CanonicalNameString(procedure_reference)), | |
| argument_count, | |
| H.IsLibrary(H.EnclosingName(procedure_reference)) | |
| ? InvocationMirror::Level::kTopLevel | |
| : InvocationMirror::Level::kStatic, | |
| InvocationMirror::Kind::kMethod); | |
| return instructions; | |
| } | |
| ASSERT(!target.IsGenerativeConstructor()); | |
| if (target.IsCachableIdempotent()) { | |
| return BuildCachableIdempotentCall(position, target); | |
| } | |
| if (target.IsExternalEffect()) { | |
| // AOT kernels will already have external effect calls removed by TFA. | |
| return BuildExternalEffect(); | |
| } | |
| const auto recognized_kind = target.recognized_kind(); | |
| switch (recognized_kind) { | |
| case MethodRecognizer::kReachabilityFence: | |
| return BuildReachabilityFence(); | |
| case MethodRecognizer::kFfiCall: | |
| return BuildFfiCall(); | |
| case MethodRecognizer::kFfiNativeCallbackFunction: | |
| return BuildFfiNativeCallbackFunction( | |
| FfiCallbackKind::kIsolateLocalStaticCallback); | |
| case MethodRecognizer::kFfiNativeAddressOf: | |
| return BuildFfiNativeAddressOf(); | |
| case MethodRecognizer::kFfiNativeIsolateLocalCallbackFunction: | |
| return BuildFfiNativeCallbackFunction( | |
| FfiCallbackKind::kIsolateLocalClosureCallback); | |
| case MethodRecognizer::kFfiNativeIsolateGroupBoundCallbackFunction: | |
| return BuildFfiNativeCallbackFunction( | |
| FfiCallbackKind::kIsolateGroupBoundStaticCallback); | |
| case MethodRecognizer::kFfiNativeIsolateGroupBoundClosureFunction: | |
| return BuildFfiNativeCallbackFunction( | |
| FfiCallbackKind::kIsolateGroupBoundClosureCallback); | |
| case MethodRecognizer::kFfiNativeAsyncCallbackFunction: | |
| return BuildFfiNativeCallbackFunction(FfiCallbackKind::kAsyncCallback); | |
| case MethodRecognizer::kFfiLoadAbiSpecificInt: | |
| return BuildLoadStoreAbiSpecificInt(/*is_store=*/false, | |
| /*at_index=*/false); | |
| case MethodRecognizer::kFfiLoadAbiSpecificIntAtIndex: | |
| return BuildLoadStoreAbiSpecificInt(/*is_store=*/false, | |
| /*at_index=*/true); | |
| case MethodRecognizer::kFfiStoreAbiSpecificInt: | |
| return BuildLoadStoreAbiSpecificInt(/*is_store=*/true, | |
| /*at_index=*/false); | |
| case MethodRecognizer::kFfiStoreAbiSpecificIntAtIndex: | |
| return BuildLoadStoreAbiSpecificInt(/*is_store=*/true, /*at_index=*/true); | |
| default: | |
| break; | |
| } | |
| Fragment instructions; | |
| const Class& klass = Class::ZoneHandle(Z, target.Owner()); | |
| const bool special_case_unchecked_cast = | |
| klass.IsTopLevel() && (klass.library() == Library::InternalLibrary()) && | |
| (target.name() == Symbols::UnsafeCast().ptr()); | |
| const bool special_case_identical = | |
| klass.IsTopLevel() && (klass.library() == Library::CoreLibrary()) && | |
| (target.name() == Symbols::Identical().ptr()); | |
| const bool special_case = | |
| special_case_identical || special_case_unchecked_cast; | |
| intptr_t type_args_len = 0; | |
| if (!special_case) { | |
| AlternativeReadingScope alt(&reader_); | |
| ReadUInt(); // read argument count. | |
| intptr_t list_length = ReadListLength(); // read types list length. | |
| if (list_length > 0) { | |
| const TypeArguments& type_arguments = | |
| T.BuildTypeArguments(list_length); // read types. | |
| instructions += TranslateInstantiatedTypeArguments(type_arguments); | |
| } | |
| type_args_len = list_length; | |
| } | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| instructions += | |
| BuildArguments(&argument_names, nullptr /* arg count */, | |
| nullptr /* positional arg count */); // read arguments. | |
| ASSERT(!special_case || | |
| target.AreValidArguments(type_args_len, argument_count, argument_names, | |
| nullptr)); | |
| // Special case identical(x, y) call. | |
| // TODO(27590) consider moving this into the inliner and force inline it | |
| // there. | |
| if (special_case_identical) { | |
| ASSERT(argument_count == 2); | |
| instructions += | |
| StrictCompare(position, Token::kEQ_STRICT, /*number_check=*/true); | |
| } else if (special_case_unchecked_cast) { | |
| // Simply do nothing: the result value is already pushed on the stack. | |
| } else { | |
| instructions += StaticCall(position, target, argument_count, argument_names, | |
| ICData::kStatic, &result_type, type_args_len); | |
| if (target.IsGenerativeConstructor()) { | |
| // Drop the result of the constructor call and leave [instance_variable] | |
| // on top-of-stack. | |
| instructions += Drop(); | |
| } | |
| // After reaching debugger(), we automatically do one single-step. | |
| // Ensure this doesn't cause us to exit the current scope. | |
| if (recognized_kind == MethodRecognizer::kDebugger) { | |
| instructions += DebugStepCheck(position); | |
| } | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildConstructorInvocation( | |
| TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| NameIndex kernel_name = | |
| ReadCanonicalNameReference(); // read target_reference. | |
| Class& klass = Class::ZoneHandle( | |
| Z, H.LookupClassByKernelClass(H.EnclosingName(kernel_name), | |
| /*required=*/false)); | |
| Fragment instructions; | |
| if (klass.IsNull()) { | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| intptr_t argument_count; | |
| instructions += BuildArguments( | |
| &argument_names, &argument_count, | |
| /* positional_argument_count = */ nullptr); // read arguments. | |
| instructions += StaticCallMissing( | |
| position, H.DartSymbolPlain(H.CanonicalNameString(kernel_name)), | |
| argument_count, InvocationMirror::Level::kConstructor, | |
| InvocationMirror::Kind::kMethod); | |
| return instructions; | |
| } | |
| const auto& error = klass.EnsureIsFinalized(H.thread()); | |
| ASSERT(error == Error::null()); | |
| if (klass.NumTypeArguments() > 0) { | |
| if (!klass.IsGeneric()) { | |
| const TypeArguments& type_arguments = TypeArguments::ZoneHandle( | |
| Z, klass.GetDeclarationInstanceTypeArguments()); | |
| instructions += Constant(type_arguments); | |
| } else { | |
| const TypeArguments& type_arguments = | |
| PeekArgumentsInstantiatedType(klass); | |
| instructions += TranslateInstantiatedTypeArguments(type_arguments); | |
| } | |
| instructions += AllocateObject(position, klass, 1); | |
| } else { | |
| instructions += AllocateObject(position, klass, 0); | |
| } | |
| LocalVariable* variable = MakeTemporary(); | |
| instructions += LoadLocal(variable); | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| intptr_t argument_count; | |
| instructions += BuildArguments( | |
| &argument_names, &argument_count, | |
| /* positional_argument_count = */ nullptr); // read arguments. | |
| const Function& target = | |
| Function::ZoneHandle(Z, H.LookupConstructorByKernelConstructor( | |
| klass, kernel_name, /*required=*/false)); | |
| ++argument_count; | |
| if (target.IsNull()) { | |
| instructions += StaticCallMissing( | |
| position, H.DartSymbolPlain(H.CanonicalNameString(kernel_name)), | |
| argument_count, InvocationMirror::Level::kConstructor, | |
| InvocationMirror::Kind::kMethod); | |
| } else { | |
| instructions += StaticCall(position, target, argument_count, argument_names, | |
| ICData::kStatic, /* result_type = */ nullptr); | |
| } | |
| return instructions + Drop(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildNot(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); | |
| if (p != nullptr) *p = position; | |
| TokenPosition operand_position = TokenPosition::kNoSource; | |
| Fragment instructions = | |
| BuildExpression(&operand_position); // read expression. | |
| instructions += BooleanNegate(); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildNullCheck(TokenPosition* p) { | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| TokenPosition operand_position = TokenPosition::kNoSource; | |
| Fragment instructions = BuildExpression(&operand_position); | |
| LocalVariable* expr_temp = MakeTemporary(); | |
| instructions += CheckNull(position, expr_temp, String::null_string()); | |
| return instructions; | |
| } | |
| // Translate the logical expression (lhs && rhs or lhs || rhs) in a context | |
| // where a value is required. | |
| // | |
| // Translation accumulates short-circuit exits from logical | |
| // subexpressions in the side_exits. These exits are expected to store | |
| // true and false into :expr_temp. | |
| // | |
| // The result of evaluating the last | |
| // expression in chain would be stored in :expr_temp directly to avoid | |
| // generating graph like: | |
| // | |
| // if (v) :expr_temp = true; else :expr_temp = false; | |
| // | |
| // Outer negations are stripped and instead negation is passed down via | |
| // negated parameter. | |
| Fragment StreamingFlowGraphBuilder::TranslateLogicalExpressionForValue( | |
| bool negated, | |
| TestFragment* side_exits) { | |
| TestFragment left = TranslateConditionForControl().Negate(negated); | |
| LogicalOperator op = static_cast<LogicalOperator>(ReadByte()); | |
| if (negated) { | |
| op = (op == kAnd) ? kOr : kAnd; | |
| } | |
| // Short circuit the control flow after the left hand side condition. | |
| if (op == kAnd) { | |
| side_exits->false_successor_addresses->AddArray( | |
| *left.false_successor_addresses); | |
| } else { | |
| side_exits->true_successor_addresses->AddArray( | |
| *left.true_successor_addresses); | |
| } | |
| // Skip negations of the right hand side. | |
| while (PeekTag() == kNot) { | |
| SkipBytes(1); | |
| ReadPosition(); | |
| negated = !negated; | |
| } | |
| Fragment right_value(op == kAnd | |
| ? left.CreateTrueSuccessor(flow_graph_builder_) | |
| : left.CreateFalseSuccessor(flow_graph_builder_)); | |
| if (PeekTag() == kLogicalExpression) { | |
| SkipBytes(1); | |
| ReadPosition(); | |
| // Handle nested logical expressions specially to avoid materializing | |
| // intermediate boolean values. | |
| right_value += TranslateLogicalExpressionForValue(negated, side_exits); | |
| } else { | |
| // Arbitrary expression on the right hand side. Translate it for value. | |
| TokenPosition position = TokenPosition::kNoSource; | |
| right_value += BuildExpression(&position); // read expression. | |
| if (negated) { | |
| right_value += BooleanNegate(); | |
| } | |
| right_value += StoreLocal(TokenPosition::kNoSource, | |
| parsed_function()->expression_temp_var()); | |
| right_value += Drop(); | |
| } | |
| return Fragment(left.entry, right_value.current); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildLogicalExpression(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); | |
| if (p != nullptr) *p = position; | |
| TestFragment exits; | |
| exits.true_successor_addresses = new TestFragment::SuccessorAddressArray(2); | |
| exits.false_successor_addresses = new TestFragment::SuccessorAddressArray(2); | |
| JoinEntryInstr* join = BuildJoinEntry(); | |
| Fragment instructions = | |
| TranslateLogicalExpressionForValue(/*negated=*/false, &exits); | |
| instructions += Goto(join); | |
| // Generate :expr_temp = true if needed and connect it to true side-exits. | |
| if (!exits.true_successor_addresses->is_empty()) { | |
| Fragment constant_fragment(exits.CreateTrueSuccessor(flow_graph_builder_)); | |
| constant_fragment += Constant(Bool::Get(true)); | |
| constant_fragment += StoreLocal(TokenPosition::kNoSource, | |
| parsed_function()->expression_temp_var()); | |
| constant_fragment += Drop(); | |
| constant_fragment += Goto(join); | |
| } | |
| // Generate :expr_temp = false if needed and connect it to false side-exits. | |
| if (!exits.false_successor_addresses->is_empty()) { | |
| Fragment constant_fragment(exits.CreateFalseSuccessor(flow_graph_builder_)); | |
| constant_fragment += Constant(Bool::Get(false)); | |
| constant_fragment += StoreLocal(TokenPosition::kNoSource, | |
| parsed_function()->expression_temp_var()); | |
| constant_fragment += Drop(); | |
| constant_fragment += Goto(join); | |
| } | |
| return Fragment(instructions.entry, join) + | |
| LoadLocal(parsed_function()->expression_temp_var()); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildConditionalExpression( | |
| TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); | |
| if (p != nullptr) *p = position; | |
| TestFragment condition = TranslateConditionForControl(); // read condition. | |
| Value* top = stack(); | |
| Fragment then_fragment(condition.CreateTrueSuccessor(flow_graph_builder_)); | |
| then_fragment += BuildExpression(); // read then. | |
| then_fragment += StoreLocal(TokenPosition::kNoSource, | |
| parsed_function()->expression_temp_var()); | |
| then_fragment += Drop(); | |
| ASSERT(stack() == top); | |
| Fragment otherwise_fragment( | |
| condition.CreateFalseSuccessor(flow_graph_builder_)); | |
| otherwise_fragment += BuildExpression(); // read otherwise. | |
| otherwise_fragment += StoreLocal(TokenPosition::kNoSource, | |
| parsed_function()->expression_temp_var()); | |
| otherwise_fragment += Drop(); | |
| ASSERT(stack() == top); | |
| JoinEntryInstr* join = BuildJoinEntry(); | |
| then_fragment += Goto(join); | |
| otherwise_fragment += Goto(join); | |
| SkipOptionalDartType(); // read unused static type. | |
| return Fragment(condition.entry, join) + | |
| LoadLocal(parsed_function()->expression_temp_var()); | |
| } | |
| void StreamingFlowGraphBuilder::FlattenStringConcatenation( | |
| PiecesCollector* collector) { | |
| const auto length = ReadListLength(); | |
| for (intptr_t i = 0; i < length; ++i) { | |
| const auto offset = reader_.offset(); | |
| switch (PeekTag()) { | |
| case kStringLiteral: { | |
| ReadTag(); | |
| ReadPosition(); | |
| const String& s = H.DartSymbolPlain(ReadStringReference()); | |
| // Skip empty strings. | |
| if (!s.Equals("")) { | |
| collector->Add({-1, &s}); | |
| } | |
| break; | |
| } | |
| case kStringConcatenation: { | |
| // Flatten by hoisting nested expressions up into the outer concat. | |
| ReadTag(); | |
| ReadPosition(); | |
| FlattenStringConcatenation(collector); | |
| break; | |
| } | |
| default: { | |
| collector->Add({offset, nullptr}); | |
| SkipExpression(); | |
| } | |
| } | |
| } | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildStringConcatenation(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); | |
| if (p != nullptr) { | |
| *p = position; | |
| } | |
| // Collect and flatten all pieces of this and any nested StringConcats. | |
| // The result is a single sequence of pieces, potentially flattened to | |
| // a single String. | |
| // The collector will hold concatenated strings and Reader offsets of | |
| // non-string pieces. | |
| PiecesCollector collector(Z, &H); | |
| FlattenStringConcatenation(&collector); | |
| collector.FlushRun(); | |
| if (collector.pieces.length() == 1) { | |
| // No need to Interp. a single string, so return string as a Constant: | |
| if (collector.pieces[0].literal != nullptr) { | |
| return Constant(*collector.pieces[0].literal); | |
| } | |
| // A single non-string piece is handle by StringInterpolateSingle: | |
| AlternativeReadingScope scope(&reader_, collector.pieces[0].offset); | |
| Fragment instructions; | |
| instructions += BuildExpression(); | |
| instructions += StringInterpolateSingle(position); | |
| return instructions; | |
| } | |
| Fragment instructions; | |
| instructions += Constant(TypeArguments::ZoneHandle(Z)); | |
| instructions += IntConstant(collector.pieces.length()); | |
| instructions += CreateArray(); | |
| LocalVariable* array = MakeTemporary(); | |
| for (intptr_t i = 0; i < collector.pieces.length(); ++i) { | |
| // All pieces are now either a concat'd string or an expression we can | |
| // read at a given offset. | |
| if (collector.pieces[i].literal != nullptr) { | |
| instructions += LoadLocal(array); | |
| instructions += IntConstant(i); | |
| instructions += Constant(*collector.pieces[i].literal); | |
| } else { | |
| AlternativeReadingScope scope(&reader_, collector.pieces[i].offset); | |
| instructions += LoadLocal(array); | |
| instructions += IntConstant(i); | |
| instructions += BuildExpression(); | |
| } | |
| instructions += StoreIndexed(kArrayCid); | |
| } | |
| instructions += StringInterpolate(position); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildIsTest(TokenPosition position, | |
| const AbstractType& type) { | |
| Fragment instructions; | |
| // The VM does not like an instanceOf call with a dynamic type. We need to | |
| // special case this situation by detecting a top type. | |
| if (type.IsTopType()) { | |
| // Evaluate the expression on the left but ignore its result. | |
| instructions += Drop(); | |
| // Let condition be always true. | |
| instructions += Constant(Bool::True()); | |
| } else { | |
| // See if simple instanceOf is applicable. | |
| if (dart::SimpleInstanceOfType(type)) { | |
| instructions += Constant(type); | |
| instructions += InstanceCall( | |
| position, Library::PrivateCoreLibName(Symbols::_simpleInstanceOf()), | |
| Token::kIS, 2, 2); // 2 checked arguments. | |
| return instructions; | |
| } | |
| if (type.IsRecordType()) { | |
| instructions += BuildRecordIsTest(position, RecordType::Cast(type)); | |
| return instructions; | |
| } | |
| if (!type.IsInstantiated(kCurrentClass)) { | |
| instructions += LoadInstantiatorTypeArguments(); | |
| } else { | |
| instructions += NullConstant(); | |
| } | |
| if (!type.IsInstantiated(kFunctions)) { | |
| instructions += LoadFunctionTypeArguments(); | |
| } else { | |
| instructions += NullConstant(); | |
| } | |
| instructions += Constant(type); | |
| instructions += InstanceCall( | |
| position, Library::PrivateCoreLibName(Symbols::_instanceOf()), | |
| Token::kIS, 4); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildRecordIsTest(TokenPosition position, | |
| const RecordType& type) { | |
| // Type of a record instance depends on the runtime types of all | |
| // its fields, so subtype test cache cannot be used for testing | |
| // record types and runtime call is used. | |
| // So it is more efficient to test each record field separately | |
| // without going to runtime. | |
| Fragment instructions; | |
| JoinEntryInstr* is_true = BuildJoinEntry(); | |
| JoinEntryInstr* is_false = BuildJoinEntry(); | |
| LocalVariable* instance = MakeTemporary(); | |
| // Test if instance is null. | |
| if (type.IsNullable()) { | |
| TargetEntryInstr* is_null; | |
| TargetEntryInstr* not_null; | |
| instructions += LoadLocal(instance); | |
| instructions += BranchIfNull(&is_null, ¬_null); | |
| Fragment(is_null) + Goto(is_true); | |
| instructions.current = not_null; | |
| } | |
| // Test if instance is record. | |
| { | |
| TargetEntryInstr* is_record; | |
| TargetEntryInstr* not_record; | |
| instructions += LoadLocal(instance); | |
| instructions += B->LoadClassId(); | |
| instructions += IntConstant(kRecordCid); | |
| instructions += BranchIfEqual(&is_record, ¬_record); | |
| Fragment(not_record) + Goto(is_false); | |
| instructions.current = is_record; | |
| } | |
| // Test record shape. | |
| { | |
| TargetEntryInstr* same_shape; | |
| TargetEntryInstr* different_shape; | |
| instructions += LoadLocal(instance); | |
| instructions += LoadNativeField(Slot::Record_shape()); | |
| instructions += IntConstant(type.shape().AsInt()); | |
| instructions += BranchIfEqual(&same_shape, &different_shape); | |
| Fragment(different_shape) + Goto(is_false); | |
| instructions.current = same_shape; | |
| } | |
| // Test each record field | |
| for (intptr_t i = 0, n = type.NumFields(); i < n; ++i) { | |
| TargetEntryInstr* success; | |
| TargetEntryInstr* failure; | |
| instructions += LoadLocal(instance); | |
| instructions += LoadNativeField(Slot::GetRecordFieldSlot( | |
| H.thread(), compiler::target::Record::field_offset(i))); | |
| instructions += | |
| BuildIsTest(position, AbstractType::ZoneHandle(Z, type.FieldTypeAt(i))); | |
| instructions += Constant(Bool::True()); | |
| instructions += BranchIfEqual(&success, &failure); | |
| Fragment(failure) + Goto(is_false); | |
| instructions.current = success; | |
| } | |
| instructions += Goto(is_true); | |
| JoinEntryInstr* join = BuildJoinEntry(); | |
| LocalVariable* expr_temp = parsed_function()->expression_temp_var(); | |
| instructions.current = is_true; | |
| instructions += Constant(Bool::True()); | |
| instructions += StoreLocal(TokenPosition::kNoSource, expr_temp); | |
| instructions += Drop(); | |
| instructions += Goto(join); | |
| instructions.current = is_false; | |
| instructions += Constant(Bool::False()); | |
| instructions += StoreLocal(TokenPosition::kNoSource, expr_temp); | |
| instructions += Drop(); | |
| instructions += Goto(join); | |
| instructions.current = join; | |
| instructions += Drop(); // Instance. | |
| instructions += LoadLocal(expr_temp); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildIsExpression(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| Fragment instructions = BuildExpression(); // read operand. | |
| const AbstractType& type = T.BuildType(); // read type. | |
| instructions += BuildIsTest(position, type); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildAsExpression(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const uint8_t flags = ReadFlags(); // read flags. | |
| const bool is_unchecked_cast = (flags & kAsExpressionFlagUnchecked) != 0; | |
| const bool is_type_error = (flags & kAsExpressionFlagTypeError) != 0; | |
| Fragment instructions = BuildExpression(); // read operand. | |
| const AbstractType& type = T.BuildType(); // read type. | |
| if (is_unchecked_cast || (type.IsInstantiated() && type.IsTopType())) { | |
| // We already evaluated the operand on the left and just leave it there as | |
| // the result of unchecked cast or `obj as dynamic` expression. | |
| } else { | |
| // We do not care whether the 'as' cast as implicitly added by the frontend | |
| // or explicitly written by the user, in both cases we use an assert | |
| // assignable. | |
| instructions += B->AssertAssignableLoadTypeArguments( | |
| position, type, | |
| is_type_error ? Symbols::Empty() : Symbols::InTypeCast(), | |
| AssertAssignableInstr::kInsertedByFrontend); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildTypeLiteral(TokenPosition* position) { | |
| TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| const AbstractType& type = T.BuildType(); // read type. | |
| Fragment instructions; | |
| if (type.IsInstantiated()) { | |
| instructions += Constant(type); | |
| } else { | |
| if (!type.IsInstantiated(kCurrentClass)) { | |
| instructions += LoadInstantiatorTypeArguments(); | |
| } else { | |
| instructions += NullConstant(); | |
| } | |
| if (!type.IsInstantiated(kFunctions)) { | |
| instructions += LoadFunctionTypeArguments(); | |
| } else { | |
| instructions += NullConstant(); | |
| } | |
| instructions += InstantiateType(type); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildThisExpression( | |
| TokenPosition* position) { | |
| ReadPosition(); // ignore file offset. | |
| if (position != nullptr) *position = TokenPosition::kNoSource; | |
| return LoadLocal(parsed_function()->receiver_var()); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildRethrow(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| Fragment instructions = DebugStepCheck(position); | |
| instructions += LoadLocal(catch_block()->exception_var()); | |
| instructions += LoadLocal(catch_block()->stack_trace_var()); | |
| instructions += RethrowException(position, catch_block()->catch_try_index()); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildThrow(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| Fragment instructions; | |
| const uint8_t flags = ReadByte(); | |
| const bool is_synthetic_error_handler = (flags & kThrowForErrorHandling) != 0; | |
| if (is_synthetic_error_handler) { | |
| synthetic_error_handler_depth_inc(); | |
| } | |
| instructions += BuildExpression(); // read expression. | |
| if (NeedsDebugStepCheck(stack(), position)) { | |
| instructions = DebugStepCheck(position) + instructions; | |
| } | |
| instructions += ThrowException(position); | |
| ASSERT(instructions.is_closed()); | |
| if (is_synthetic_error_handler) { | |
| synthetic_error_handler_depth_dec(); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildListLiteral(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const TypeArguments& type_arguments = T.BuildTypeArguments(1); // read type. | |
| intptr_t length = ReadListLength(); // read list length. | |
| // Note: there will be "length" expressions. | |
| // The type argument for the factory call. | |
| Fragment instructions = TranslateInstantiatedTypeArguments(type_arguments); | |
| // List literals up to 8 elements are lowered in the front-end | |
| // (pkg/vm/lib/transformations/list_literals_lowering.dart) | |
| const intptr_t kNumSpecializedListLiteralConstructors = 8; | |
| ASSERT(length > kNumSpecializedListLiteralConstructors); | |
| LocalVariable* type = MakeTemporary(); | |
| instructions += LoadLocal(type); | |
| // The type arguments for CreateArray. | |
| instructions += LoadLocal(type); | |
| instructions += IntConstant(length); | |
| instructions += CreateArray(); | |
| LocalVariable* array = MakeTemporary(); | |
| for (intptr_t i = 0; i < length; ++i) { | |
| instructions += LoadLocal(array); | |
| instructions += IntConstant(i); | |
| instructions += BuildExpression(); // read ith expression. | |
| instructions += StoreIndexed(kArrayCid); | |
| } | |
| const Class& growable_list_class = | |
| Class::Handle(Z, Library::LookupCoreClass(Symbols::_GrowableList())); | |
| ASSERT(!growable_list_class.IsNull()); | |
| const Function& factory_method = | |
| Function::ZoneHandle(Z, growable_list_class.LookupFunctionAllowPrivate( | |
| Symbols::_GrowableListLiteralFactory())); | |
| ASSERT(!factory_method.IsNull()); | |
| instructions += StaticCall(position, factory_method, | |
| /*argument_count=*/1, Array::null_array(), | |
| ICData::kStatic, /*result_type=*/nullptr, | |
| /*type_args_len=*/1); | |
| instructions += DropTempsPreserveTop(1); // Instantiated type_arguments. | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildMapLiteral(TokenPosition* p) { | |
| TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| const TypeArguments& type_arguments = | |
| T.BuildTypeArguments(2); // read key_type and value_type. | |
| // The type argument for the factory call `new Map<K, V>._fromLiteral(List)`. | |
| Fragment instructions = TranslateInstantiatedTypeArguments(type_arguments); | |
| intptr_t length = ReadListLength(); // read list length. | |
| // Note: there will be "length" map entries (i.e. key and value expressions). | |
| if (length == 0) { | |
| instructions += Constant(Object::empty_array()); | |
| } else { | |
| // The type arguments for `new List<X>(int len)`. | |
| instructions += Constant(TypeArguments::ZoneHandle(Z)); | |
| // We generate a list of tuples, i.e. [key1, value1, ..., keyN, valueN]. | |
| instructions += IntConstant(2 * length); | |
| instructions += CreateArray(); | |
| LocalVariable* array = MakeTemporary(); | |
| for (intptr_t i = 0; i < length; ++i) { | |
| instructions += LoadLocal(array); | |
| instructions += IntConstant(2 * i); | |
| instructions += BuildExpression(); // read ith key. | |
| instructions += StoreIndexed(kArrayCid); | |
| instructions += LoadLocal(array); | |
| instructions += IntConstant(2 * i + 1); | |
| instructions += BuildExpression(); // read ith value. | |
| instructions += StoreIndexed(kArrayCid); | |
| } | |
| } | |
| const Class& map_class = | |
| Class::Handle(Z, Library::LookupCoreClass(Symbols::Map())); | |
| Function& factory_method = Function::ZoneHandle(Z); | |
| if (map_class.EnsureIsFinalized(H.thread()) == Error::null()) { | |
| factory_method = map_class.LookupFactory( | |
| Library::PrivateCoreLibName(Symbols::MapLiteralFactory())); | |
| } | |
| return instructions + StaticCall(position, factory_method, | |
| /*argument_count=*/1, Array::null_array(), | |
| ICData::kStatic, /*result_type=*/nullptr, | |
| /*type_args_len=*/2); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildRecordLiteral(TokenPosition* p) { | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| // Figure out record shape. | |
| const intptr_t positional_count = ReadListLength(); | |
| intptr_t named_count = -1; | |
| const Array* field_names = &Object::empty_array(); | |
| { | |
| AlternativeReadingScope alt(&reader_); | |
| for (intptr_t i = 0; i < positional_count; ++i) { | |
| SkipExpression(); | |
| } | |
| named_count = ReadListLength(); | |
| if (named_count > 0) { | |
| Array& names = Array::ZoneHandle(Z, Array::New(named_count, Heap::kOld)); | |
| for (intptr_t i = 0; i < named_count; ++i) { | |
| String& name = | |
| H.DartSymbolObfuscate(ReadStringReference()); // read ith name. | |
| SkipExpression(); // read ith expression. | |
| names.SetAt(i, name); | |
| } | |
| names.MakeImmutable(); | |
| field_names = &names; | |
| } | |
| } | |
| const intptr_t num_fields = positional_count + named_count; | |
| const RecordShape shape = | |
| RecordShape::Register(thread(), num_fields, *field_names); | |
| Fragment instructions; | |
| if (num_fields == 2 || | |
| (num_fields == 3 && AllocateSmallRecordABI::kValue2Reg != kNoRegister)) { | |
| // Generate specialized allocation for a small number of fields. | |
| for (intptr_t i = 0; i < positional_count; ++i) { | |
| instructions += BuildExpression(); // read ith expression. | |
| } | |
| ReadListLength(); // read list length. | |
| for (intptr_t i = 0; i < named_count; ++i) { | |
| SkipStringReference(); // read ith name. | |
| instructions += BuildExpression(); // read ith expression. | |
| } | |
| SkipDartType(); // read recordType. | |
| instructions += B->AllocateSmallRecord(position, shape); | |
| return instructions; | |
| } | |
| instructions += B->AllocateRecord(position, shape); | |
| LocalVariable* record = MakeTemporary(); | |
| // List of positional. | |
| intptr_t pos = 0; | |
| for (intptr_t i = 0; i < positional_count; ++i, ++pos) { | |
| instructions += LoadLocal(record); | |
| instructions += BuildExpression(); // read ith expression. | |
| instructions += B->StoreNativeField( | |
| Slot::GetRecordFieldSlot(thread(), | |
| compiler::target::Record::field_offset(pos)), | |
| StoreFieldInstr::Kind::kInitializing); | |
| } | |
| // List of named. | |
| ReadListLength(); // read list length. | |
| for (intptr_t i = 0; i < named_count; ++i, ++pos) { | |
| SkipStringReference(); // read ith name. | |
| instructions += LoadLocal(record); | |
| instructions += BuildExpression(); // read ith expression. | |
| instructions += B->StoreNativeField( | |
| Slot::GetRecordFieldSlot(thread(), | |
| compiler::target::Record::field_offset(pos)), | |
| StoreFieldInstr::Kind::kInitializing); | |
| } | |
| SkipDartType(); // read recordType. | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildRecordFieldGet(TokenPosition* p, | |
| bool is_named) { | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| Fragment instructions = BuildExpression(); // read receiver. | |
| const RecordType& record_type = | |
| RecordType::Cast(T.BuildType()); // read recordType. | |
| intptr_t field_index = -1; | |
| const Array& field_names = | |
| Array::Handle(Z, record_type.GetFieldNames(H.thread())); | |
| const intptr_t num_positional_fields = | |
| record_type.NumFields() - field_names.Length(); | |
| if (is_named) { | |
| const String& field_name = H.DartSymbolObfuscate(ReadStringReference()); | |
| for (intptr_t i = 0, n = field_names.Length(); i < n; ++i) { | |
| if (field_names.At(i) == field_name.ptr()) { | |
| field_index = i; | |
| break; | |
| } | |
| } | |
| ASSERT(field_index >= 0 && field_index < field_names.Length()); | |
| field_index += num_positional_fields; | |
| } else { | |
| field_index = ReadUInt(); | |
| ASSERT(field_index < num_positional_fields); | |
| } | |
| instructions += B->LoadNativeField(Slot::GetRecordFieldSlot( | |
| thread(), compiler::target::Record::field_offset(field_index))); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildFunctionExpression() { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| ReadPosition(); // read position. | |
| const intptr_t local_function_id = ReadUInt(); // read id. | |
| ASSERT(local_function_id > 0); | |
| return BuildFunctionNode(local_function_id, offset); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildLet(TokenPosition* p) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| Fragment instructions; | |
| instructions += EnterScope(offset); | |
| instructions += BuildVariable(nullptr); // read variable. | |
| instructions += BuildExpression(); // read body. | |
| instructions += ExitScope(offset); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildBlockExpression() { | |
| block_expression_depth_inc(); | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| Fragment instructions; | |
| instructions += EnterScope(offset); | |
| ReadPosition(); // ignore file offset. | |
| ReadUInt(); // read scope size. | |
| const intptr_t list_length = ReadListLength(); // read number of statements. | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| instructions += BuildStatement(); // read ith statement. | |
| } | |
| instructions += BuildExpression(); // read expression (inside scope). | |
| instructions += ExitScope(offset); | |
| SkipScope(); | |
| block_expression_depth_dec(); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildBigIntLiteral( | |
| TokenPosition* position) { | |
| ReadPosition(); // ignore file offset. | |
| if (position != nullptr) *position = TokenPosition::kNoSource; | |
| const String& value = | |
| H.DartString(ReadStringReference()); // read index into string table. | |
| const Integer& integer = Integer::ZoneHandle(Z, Integer::NewCanonical(value)); | |
| if (integer.IsNull()) { | |
| const auto& script = Script::Handle(Z, Script()); | |
| H.ReportError(script, TokenPosition::kNoSource, | |
| "Integer literal %s is out of range", value.ToCString()); | |
| UNREACHABLE(); | |
| } | |
| return Constant(integer); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildStringLiteral( | |
| TokenPosition* position) { | |
| ReadPosition(); // ignore file offset. | |
| if (position != nullptr) *position = TokenPosition::kNoSource; | |
| return Constant(H.DartSymbolPlain( | |
| ReadStringReference())); // read index into string table. | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildIntLiteral(uint8_t payload, | |
| TokenPosition* position) { | |
| ReadPosition(); // ignore file offset. | |
| if (position != nullptr) *position = TokenPosition::kNoSource; | |
| int64_t value = static_cast<int32_t>(payload) - SpecializedIntLiteralBias; | |
| return IntConstant(value); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildIntLiteral(bool is_negative, | |
| TokenPosition* position) { | |
| ReadPosition(); // ignore file offset. | |
| if (position != nullptr) *position = TokenPosition::kNoSource; | |
| int64_t value = is_negative ? -static_cast<int64_t>(ReadUInt()) | |
| : ReadUInt(); // read value. | |
| return IntConstant(value); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildDoubleLiteral( | |
| TokenPosition* position) { | |
| ReadPosition(); // ignore file offset. | |
| if (position != nullptr) *position = TokenPosition::kNoSource; | |
| Double& constant = Double::ZoneHandle( | |
| Z, Double::NewCanonical(ReadDouble())); // read double. | |
| return Constant(constant); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildBoolLiteral(bool value, | |
| TokenPosition* position) { | |
| ReadPosition(); // ignore file offset. | |
| if (position != nullptr) *position = TokenPosition::kNoSource; | |
| return Constant(Bool::Get(value)); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildNullLiteral(TokenPosition* position) { | |
| ReadPosition(); // ignore file offset. | |
| if (position != nullptr) *position = TokenPosition::kNoSource; | |
| return Constant(Instance::ZoneHandle(Z, Instance::null())); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildConstantExpression( | |
| TokenPosition* position, | |
| Tag tag) { | |
| TokenPosition p = TokenPosition::kNoSource; | |
| if (tag == kConstantExpression) { | |
| p = ReadPosition(); | |
| SkipDartType(); | |
| } else if (tag == kFileUriConstantExpression) { | |
| // TODO(alexmarkov): Use file offset together with file uri. | |
| ReadPosition(); | |
| ReadUInt(); | |
| SkipDartType(); | |
| } | |
| if (position != nullptr) *position = p; | |
| const intptr_t constant_index = ReadUInt(); | |
| Fragment result = Constant( | |
| Object::ZoneHandle(Z, constant_reader_.ReadConstant(constant_index))); | |
| return result; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildPartialTearoffInstantiation( | |
| TokenPosition* p) { | |
| const TokenPosition position = ReadPosition(); // read position. | |
| if (p != nullptr) *p = position; | |
| Fragment instructions = BuildExpression(); | |
| intptr_t num_type_args = ReadListLength(); | |
| const TypeArguments& type_args = T.BuildTypeArguments(num_type_args); | |
| instructions += TranslateInstantiatedTypeArguments(type_args); | |
| const Library& dart_internal = Library::Handle(Z, Library::InternalLibrary()); | |
| const Function& instantiate_closure_function = Function::ZoneHandle( | |
| Z, | |
| dart_internal.LookupFunctionAllowPrivate(Symbols::_instantiateClosure())); | |
| ASSERT(!instantiate_closure_function.IsNull()); | |
| instructions += StaticCall(TokenPosition::kNoSource, | |
| instantiate_closure_function, 2, ICData::kStatic); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildLibraryPrefixAction( | |
| TokenPosition* position, | |
| const String& selector) { | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| const intptr_t dependency_index = ReadUInt(); | |
| const Library& current_library = Library::Handle( | |
| Z, Class::Handle(Z, parsed_function()->function().Owner()).library()); | |
| const Array& dependencies = Array::Handle(Z, current_library.dependencies()); | |
| const LibraryPrefix& prefix = | |
| LibraryPrefix::CheckedZoneHandle(Z, dependencies.At(dependency_index)); | |
| const Function& function = | |
| Function::ZoneHandle(Z, Library::Handle(Z, Library::CoreLibrary()) | |
| .LookupFunctionAllowPrivate(selector)); | |
| ASSERT(!function.IsNull()); | |
| Fragment instructions; | |
| instructions += Constant(prefix); | |
| instructions += StaticCall(pos, function, 1, ICData::kStatic); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildAwaitExpression( | |
| TokenPosition* position) { | |
| ASSERT(parsed_function()->function().IsAsyncFunction() || | |
| parsed_function()->function().IsAsyncGenerator()); | |
| Fragment instructions; | |
| const TokenPosition pos = ReadPosition(); // read file offset. | |
| if (position != nullptr) *position = pos; | |
| instructions += BuildExpression(); // read operand. | |
| SuspendInstr::StubId stub_id = SuspendInstr::StubId::kAwait; | |
| if (ReadTag() == kSomething) { | |
| const AbstractType& type = T.BuildType(); // read runtime check type. | |
| if (!type.IsType() || | |
| !Class::Handle(Z, type.type_class()).IsFutureClass()) { | |
| FATAL("Unexpected type for runtime check in await: %s", type.ToCString()); | |
| } | |
| ASSERT(type.IsFinalized()); | |
| const auto& type_args = | |
| TypeArguments::ZoneHandle(Z, Type::Cast(type).arguments()); | |
| if (!type_args.IsNull()) { | |
| const auto& type_arg = AbstractType::Handle(Z, type_args.TypeAt(0)); | |
| if (!type_arg.IsTopType()) { | |
| instructions += TranslateInstantiatedTypeArguments(type_args); | |
| stub_id = SuspendInstr::StubId::kAwaitWithTypeCheck; | |
| } | |
| } | |
| } | |
| if (NeedsDebugStepCheck(parsed_function()->function(), pos)) { | |
| instructions += DebugStepCheck(pos); | |
| } | |
| instructions += B->Suspend(pos, stub_id); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildFileUriExpression( | |
| TokenPosition* position) { | |
| ReadUInt(); // read uri | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| return BuildExpression(position); // read expression. | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildExpressionStatement( | |
| TokenPosition* position) { | |
| Fragment instructions = BuildExpression(position); // read expression. | |
| instructions += Drop(); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildBlock(TokenPosition* position) { | |
| intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| Fragment instructions; | |
| instructions += EnterScope(offset); | |
| const TokenPosition pos = ReadPosition(); // read file offset. | |
| if (position != nullptr) *position = pos; | |
| ReadPosition(); // read file end offset. | |
| ReadUInt(); // read scope size. | |
| intptr_t list_length = ReadListLength(); // read number of statements. | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| if (instructions.is_open()) { | |
| instructions += BuildStatement(); // read ith statement. | |
| } else { | |
| SkipStatement(); // read ith statement. | |
| } | |
| } | |
| instructions += ExitScope(offset); | |
| SkipScope(); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildEmptyStatement() { | |
| return Fragment(); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildAssertBlock(TokenPosition* position) { | |
| if (!IG->asserts()) { | |
| SkipStatementList(); | |
| return Fragment(); | |
| } | |
| intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| Fragment instructions; | |
| instructions += EnterScope(offset); | |
| intptr_t list_length = ReadListLength(); // read number of statements. | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| if (instructions.is_open()) { | |
| // read ith statement. | |
| instructions += BuildStatement(i == 0 ? position : nullptr); | |
| } else { | |
| SkipStatement(); // read ith statement. | |
| } | |
| } | |
| instructions += ExitScope(offset); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildAssertStatement( | |
| TokenPosition* position) { | |
| if (!IG->asserts()) { | |
| SetOffset(ReaderOffset() - 1); // Include the tag. | |
| SkipStatement(); // read this statement. | |
| return Fragment(); | |
| } | |
| TargetEntryInstr* then; | |
| TargetEntryInstr* otherwise; | |
| Fragment instructions; | |
| instructions += BuildExpression(position); // read condition. | |
| const TokenPosition condition_start_offset = | |
| ReadPosition(); // read condition start offset. | |
| const TokenPosition condition_end_offset = | |
| ReadPosition(); // read condition end offset. | |
| instructions += RecordCoverage(condition_start_offset); | |
| instructions += Constant(Bool::True()); | |
| instructions += BranchIfEqual(&then, &otherwise); | |
| const Class& klass = | |
| Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::AssertionError())); | |
| ASSERT(!klass.IsNull()); | |
| const auto& error = klass.EnsureIsFinalized(thread()); | |
| ASSERT(error == Error::null()); | |
| Fragment otherwise_fragment(otherwise); | |
| if (CompilerState::Current().is_aot()) { | |
| // When in AOT, figure out start line, end line, line fragment needed for | |
| // the message now, because it won't be available at runtime. | |
| const Function& target = Function::ZoneHandle( | |
| Z, klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNewSource())); | |
| ASSERT(!target.IsNull()); | |
| auto& script = Script::ZoneHandle(Z, Script()); | |
| auto& condition_text = String::ZoneHandle(Z); | |
| intptr_t from_line = -1, from_column = -1; | |
| if (script.GetTokenLocation(condition_start_offset, &from_line, | |
| &from_column)) { | |
| // Extract the assertion condition text (if source is available). | |
| intptr_t to_line, to_column; | |
| script.GetTokenLocation(condition_end_offset, &to_line, &to_column); | |
| condition_text = | |
| script.GetSnippet(from_line, from_column, to_line, to_column); | |
| condition_text = Symbols::New(thread(), condition_text); | |
| } else { | |
| condition_text = Symbols::OptimizedOut().ptr(); | |
| } | |
| otherwise_fragment += Constant(condition_text); | |
| otherwise_fragment += Constant(String::ZoneHandle(Z, script.url())); | |
| otherwise_fragment += IntConstant(from_line); // line | |
| otherwise_fragment += IntConstant(from_column); // pos | |
| Tag tag = ReadTag(); // read (first part of) message. | |
| if (tag == kSomething) { | |
| otherwise_fragment += BuildExpression(); // read (rest of) message. | |
| } else { | |
| otherwise_fragment += Constant(Instance::ZoneHandle(Z)); // null. | |
| } | |
| otherwise_fragment += | |
| StaticCall(condition_start_offset, target, 5, ICData::kStatic); | |
| } else { | |
| const Function& target = Function::ZoneHandle( | |
| Z, klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNew())); | |
| ASSERT(!target.IsNull()); | |
| otherwise_fragment += IntConstant(condition_start_offset.Pos()); | |
| otherwise_fragment += IntConstant(condition_end_offset.Pos()); | |
| Tag tag = ReadTag(); // read (first part of) message. | |
| if (tag == kSomething) { | |
| otherwise_fragment += BuildExpression(); // read (rest of) message. | |
| } else { | |
| otherwise_fragment += Constant(Instance::ZoneHandle(Z)); // null. | |
| } | |
| // Note: condition_start_offset points to the first token after the opening | |
| // paren, not the beginning of 'assert'. | |
| otherwise_fragment += | |
| StaticCall(condition_start_offset, target, 3, ICData::kStatic); | |
| } | |
| otherwise_fragment += Drop(); | |
| ASSERT(otherwise_fragment.is_closed()); | |
| return Fragment(instructions.entry, then); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildLabeledStatement( | |
| TokenPosition* position) { | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| // There can be several cases: | |
| // | |
| // * the body contains a break | |
| // * the body doesn't contain a break | |
| // | |
| // * translating the body results in a closed fragment | |
| // * translating the body results in a open fragment | |
| // | |
| // => We will only know which case we are in after the body has been | |
| // traversed. | |
| BreakableBlock block(flow_graph_builder_); | |
| Fragment instructions = BuildStatement(position); // read body. | |
| if (block.HadJumper()) { | |
| if (instructions.is_open()) { | |
| instructions += Goto(block.destination()); | |
| } | |
| return Fragment(instructions.entry, block.destination()); | |
| } else { | |
| return instructions; | |
| } | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildBreakStatement( | |
| TokenPosition* position) { | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| intptr_t target_index = ReadUInt(); // read target index. | |
| TryFinallyBlock* outer_finally = nullptr; | |
| intptr_t target_context_depth = -1; | |
| JoinEntryInstr* destination = breakable_block()->BreakDestination( | |
| target_index, &outer_finally, &target_context_depth); | |
| Fragment instructions; | |
| // Break statement should pause before manipulation of context, which | |
| // will possibly cause debugger having incorrect context object. | |
| if (NeedsDebugStepCheck(parsed_function()->function(), pos)) { | |
| instructions += DebugStepCheck(pos); | |
| } | |
| instructions += | |
| TranslateFinallyFinalizers(outer_finally, target_context_depth); | |
| if (instructions.is_open()) { | |
| instructions += Goto(destination); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildWhileStatement( | |
| TokenPosition* position) { | |
| loop_depth_inc(); | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| ReadUInt(); // read scope size. | |
| TestFragment condition = TranslateConditionForControl(); // read condition. | |
| const Fragment body = BuildStatementWithBranchCoverage(); // read body | |
| Fragment body_entry(condition.CreateTrueSuccessor(flow_graph_builder_)); | |
| body_entry += body; | |
| Instruction* entry; | |
| if (body_entry.is_open()) { | |
| JoinEntryInstr* join = BuildJoinEntry(); | |
| body_entry += Goto(join); | |
| Fragment loop(join); | |
| loop += CheckStackOverflow(pos); // may have non-empty stack | |
| loop.current->LinkTo(condition.entry); | |
| entry = Goto(join).entry; | |
| } else { | |
| entry = condition.entry; | |
| } | |
| SkipScope(); | |
| loop_depth_dec(); | |
| return Fragment(entry, condition.CreateFalseSuccessor(flow_graph_builder_)); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildDoStatement(TokenPosition* position) { | |
| loop_depth_inc(); | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| Fragment body = BuildStatementWithBranchCoverage(); // read body. | |
| if (body.is_closed()) { | |
| SkipExpression(); // read condition. | |
| loop_depth_dec(); | |
| return body; | |
| } | |
| TestFragment condition = TranslateConditionForControl(); | |
| JoinEntryInstr* join = BuildJoinEntry(); | |
| Fragment loop(join); | |
| loop += CheckStackOverflow(pos); // may have non-empty stack | |
| loop += body; | |
| loop <<= condition.entry; | |
| condition.IfTrueGoto(flow_graph_builder_, join); | |
| loop_depth_dec(); | |
| return Fragment( | |
| new (Z) GotoInstr(join, CompilerState::Current().GetNextDeoptId()), | |
| condition.CreateFalseSuccessor(flow_graph_builder_)); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildForStatement(TokenPosition* position) { | |
| intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| ReadUInt(); // read scope size. | |
| Fragment declarations; | |
| loop_depth_inc(); | |
| const LocalScope* context_scope = nullptr; | |
| declarations += EnterScope(offset, &context_scope); | |
| intptr_t list_length = ReadListLength(); // read number of variables. | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| declarations += BuildVariableDeclaration(nullptr); // read ith variable. | |
| } | |
| Tag tag = ReadTag(); // Read first part of condition. | |
| TestFragment condition; | |
| BlockEntryInstr* body_entry; | |
| BlockEntryInstr* loop_exit; | |
| if (tag != kNothing) { | |
| condition = TranslateConditionForControl(); | |
| body_entry = condition.CreateTrueSuccessor(flow_graph_builder_); | |
| loop_exit = condition.CreateFalseSuccessor(flow_graph_builder_); | |
| } else { | |
| body_entry = BuildJoinEntry(); | |
| loop_exit = BuildJoinEntry(); | |
| } | |
| Fragment updates; | |
| list_length = ReadListLength(); // read number of updates. | |
| for (intptr_t i = 0; i < list_length; ++i) { | |
| updates += BuildExpression(); // read ith update. | |
| updates += Drop(); | |
| } | |
| Fragment body(body_entry); | |
| body += BuildStatementWithBranchCoverage(); // read body. | |
| if (body.is_open()) { | |
| // We allocated a fresh context before the loop which contains captured | |
| // [ForStatement] variables. Before jumping back to the loop entry we clone | |
| // the context object (at same depth) which ensures the next iteration of | |
| // the body gets a fresh set of [ForStatement] variables (with the old | |
| // (possibly updated) values). | |
| if (context_scope->num_context_variables() > 0) { | |
| body += CloneContext(context_scope->context_slots()); | |
| } | |
| body += updates; | |
| JoinEntryInstr* join = BuildJoinEntry(); | |
| declarations += Goto(join); | |
| body += Goto(join); | |
| Fragment loop(join); | |
| loop += CheckStackOverflow(pos); // may have non-empty stack | |
| if (condition.entry != nullptr) { | |
| loop <<= condition.entry; | |
| } else { | |
| loop += Goto(body_entry->AsJoinEntry()); | |
| } | |
| } else { | |
| if (condition.entry != nullptr) { | |
| declarations <<= condition.entry; | |
| } else { | |
| declarations += Goto(body_entry->AsJoinEntry()); | |
| } | |
| } | |
| Fragment loop(declarations.entry, loop_exit); | |
| SkipScope(); | |
| loop += ExitScope(offset); | |
| loop_depth_dec(); | |
| return loop; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildSwitchStatement( | |
| TokenPosition* position) { | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| const bool is_exhaustive = ReadBool(); // read exhaustive flag. | |
| // We need the number of cases. So start by getting that, then go back. | |
| const intptr_t offset = ReaderOffset(); | |
| SkipExpression(); // temporarily skip condition | |
| SkipOptionalDartType(); // temporarily skip expression type | |
| intptr_t case_count = ReadListLength(); // read number of cases. | |
| SetOffset(offset); | |
| SwitchBlock block(flow_graph_builder_, case_count); | |
| Fragment instructions = BuildExpression(); // read condition. | |
| const AbstractType* expression_type = &Object::dynamic_type(); | |
| if (ReadTag() == kSomething) { | |
| expression_type = &T.BuildType(); // read expression type. | |
| } | |
| instructions += | |
| StoreLocal(TokenPosition::kNoSource, scopes()->switch_variable); | |
| instructions += Drop(); | |
| case_count = ReadListLength(); // read number of cases. | |
| SwitchHelper helper(Z, pos, is_exhaustive, *expression_type, &block, | |
| case_count); | |
| // Build the case bodies and collect the expressions into the helper | |
| // for the next step. | |
| for (intptr_t i = 0; i < case_count; ++i) { | |
| helper.AddCaseBody(BuildSwitchCase(&helper, i)); | |
| } | |
| // Build the code to dispatch to the case bodies. | |
| switch (helper.SelectDispatchStrategy()) { | |
| case kSwitchDispatchAuto: | |
| UNREACHABLE(); | |
| case kSwitchDispatchLinearScan: | |
| instructions += BuildLinearScanSwitch(&helper); | |
| break; | |
| case kSwitchDispatchBinarySearch: | |
| instructions += BuildBinarySearchSwitch(&helper); | |
| break; | |
| case kSwitchDispatchJumpTable: | |
| instructions += BuildJumpTableSwitch(&helper); | |
| break; | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildSwitchCase(SwitchHelper* helper, | |
| intptr_t case_index) { | |
| // Generate case body and try to find out whether the body will be target | |
| // of a jump due to: | |
| // * `continue case_label` | |
| // * `case e1: case e2: body` | |
| // | |
| // Also collect switch expressions into helper. | |
| ReadPosition(); // read file offset. | |
| const int expression_count = ReadListLength(); // read number of expressions. | |
| for (intptr_t j = 0; j < expression_count; ++j) { | |
| const TokenPosition pos = ReadPosition(); // read jth position. | |
| // read jth expression. | |
| const Instance& value = | |
| Instance::ZoneHandle(Z, constant_reader_.ReadConstantExpression()); | |
| helper->AddExpression(case_index, pos, value); | |
| } | |
| const bool is_default = ReadBool(); // read is_default. | |
| if (is_default) helper->set_default_case(case_index); | |
| Fragment body_fragment = BuildStatementWithBranchCoverage(); // read body. | |
| if (body_fragment.entry == nullptr) { | |
| // Make a NOP in order to ensure linking works properly. | |
| body_fragment = NullConstant(); | |
| body_fragment += Drop(); | |
| } | |
| if (!is_default && body_fragment.is_open() && | |
| (case_index < (helper->case_count() - 1))) { | |
| body_fragment += B->Stop("Unreachable end of case"); | |
| } | |
| // If there is an implicit fall-through we have one [SwitchCase] and | |
| // multiple expressions, e.g. | |
| // | |
| // switch(expr) { | |
| // case a: | |
| // case b: | |
| // <stmt-body> | |
| // } | |
| // | |
| // This means that the <stmt-body> will have more than 1 incoming edge (one | |
| // from `a == expr` and one from `a != expr && b == expr`). The | |
| // `block.Destination()` records the additional jump. | |
| if (expression_count > 1) { | |
| helper->switch_block()->DestinationDirect(case_index); | |
| } | |
| return body_fragment; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildLinearScanSwitch( | |
| SwitchHelper* helper) { | |
| // Build a switch using a sequence of equality tests. | |
| // | |
| // From a test: | |
| // * jump directly to a body, if there is no jumper. | |
| // * jump to a wrapper block which jumps to the body, if there is a jumper. | |
| SwitchBlock* block = helper->switch_block(); | |
| const intptr_t case_count = helper->case_count(); | |
| const intptr_t default_case = helper->default_case(); | |
| const GrowableArray<Fragment>& case_bodies = helper->case_bodies(); | |
| Fragment current_instructions; | |
| intptr_t expression_index = 0; | |
| for (intptr_t i = 0; i < case_count; ++i) { | |
| if (i == default_case) { | |
| ASSERT(i == (case_count - 1)); | |
| if (block->HadJumper(i)) { | |
| // There are several branches to the body, so we will make a goto to | |
| // the join block (and prepend a join instruction to the real body). | |
| JoinEntryInstr* join = block->DestinationDirect(i); | |
| current_instructions += Goto(join); | |
| current_instructions = Fragment(current_instructions.entry, join); | |
| current_instructions += case_bodies[i]; | |
| } else { | |
| current_instructions += case_bodies[i]; | |
| } | |
| } else { | |
| JoinEntryInstr* body_join = nullptr; | |
| if (block->HadJumper(i)) { | |
| body_join = block->DestinationDirect(i); | |
| case_bodies[i] = Fragment(body_join) + case_bodies[i]; | |
| } | |
| const intptr_t expression_count = helper->case_expression_counts().At(i); | |
| for (intptr_t j = 0; j < expression_count; ++j) { | |
| TargetEntryInstr* then; | |
| TargetEntryInstr* otherwise; | |
| const SwitchExpression& expression = | |
| helper->expressions().At(expression_index++); | |
| current_instructions += Constant(expression.value()); | |
| current_instructions += LoadLocal(scopes()->switch_variable); | |
| current_instructions += InstanceCall( | |
| expression.position(), Symbols::EqualOperator(), Token::kEQ, | |
| /*argument_count=*/2, | |
| /*checked_argument_count=*/2); | |
| current_instructions += BranchIfTrue(&then, &otherwise, false); | |
| Fragment then_fragment(then); | |
| if (body_join != nullptr) { | |
| // There are several branches to the body, so we will make a goto to | |
| // the join block (the real body has already been prepended with a | |
| // join instruction). | |
| then_fragment += Goto(body_join); | |
| } else { | |
| // There is only a single branch to the body, so we will just append | |
| // the body fragment. | |
| then_fragment += case_bodies[i]; | |
| } | |
| current_instructions = Fragment(current_instructions.entry, otherwise); | |
| } | |
| } | |
| } | |
| if (case_count > 0 && !helper->has_default()) { | |
| // There is no default, which means we have an open [current_instructions] | |
| // (which is a [TargetEntryInstruction] for the last "otherwise" branch). | |
| // | |
| // Furthermore the last [SwitchCase] can be open as well. If so, we need | |
| // to join these two. | |
| Fragment& last_body = case_bodies[case_count - 1]; | |
| if (last_body.is_open()) { | |
| ASSERT(current_instructions.is_open()); | |
| ASSERT(current_instructions.current->IsTargetEntry()); | |
| // Join the last "otherwise" branch and the last [SwitchCase] fragment. | |
| JoinEntryInstr* join = BuildJoinEntry(); | |
| current_instructions += Goto(join); | |
| last_body += Goto(join); | |
| current_instructions = Fragment(current_instructions.entry, join); | |
| } | |
| } else { | |
| // All non-default cases will be closed (i.e. break/continue/throw/return) | |
| // So it is fine to just let more statements after the switch append to the | |
| // default case. | |
| } | |
| return current_instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildOptimizedSwitchPrelude( | |
| SwitchHelper* helper, | |
| JoinEntryInstr* join) { | |
| const TokenPosition pos = helper->position(); | |
| Fragment instructions; | |
| if (helper->is_enum_switch()) { | |
| // For an enum switch, we need to load the enum index from the switch | |
| // variable. | |
| instructions += LoadLocal(scopes()->switch_variable); | |
| const Field& enum_index_field = | |
| Field::ZoneHandle(Z, IG->object_store()->enum_index_field()); | |
| instructions += B->LoadField(enum_index_field, /*calls_initializer=*/false); | |
| instructions += StoreLocal(pos, scopes()->switch_variable); | |
| instructions += Drop(); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildBinarySearchSwitch( | |
| SwitchHelper* helper) { | |
| // * We build a binary tree of conditional branches where each branch bisects | |
| // the remaining cases. | |
| // * At holes in the switch expression range we need to add additional | |
| // bound checks. | |
| // * At each leaf we add the body of the case or a goto, if the case has | |
| // jumpers. | |
| // * Leafs at the bounds of the switch expression range might need to | |
| // do a bound check. | |
| SwitchBlock* block = helper->switch_block(); | |
| const intptr_t case_count = helper->case_count(); | |
| const intptr_t default_case = helper->default_case(); | |
| const GrowableArray<Fragment>& case_bodies = helper->case_bodies(); | |
| const intptr_t expression_count = helper->expressions().length(); | |
| const GrowableArray<SwitchExpression*>& sorted_expressions = | |
| helper->sorted_expressions(); | |
| TargetEntryInstr* then_entry; | |
| TargetEntryInstr* otherwise_entry; | |
| // Entry to the default case or the exit of the switch, if there is no | |
| // default case. | |
| JoinEntryInstr* join; | |
| if (helper->has_default()) { | |
| join = block->DestinationDirect(default_case); | |
| } else { | |
| join = BuildJoinEntry(); | |
| } | |
| Fragment join_instructions(join); | |
| if (helper->has_default()) { | |
| join_instructions += case_bodies.At(default_case); | |
| } | |
| Fragment current_instructions = BuildOptimizedSwitchPrelude(helper, join); | |
| GrowableArray<SwitchRange> stack; | |
| stack.Add(SwitchRange::Branch(0, expression_count - 1, current_instructions)); | |
| while (!stack.is_empty()) { | |
| const SwitchRange range = stack.RemoveLast(); | |
| Fragment branch_instructions = range.branch_instructions(); | |
| if (range.is_leaf()) { | |
| const intptr_t expression_index = range.min(); | |
| const SwitchExpression& expression = | |
| *sorted_expressions.At(expression_index); | |
| if (!range.is_bounds_checked() && | |
| ((helper->RequiresLowerBoundCheck() && expression_index == 0) || | |
| (helper->RequiresUpperBoundCheck() && | |
| expression_index == expression_count - 1))) { | |
| // This leaf needs a bound check. | |
| branch_instructions += LoadLocal(scopes()->switch_variable); | |
| branch_instructions += Constant(expression.integer()); | |
| branch_instructions += | |
| StrictCompare(expression.position(), Token::kEQ_STRICT, | |
| /*number_check=*/true); | |
| branch_instructions += | |
| BranchIfTrue(&then_entry, &otherwise_entry, /*negate=*/false); | |
| Fragment otherwise_instructions(otherwise_entry); | |
| otherwise_instructions += Goto(join); | |
| stack.Add(SwitchRange::Leaf(expression_index, Fragment(then_entry), | |
| /*is_bounds_checked=*/true)); | |
| } else { | |
| // We are at a leaf where we can add the body of the case or a goto to | |
| // [join]. | |
| const intptr_t case_index = expression.case_index(); | |
| if (case_index == default_case) { | |
| branch_instructions += Goto(join); | |
| } else { | |
| if (block->HadJumper(case_index)) { | |
| JoinEntryInstr* join = block->DestinationDirect(case_index); | |
| branch_instructions += Goto(join); | |
| if (join->next() == nullptr) { | |
| // The first time we reach an expression that jumps to a case | |
| // body we emit the body. | |
| branch_instructions = Fragment(join); | |
| branch_instructions += case_bodies.At(case_index); | |
| } | |
| } else { | |
| branch_instructions += case_bodies.At(case_index); | |
| } | |
| if (!helper->has_default() && case_index == case_count - 1) { | |
| if (branch_instructions.is_open()) { | |
| branch_instructions += Goto(join); | |
| } | |
| } | |
| } | |
| ASSERT(branch_instructions.is_closed()); | |
| } | |
| } else { | |
| // Add a conditional to bisect the range. | |
| const intptr_t middle = range.min() + (range.max() - range.min()) / 2; | |
| const intptr_t next = middle + 1; | |
| const SwitchExpression& middle_expression = | |
| *sorted_expressions.At(middle); | |
| const SwitchExpression& next_expression = *sorted_expressions.At(next); | |
| branch_instructions += LoadLocal(scopes()->switch_variable); | |
| branch_instructions += Constant(middle_expression.integer()); | |
| branch_instructions += | |
| B->IntRelationalOp(middle_expression.position(), Token::kLTE); | |
| branch_instructions += | |
| BranchIfTrue(&then_entry, &otherwise_entry, /*negate=*/false); | |
| Fragment lower_branch_instructions(then_entry); | |
| Fragment upper_branch_instructions(otherwise_entry); | |
| if (next_expression.integer().Value() > | |
| middle_expression.integer().Value() + 1) { | |
| // The upper branch is not contiguous with the lower branch. | |
| // Before continuing in the upper branch we add a bound check. | |
| upper_branch_instructions += LoadLocal(scopes()->switch_variable); | |
| upper_branch_instructions += Constant(next_expression.integer()); | |
| upper_branch_instructions += | |
| B->IntRelationalOp(next_expression.position(), Token::kGTE); | |
| upper_branch_instructions += | |
| BranchIfTrue(&then_entry, &otherwise_entry, /*negate=*/false); | |
| Fragment otherwise_instructions(otherwise_entry); | |
| otherwise_instructions += Goto(join); | |
| upper_branch_instructions = Fragment(then_entry); | |
| } | |
| stack.Add( | |
| SwitchRange::Branch(next, range.max(), upper_branch_instructions)); | |
| stack.Add( | |
| SwitchRange::Branch(range.min(), middle, lower_branch_instructions)); | |
| } | |
| if (current_instructions.is_empty()) { | |
| current_instructions = branch_instructions; | |
| } | |
| } | |
| return Fragment(current_instructions.entry, join_instructions.current); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildJumpTableSwitch(SwitchHelper* helper) { | |
| // * If input value is not integer or enum value, goto default case or | |
| // switch exit. | |
| // * If value is enum value, load its index. | |
| // * If input integer is outside of jump table range, goto default case | |
| // or switch exit. | |
| // * Jump to case with jump table. | |
| // * For each expression, add entry to jump to case. | |
| // * For each hole in the integer range, add entry to jump to default | |
| // cause or switch exit. | |
| SwitchBlock* block = helper->switch_block(); | |
| const TokenPosition pos = helper->position(); | |
| const intptr_t case_count = helper->case_count(); | |
| const intptr_t default_case = helper->default_case(); | |
| const GrowableArray<Fragment>& case_bodies = helper->case_bodies(); | |
| const Integer& expression_min = helper->expression_min(); | |
| const Integer& expression_max = helper->expression_max(); | |
| TargetEntryInstr* then_entry; | |
| TargetEntryInstr* otherwise_entry; | |
| // Entry to the default case or the exit of the switch, if there is no | |
| // default case. | |
| JoinEntryInstr* join; | |
| if (helper->has_default()) { | |
| join = block->DestinationDirect(default_case); | |
| } else { | |
| join = BuildJoinEntry(); | |
| } | |
| Fragment join_instructions(join); | |
| Fragment current_instructions = BuildOptimizedSwitchPrelude(helper, join); | |
| if (helper->RequiresLowerBoundCheck()) { | |
| current_instructions += LoadLocal(scopes()->switch_variable); | |
| current_instructions += Constant(expression_min); | |
| current_instructions += B->IntRelationalOp(pos, Token::kGTE); | |
| current_instructions += BranchIfTrue(&then_entry, &otherwise_entry, | |
| /*negate=*/false); | |
| Fragment otherwise_instructions(otherwise_entry); | |
| otherwise_instructions += Goto(join); | |
| current_instructions = Fragment(current_instructions.entry, then_entry); | |
| } | |
| if (helper->RequiresUpperBoundCheck()) { | |
| current_instructions += LoadLocal(scopes()->switch_variable); | |
| current_instructions += Constant(expression_max); | |
| current_instructions += B->IntRelationalOp(pos, Token::kLTE); | |
| current_instructions += BranchIfTrue(&then_entry, &otherwise_entry, | |
| /*negate=*/false); | |
| Fragment otherwise_instructions(otherwise_entry); | |
| otherwise_instructions += Goto(join); | |
| current_instructions = Fragment(current_instructions.entry, then_entry); | |
| } | |
| current_instructions += LoadLocal(scopes()->switch_variable); | |
| if (expression_min.Value() != 0) { | |
| // Adjust for the range of the jump table, which starts at 0. | |
| current_instructions += Constant(expression_min); | |
| current_instructions += | |
| InstanceCall(pos, Symbols::Minus(), Token::kSUB, /*argument_count=*/2, | |
| /*checked_argument_count=*/2); | |
| } | |
| const intptr_t table_size = helper->ExpressionRange(); | |
| IndirectGotoInstr* indirect_goto = IndirectGoto(table_size); | |
| current_instructions <<= indirect_goto; | |
| current_instructions = current_instructions.closed(); | |
| GrowableArray<TargetEntryInstr*> table_entries(table_size); | |
| table_entries.FillWith(nullptr, 0, table_size); | |
| // Generate the jump table entries for the switch cases. | |
| intptr_t expression_index = 0; | |
| for (intptr_t i = 0; i < case_count; ++i) { | |
| const int expression_count = helper->case_expression_counts().At(i); | |
| // Generate jump table entries for each case expression. | |
| if (i != default_case) { | |
| for (intptr_t j = 0; j < expression_count; ++j) { | |
| const SwitchExpression& expression = | |
| helper->expressions().At(expression_index++); | |
| const intptr_t table_offset = | |
| expression.integer().Value() - expression_min.Value(); | |
| IndirectEntryInstr* indirect_entry = | |
| B->BuildIndirectEntry(table_offset, CurrentTryIndex()); | |
| Fragment indirect_entry_instructions(indirect_entry); | |
| indirect_entry_instructions += Goto(block->DestinationDirect(i)); | |
| TargetEntryInstr* entry = B->BuildTargetEntry(); | |
| Fragment entry_instructions(entry); | |
| entry_instructions += Goto(indirect_entry); | |
| table_entries[table_offset] = entry; | |
| } | |
| } | |
| // Connect the case body to its join entry. | |
| if (i == default_case) { | |
| join_instructions += case_bodies.At(i); | |
| } else { | |
| Fragment case_instructions(block->DestinationDirect(i)); | |
| case_instructions += case_bodies.At(i); | |
| if (i == case_count - 1) { | |
| // If the last case is not the default case and it is still open | |
| // close it by going to the exit of the switch. | |
| if (case_instructions.is_open()) { | |
| case_instructions += Goto(join); | |
| } | |
| } | |
| ASSERT(case_instructions.is_closed()); | |
| } | |
| } | |
| // Generate the jump table entries for holes in the integer range. | |
| for (intptr_t i = 0; i < table_size; i++) { | |
| if (table_entries.At(i) == nullptr) { | |
| IndirectEntryInstr* indirect_entry = | |
| B->BuildIndirectEntry(i, CurrentTryIndex()); | |
| Fragment indirect_entry_instructions(indirect_entry); | |
| indirect_entry_instructions += Goto(join); | |
| TargetEntryInstr* entry = flow_graph_builder_->BuildTargetEntry(); | |
| Fragment entry_instructions(entry); | |
| entry_instructions += Goto(indirect_entry); | |
| table_entries[i] = entry; | |
| } | |
| } | |
| // Add the jump table entries to the jump table. | |
| for (intptr_t i = 0; i < table_size; i++) { | |
| indirect_goto->AddSuccessor(table_entries.At(i)); | |
| } | |
| return Fragment(current_instructions.entry, join_instructions.current); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildContinueSwitchStatement( | |
| TokenPosition* position) { | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| intptr_t target_index = ReadUInt(); // read target index. | |
| TryFinallyBlock* outer_finally = nullptr; | |
| intptr_t target_context_depth = -1; | |
| JoinEntryInstr* entry = switch_block()->Destination( | |
| target_index, &outer_finally, &target_context_depth); | |
| Fragment instructions; | |
| instructions += | |
| TranslateFinallyFinalizers(outer_finally, target_context_depth); | |
| if (instructions.is_open()) { | |
| if (NeedsDebugStepCheck(parsed_function()->function(), pos)) { | |
| instructions += DebugStepCheck(pos); | |
| } | |
| instructions += Goto(entry); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildIfStatement(TokenPosition* position) { | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| TestFragment condition = TranslateConditionForControl(); | |
| Fragment then_fragment(condition.CreateTrueSuccessor(flow_graph_builder_)); | |
| then_fragment += BuildStatementWithBranchCoverage(); // read then. | |
| Fragment otherwise_fragment( | |
| condition.CreateFalseSuccessor(flow_graph_builder_)); | |
| otherwise_fragment += BuildStatementWithBranchCoverage(); // read otherwise. | |
| if (then_fragment.is_open()) { | |
| if (otherwise_fragment.is_open()) { | |
| JoinEntryInstr* join = BuildJoinEntry(); | |
| then_fragment += Goto(join); | |
| otherwise_fragment += Goto(join); | |
| return Fragment(condition.entry, join); | |
| } else { | |
| return Fragment(condition.entry, then_fragment.current); | |
| } | |
| } else if (otherwise_fragment.is_open()) { | |
| return Fragment(condition.entry, otherwise_fragment.current); | |
| } else { | |
| return Fragment(condition.entry, nullptr); | |
| } | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildReturnStatement( | |
| TokenPosition* position) { | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| Tag tag = ReadTag(); // read first part of expression. | |
| bool inside_try_finally = try_finally_block() != nullptr; | |
| Fragment instructions; | |
| if (parsed_function()->function().IsSyncGenerator()) { | |
| // Return false from sync* function to indicate the end of iteration. | |
| instructions += Constant(Bool::False()); | |
| if (tag != kNothing) { | |
| ASSERT(PeekTag() == kNullLiteral); | |
| SkipExpression(); | |
| } | |
| } else { | |
| instructions += | |
| (tag == kNothing ? NullConstant() | |
| : BuildExpression()); // read rest of expression. | |
| } | |
| if (instructions.is_open()) { | |
| if (inside_try_finally) { | |
| LocalVariable* const finally_return_variable = | |
| scopes()->finally_return_variable; | |
| ASSERT(finally_return_variable != nullptr); | |
| const Function& function = parsed_function()->function(); | |
| if (NeedsDebugStepCheck(function, pos)) { | |
| instructions += DebugStepCheck(pos); | |
| } | |
| instructions += StoreLocal(pos, finally_return_variable); | |
| instructions += Drop(); | |
| const intptr_t target_context_depth = | |
| finally_return_variable->is_captured() | |
| ? finally_return_variable->owner()->context_level() | |
| : -1; | |
| instructions += TranslateFinallyFinalizers(nullptr, target_context_depth); | |
| if (instructions.is_open()) { | |
| const intptr_t saved_context_depth = B->context_depth_; | |
| if (finally_return_variable->is_captured()) { | |
| B->context_depth_ = target_context_depth; | |
| } | |
| instructions += LoadLocal(finally_return_variable); | |
| instructions += Return(TokenPosition::kNoSource); | |
| B->context_depth_ = saved_context_depth; | |
| } | |
| } else { | |
| instructions += Return(pos); | |
| } | |
| } else { | |
| Pop(); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildTryCatch(TokenPosition* position) { | |
| ASSERT(block_expression_depth() == 0); // no try-catch in block-expr | |
| InlineBailout("kernel::FlowgraphBuilder::VisitTryCatch"); | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| // [try entry, try_body=B1, catch=B3] | |
| // B1: [try body, try_index = <n>] | |
| // goto B2 | |
| // B3: [catch entry, try_handler_index = <n>] | |
| // goto B2 | |
| // B2: [join] | |
| // ... | |
| intptr_t try_handler_index = AllocateTryIndex(); | |
| Fragment try_body = TryEntry(try_handler_index); | |
| JoinEntryInstr* after_try = BuildJoinEntry(); | |
| // Fill in the body of the try. | |
| try_depth_inc(); | |
| { | |
| TryCatchBlock block(flow_graph_builder_, try_handler_index); | |
| try_body += BuildStatementWithBranchCoverage(position); // read body. | |
| try_body += Goto(after_try); | |
| } | |
| try_depth_dec(); | |
| const int kNeedsStracktraceBit = 1 << 0; | |
| const int kIsSyntheticBit = 1 << 1; | |
| uint8_t flags = ReadByte(); | |
| bool needs_stacktrace = | |
| (flags & kNeedsStracktraceBit) == kNeedsStracktraceBit; | |
| bool is_synthetic = (flags & kIsSyntheticBit) == kIsSyntheticBit; | |
| catch_depth_inc(); | |
| intptr_t catch_count = ReadListLength(); // read number of catches. | |
| const Array& handler_types = | |
| Array::ZoneHandle(Z, Array::New(catch_count, Heap::kOld)); | |
| Fragment catch_body = CatchBlockEntry(handler_types, try_handler_index, | |
| needs_stacktrace, is_synthetic); | |
| // Fill in the body of the catch. | |
| for (intptr_t i = 0; i < catch_count; ++i) { | |
| intptr_t catch_offset = ReaderOffset(); // Catch has no tag. | |
| TokenPosition pos = ReadPosition(); // read position. | |
| ReadUInt(); // read scope size. | |
| const AbstractType& type_guard = T.BuildType(); // read guard. | |
| handler_types.SetAt(i, type_guard); | |
| Fragment catch_handler_body = EnterScope(catch_offset); | |
| Tag tag = ReadTag(); // read first part of exception. | |
| if (tag == kSomething) { | |
| catch_handler_body += LoadLocal(CurrentException()); | |
| catch_handler_body += | |
| StoreLocal(TokenPosition::kNoSource, | |
| LookupVariable(ReaderOffset() + data_program_offset_)); | |
| catch_handler_body += Drop(); | |
| SkipVariable(); // read exception. | |
| } | |
| tag = ReadTag(); // read first part of stack trace. | |
| if (tag == kSomething) { | |
| catch_handler_body += LoadLocal(CurrentStackTrace()); | |
| catch_handler_body += | |
| StoreLocal(TokenPosition::kNoSource, | |
| LookupVariable(ReaderOffset() + data_program_offset_)); | |
| catch_handler_body += Drop(); | |
| SkipVariable(); // read stack trace. | |
| } | |
| { | |
| CatchBlock block(flow_graph_builder_, CurrentException(), | |
| CurrentStackTrace(), try_handler_index); | |
| catch_handler_body += BuildStatementWithBranchCoverage(); // read body. | |
| SkipScope(); | |
| // Note: ExitScope adjusts context_depth_ so even if catch_handler_body | |
| // is closed we still need to execute ExitScope for its side effect. | |
| catch_handler_body += ExitScope(catch_offset); | |
| if (catch_handler_body.is_open()) { | |
| catch_handler_body += Goto(after_try); | |
| } | |
| } | |
| if (!type_guard.IsCatchAllType()) { | |
| catch_body += LoadLocal(CurrentException()); | |
| if (!type_guard.IsInstantiated(kCurrentClass)) { | |
| catch_body += LoadInstantiatorTypeArguments(); | |
| } else { | |
| catch_body += NullConstant(); | |
| } | |
| if (!type_guard.IsInstantiated(kFunctions)) { | |
| catch_body += LoadFunctionTypeArguments(); | |
| } else { | |
| catch_body += NullConstant(); | |
| } | |
| catch_body += Constant(type_guard); | |
| catch_body += | |
| InstanceCall(pos, Library::PrivateCoreLibName(Symbols::_instanceOf()), | |
| Token::kIS, 4); | |
| TargetEntryInstr* catch_entry; | |
| TargetEntryInstr* next_catch_entry; | |
| catch_body += BranchIfTrue(&catch_entry, &next_catch_entry, false); | |
| Fragment(catch_entry) + catch_handler_body; | |
| catch_body = Fragment(next_catch_entry); | |
| } else { | |
| catch_body += catch_handler_body; | |
| } | |
| } | |
| // In case the last catch body was not handling the exception and branching to | |
| // after the try block, we will rethrow the exception (i.e. no default catch | |
| // handler). | |
| if (catch_body.is_open()) { | |
| catch_body += LoadLocal(CurrentException()); | |
| catch_body += LoadLocal(CurrentStackTrace()); | |
| catch_body += RethrowException(TokenPosition::kNoSource, try_handler_index); | |
| Drop(); | |
| } | |
| catch_depth_dec(); | |
| return Fragment(try_body.entry, after_try); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildTryFinally(TokenPosition* position) { | |
| // Note on streaming: | |
| // We only stream this TryFinally if we can stream everything inside it, | |
| // so creating a "TryFinallyBlock" with a kernel binary offset instead of an | |
| // AST node isn't a problem. | |
| InlineBailout("kernel::FlowgraphBuilder::VisitTryFinally"); | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| // There are 5 different cases where we need to execute the finally block: | |
| // | |
| // a) 1/2/3th case: Special control flow going out of `node->body()`: | |
| // | |
| // * [BreakStatement] transfers control to a [LabeledStatement] | |
| // * [ContinueSwitchStatement] transfers control to a [SwitchCase] | |
| // * [ReturnStatement] returns a value | |
| // | |
| // => All three cases will automatically append all finally blocks | |
| // between the branching point and the destination (so we don't need to | |
| // do anything here). | |
| // | |
| // b) 4th case: Translating the body resulted in an open fragment (i.e. body | |
| // executes without any control flow out of it) | |
| // | |
| // => We are responsible for jumping out of the body to a new block (with | |
| // different try index) and execute the finalizer. | |
| // | |
| // c) 5th case: An exception occurred inside the body. | |
| // | |
| // => We are responsible for catching it, executing the finally block and | |
| // rethrowing the exception. | |
| intptr_t try_handler_index = AllocateTryIndex(); | |
| Fragment try_body = TryEntry(try_handler_index); | |
| JoinEntryInstr* after_try = BuildJoinEntry(); | |
| intptr_t offset = ReaderOffset(); | |
| SkipStatement(); // temporarily read body. | |
| intptr_t finalizer_offset = ReaderOffset(); | |
| SetOffset(offset); | |
| // Fill in the body of the try. | |
| try_depth_inc(); | |
| { | |
| TryFinallyBlock tfb(flow_graph_builder_, finalizer_offset); | |
| TryCatchBlock tcb(flow_graph_builder_, try_handler_index); | |
| try_body += BuildStatementWithBranchCoverage(position); // read body. | |
| } | |
| try_depth_dec(); | |
| if (try_body.is_open()) { | |
| // Please note: The try index will be on level out of this block, | |
| // thereby ensuring if there's an exception in the finally block we | |
| // won't run it twice. | |
| JoinEntryInstr* finally_entry = BuildJoinEntry(); | |
| try_body += Goto(finally_entry); | |
| Fragment finally_body(finally_entry); | |
| finally_body += BuildStatementWithBranchCoverage(); // read finalizer. | |
| finally_body += Goto(after_try); | |
| } | |
| // Fill in the body of the catch. | |
| catch_depth_inc(); | |
| const Array& handler_types = Array::ZoneHandle(Z, Array::New(1, Heap::kOld)); | |
| handler_types.SetAt(0, Object::dynamic_type()); | |
| // Note: rethrow will actually force mark the handler as needing a stacktrace. | |
| Fragment finally_body = CatchBlockEntry(handler_types, try_handler_index, | |
| /* needs_stacktrace = */ false, | |
| /* is_synthesized = */ true); | |
| SetOffset(finalizer_offset); | |
| // Try/finally might occur in control flow collections with non-empty | |
| // expression stack (via desugaring of 'await for'). Note that catch-block | |
| // generated for finally always throws so there is no merge. | |
| // Save and reset expression stack around catch body in order to maintain | |
| // correct stack depth, as catch entry drops expression stack. | |
| Value* const saved_stack_top = stack(); | |
| set_stack(nullptr); | |
| finally_body += BuildStatementWithBranchCoverage(); // read finalizer | |
| if (finally_body.is_open()) { | |
| finally_body += LoadLocal(CurrentException()); | |
| finally_body += LoadLocal(CurrentStackTrace()); | |
| finally_body += | |
| RethrowException(TokenPosition::kNoSource, try_handler_index); | |
| Drop(); | |
| } | |
| ASSERT(stack() == nullptr); | |
| set_stack(saved_stack_top); | |
| catch_depth_dec(); | |
| return Fragment(try_body.entry, after_try); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildYieldStatement( | |
| TokenPosition* position) { | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| const uint8_t flags = ReadByte(); // read flags. | |
| Fragment instructions; | |
| const bool is_yield_star = (flags & kYieldStatementFlagYieldStar) != 0; | |
| // Load :suspend_state variable using low-level FP-relative load | |
| // in order to avoid confusing SSA construction (which cannot | |
| // track its value as it is modified implicitly by stubs). | |
| LocalVariable* suspend_state = parsed_function()->suspend_state_var(); | |
| ASSERT(suspend_state != nullptr); | |
| instructions += IntConstant(0); | |
| instructions += B->LoadFpRelativeSlot( | |
| compiler::target::frame_layout.FrameSlotForVariable(suspend_state) * | |
| compiler::target::kWordSize, | |
| CompileType::Dynamic(), kTagged); | |
| instructions += LoadNativeField(Slot::SuspendState_function_data()); | |
| instructions += BuildExpression(); // read expression. | |
| if (NeedsDebugStepCheck(parsed_function()->function(), pos)) { | |
| instructions += DebugStepCheck(pos); | |
| } | |
| if (parsed_function()->function().IsAsyncGenerator()) { | |
| // In the async* functions, generate the following code for yield <expr>: | |
| // | |
| // _AsyncStarStreamController controller = :suspend_state._functionData; | |
| // if (controller.add(<expr>)) { | |
| // return; | |
| // } | |
| // if (suspend()) { | |
| // return; | |
| // } | |
| // | |
| // Generate the following code for yield* <expr>: | |
| // | |
| // _AsyncStarStreamController controller = :suspend_state._functionData; | |
| // if (controller.addStream(<expr>)) { | |
| // return; | |
| // } | |
| // if (suspend()) { | |
| // return; | |
| // } | |
| // | |
| auto& add_method = Function::ZoneHandle(Z); | |
| if (is_yield_star) { | |
| add_method = | |
| IG->object_store()->async_star_stream_controller_add_stream(); | |
| } else { | |
| add_method = IG->object_store()->async_star_stream_controller_add(); | |
| } | |
| instructions += | |
| StaticCall(TokenPosition::kNoSource, add_method, 2, ICData::kNoRebind); | |
| TargetEntryInstr *return1, *continue1; | |
| instructions += BranchIfTrue(&return1, &continue1, false); | |
| JoinEntryInstr* return_join = BuildJoinEntry(); | |
| Fragment(return1) + Goto(return_join); | |
| instructions = Fragment(instructions.entry, continue1); | |
| // Suspend and test value passed to the resumed async* body. | |
| instructions += NullConstant(); | |
| instructions += B->Suspend(pos, SuspendInstr::StubId::kYieldAsyncStar); | |
| TargetEntryInstr *return2, *continue2; | |
| instructions += BranchIfTrue(&return2, &continue2, false); | |
| Fragment(return2) + Goto(return_join); | |
| instructions = Fragment(instructions.entry, continue2); | |
| Fragment do_return(return_join); | |
| do_return += TranslateFinallyFinalizers(nullptr, -1); | |
| do_return += NullConstant(); | |
| do_return += Return(TokenPosition::kNoSource); | |
| } else if (parsed_function()->function().IsSyncGenerator()) { | |
| // In the sync* functions, generate the following code for yield <expr>: | |
| // | |
| // _SyncStarIterator iterator = :suspend_state._functionData; | |
| // iterator._current = <expr>; | |
| // suspend(); | |
| // | |
| // Generate the following code for yield* <expr>: | |
| // | |
| // _SyncStarIterator iterator = :suspend_state._functionData; | |
| // iterator._yieldStarIterable = <expr>; | |
| // suspend(); | |
| // | |
| auto& field = Field::ZoneHandle(Z); | |
| if (is_yield_star) { | |
| field = IG->object_store()->sync_star_iterator_yield_star_iterable(); | |
| } else { | |
| field = IG->object_store()->sync_star_iterator_current(); | |
| } | |
| instructions += B->StoreFieldGuarded(field); | |
| instructions += B->Constant(Bool::True()); | |
| instructions += | |
| B->Suspend(pos, SuspendInstr::StubId::kSuspendSyncStarAtYield); | |
| instructions += Drop(); | |
| } else { | |
| UNREACHABLE(); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildVariableStatement( | |
| TokenPosition* position) { | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| return BuildVariableDeclaration(position); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildVariableDeclaration( | |
| TokenPosition* position) { | |
| const Tag tag = ReadTag(); // read tag. | |
| ASSERT(tag == kVariableDeclaration); | |
| const TokenPosition pos = ReadPosition(); // read position. | |
| if (position != nullptr) *position = pos; | |
| SkipCapturedContexts(); | |
| return BuildVariable(position); | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildVariable(TokenPosition* position) { | |
| intptr_t kernel_position_no_tag = ReaderOffset() + data_program_offset_; | |
| LocalVariable* variable = LookupVariable(kernel_position_no_tag); | |
| VariableHelper helper(this); | |
| helper.ReadUntilExcluding(VariableHelper::kType); | |
| T.BuildType(); // read type. | |
| bool has_initializer = (ReadTag() != kNothing); | |
| if (position != nullptr) *position = helper.position_; | |
| Fragment instructions; | |
| if (variable->is_late()) { | |
| // TODO(liama): Treat the field as non-late if the initializer is trivial. | |
| if (has_initializer) { | |
| SkipExpression(); | |
| } | |
| instructions += Constant(Object::sentinel()); | |
| } else if (!has_initializer) { | |
| // We may have | |
| // | |
| // final a; // <-- `final` but no initializer! | |
| // ...; | |
| // a = ...; | |
| // | |
| // If the debugger is available one may be able to break in a position after | |
| // the variable was declared but before it was initialized. We'd like to see | |
| // `null` in that case. | |
| // | |
| // `null` is the default for variables | |
| // - non captured variable slots will be set to `null` in function | |
| // prologue | |
| // - captured variable slots will be `null` on context allocation | |
| // | |
| // Though if the variable is inside a loop one would observe in debugger | |
| // not `null` but the last loop iteration's value due to | |
| // - non captured: not clearing variable slots | |
| // - captured: cloning context | |
| // | |
| // => So if we're not in AOT mode we issue an extra null store. | |
| // => That means in JIT mode we'll have multiple stores to the context slot | |
| // of an uninitialized final variable (the initial `null` store plus the | |
| // initializing store(s)). | |
| // | |
| if (FLAG_precompiled_mode && variable->is_final()) { | |
| return instructions; | |
| } | |
| instructions += NullConstant(); | |
| } else { | |
| // Initializer | |
| instructions += BuildExpression(); // read (actual) initializer. | |
| } | |
| // Use position of equal sign if it exists. If the equal sign does not exist | |
| // use the position of the identifier. | |
| const TokenPosition debug_position = helper.equals_position_.IsReal() | |
| ? helper.equals_position_ | |
| : helper.position_; | |
| if (debug_position.IsDebugPause() && !helper.IsHoisted() && | |
| // We always make it possible to add a breakpoint on the equals sign if it | |
| // exists. | |
| (helper.equals_position_.IsReal() || | |
| NeedsDebugStepCheck(stack(), debug_position))) { | |
| instructions = DebugStepCheck(debug_position) + instructions; | |
| } | |
| instructions += StoreLocal(helper.position_, variable); | |
| instructions += Drop(); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildFunctionDeclaration( | |
| TokenPosition* position) { | |
| const intptr_t offset = ReaderOffset() - 1; // Include the tag. | |
| const TokenPosition pos = ReadPosition(); | |
| if (position != nullptr) *position = pos; | |
| const intptr_t variable_offset = ReaderOffset() + data_program_offset_; | |
| SkipVariable(); | |
| const intptr_t local_function_id = ReadUInt(); // read id. | |
| ASSERT(local_function_id > 0); | |
| Fragment instructions = DebugStepCheck(pos); | |
| instructions += BuildFunctionNode(local_function_id, offset); | |
| instructions += StoreLocal(pos, LookupVariable(variable_offset)); | |
| instructions += Drop(); | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildFunctionNode( | |
| intptr_t local_function_id, | |
| intptr_t func_decl_offset) { | |
| const intptr_t func_node_offset = ReaderOffset(); | |
| const auto& member_function = | |
| Function::Handle(Z, parsed_function()->function().GetOutermostFunction()); | |
| const Function& function = Function::ZoneHandle( | |
| Z, KernelLoader::GetClosureFunction( | |
| thread(), local_function_id, func_decl_offset, member_function, | |
| parsed_function()->function(), closure_owner_)); | |
| if (function.context_scope() == ContextScope::null()) { | |
| SafepointWriteRwLocker ml(thread(), | |
| thread()->isolate_group()->program_lock()); | |
| if (function.context_scope() == ContextScope::null()) { | |
| for (intptr_t i = 0; i < scopes()->function_scopes.length(); ++i) { | |
| if (scopes()->function_scopes[i].kernel_offset != | |
| function.kernel_offset()) { | |
| continue; | |
| } | |
| LocalScope* scope = scopes()->function_scopes[i].scope; | |
| const ContextScope& context_scope = ContextScope::Handle( | |
| Z, scope->PreserveOuterScope(function, | |
| flow_graph_builder_->context_depth_)); | |
| function.set_context_scope(context_scope); | |
| } | |
| } | |
| } | |
| ASSERT(function.kernel_offset() == func_node_offset); | |
| SkipFunctionNode(); | |
| const bool has_delayed_type_args = | |
| Closure::HasDelayedTypeArgumentsField(function); | |
| const bool has_instantiator_type_args = | |
| Closure::HasInstantiatorTypeArgumentsField(function); | |
| const bool has_function_type_args = | |
| Closure::HasFunctionTypeArgumentsField(function); | |
| Fragment instructions; | |
| instructions += Constant(function); | |
| if (scopes()->IsClosureWithEmptyContext(func_node_offset)) { | |
| instructions += NullConstant(); | |
| } else { | |
| instructions += LoadLocal(parsed_function()->current_context_var()); | |
| } | |
| instructions += flow_graph_builder_->AllocateClosure( | |
| function.token_pos(), has_delayed_type_args, has_instantiator_type_args, | |
| has_function_type_args, | |
| /*is_tear_off=*/false); | |
| LocalVariable* closure = MakeTemporary(); | |
| // The function signature can have uninstantiated class type parameters. | |
| if (has_instantiator_type_args) { | |
| instructions += LoadLocal(closure); | |
| instructions += LoadInstantiatorTypeArguments(); | |
| instructions += flow_graph_builder_->StoreNativeField( | |
| Slot::GetClosureElementSlot( | |
| thread(), compiler::target::Closure::element_offset( | |
| UntaggedClosure::InstantiatorTypeArgumentsIndex( | |
| has_delayed_type_args))), | |
| StoreFieldInstr::Kind::kInitializing); | |
| } | |
| if (has_function_type_args) { | |
| // TODO(30455): We only need to save these if the closure uses any captured | |
| // type parameters. | |
| instructions += LoadLocal(closure); | |
| instructions += LoadFunctionTypeArguments(); | |
| instructions += flow_graph_builder_->StoreNativeField( | |
| Slot::GetClosureElementSlot( | |
| thread(), | |
| compiler::target::Closure::element_offset( | |
| UntaggedClosure::FunctionTypeArgumentsIndex( | |
| has_delayed_type_args, has_instantiator_type_args))), | |
| StoreFieldInstr::Kind::kInitializing); | |
| } | |
| return instructions; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildExternalEffect() { | |
| const intptr_t argc = ReadUInt(); // Read argument count. | |
| ASSERT(argc == 1); // Native side effect to ignore. | |
| const intptr_t list_length = ReadListLength(); // Read types list length. | |
| ASSERT(list_length == 0); | |
| const intptr_t positional_count = | |
| ReadListLength(); // Read positional argument count. | |
| ASSERT(positional_count == 1); | |
| BuildExpression(); // Consume expression but don't save the fragment. | |
| Pop(); // Restore the stack. | |
| const intptr_t named_args_len = | |
| ReadListLength(); // Skip empty named arguments. | |
| ASSERT(named_args_len == 0); | |
| Fragment code; | |
| code += NullConstant(); // Return type is void. | |
| return code; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildReachabilityFence() { | |
| const intptr_t argc = ReadUInt(); // Read argument count. | |
| ASSERT(argc == 1); // LoadField, can be late. | |
| const intptr_t list_length = ReadListLength(); // Read types list length. | |
| ASSERT(list_length == 0); | |
| const intptr_t positional_count = ReadListLength(); | |
| ASSERT(positional_count == 1); | |
| // The CFE transform only generates a subset of argument expressions: | |
| // either variable get or `this`. However, subsequent transforms can | |
| // generate different expressions, including: constant expressions. | |
| // So, build an arbitrary expression here instead. | |
| TokenPosition* position = nullptr; | |
| Fragment code = BuildExpression(position); | |
| const intptr_t named_args_len = ReadListLength(); | |
| ASSERT(named_args_len == 0); | |
| code <<= new (Z) ReachabilityFenceInstr(Pop()); | |
| code += NullConstant(); // Return type is void. | |
| return code; | |
| } | |
| static void ReportIfNotNull(const char* error) { | |
| if (error != nullptr) { | |
| const auto& language_error = Error::Handle( | |
| LanguageError::New(String::Handle(String::New(error, Heap::kOld)), | |
| Report::kError, Heap::kOld)); | |
| Report::LongJump(language_error); | |
| } | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildLoadStoreAbiSpecificInt( | |
| bool is_store, | |
| bool at_index) { | |
| const intptr_t argument_count = ReadUInt(); // Read argument count. | |
| const intptr_t expected_argument_count = 2 // TypedDataBase, offset | |
| + (at_index ? 1 : 0) // index | |
| + (is_store ? 1 : 0); // value | |
| ASSERT_EQUAL(argument_count, expected_argument_count); | |
| const intptr_t list_length = ReadListLength(); | |
| ASSERT_EQUAL(list_length, 1); | |
| // Read types. | |
| const TypeArguments& type_arguments = T.BuildTypeArguments(list_length); | |
| const AbstractType& type_argument = | |
| AbstractType::Handle(type_arguments.TypeAt(0)); | |
| // AbiSpecificTypes can have an incomplete mapping. | |
| const char* error = nullptr; | |
| const auto* native_type = | |
| compiler::ffi::NativeType::FromAbstractType(zone_, type_argument, &error); | |
| ReportIfNotNull(error); | |
| Fragment code; | |
| // Read positional argument count. | |
| const intptr_t positional_count = ReadListLength(); | |
| ASSERT(positional_count == argument_count); | |
| code += BuildExpression(); // Argument 1: typedDataBase. | |
| code += BuildExpression(); // Argument 2: offsetInBytes | |
| if (at_index) { | |
| code += BuildExpression(); // Argument 3: index | |
| code += IntConstant(native_type->SizeInBytes()); | |
| code += B->BinaryIntegerOp(Token::kMUL, kTagged, /*is_truncating=*/true); | |
| code += B->BinaryIntegerOp(Token::kADD, kTagged, /*is_truncating=*/true); | |
| } | |
| if (is_store) { | |
| code += BuildExpression(); // Argument 4: value | |
| } | |
| // Skip (empty) named arguments list. | |
| const intptr_t named_args_len = ReadListLength(); | |
| ASSERT(named_args_len == 0); | |
| // This call site is not guaranteed to be optimized. So, do a call to the | |
| // correct force optimized function instead of compiling the body. | |
| MethodRecognizer::Kind kind; | |
| if (is_store) { | |
| kind = compiler::ffi::FfiStore(*native_type); | |
| } else { | |
| kind = compiler::ffi::FfiLoad(*native_type); | |
| } | |
| const char* function_name = MethodRecognizer::KindToFunctionNameCString(kind); | |
| const Library& ffi_library = Library::Handle(Z, Library::FfiLibrary()); | |
| const Function& target = Function::ZoneHandle( | |
| Z, ffi_library.LookupFunctionAllowPrivate( | |
| String::Handle(Z, String::New(function_name)))); | |
| ASSERT(!target.IsNull()); | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| const intptr_t static_call_arg_count = 2 + (is_store ? 1 : 0); | |
| code += StaticCall(TokenPosition::kNoSource, target, static_call_arg_count, | |
| argument_names, ICData::kStatic); | |
| return code; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildFfiCall() { | |
| const intptr_t argc = ReadUInt(); // Read argument count. | |
| ASSERT(argc == 1); // Target pointer. | |
| const intptr_t list_length = ReadListLength(); // Read types list length. | |
| T.BuildTypeArguments(list_length); // Read types. | |
| // Read positional argument count. | |
| const intptr_t positional_count = ReadListLength(); | |
| ASSERT(positional_count == argc); | |
| Fragment code; | |
| // Push the target function pointer passed as Pointer object. | |
| code += BuildExpression(); | |
| // This can only be Pointer, so the data field points to unmanaged memory. | |
| code += LoadNativeField(Slot::PointerBase_data(), | |
| InnerPointerAccess::kCannotBeInnerPointer); | |
| // Skip (empty) named arguments list. | |
| const intptr_t named_args_len = ReadListLength(); | |
| ASSERT(named_args_len == 0); | |
| const auto& native_type = FunctionType::ZoneHandle( | |
| Z, parsed_function()->function().FfiCSignature()); | |
| // AbiSpecificTypes can have an incomplete mapping. | |
| const char* error = nullptr; | |
| compiler::ffi::NativeFunctionTypeFromFunctionType(Z, native_type, &error); | |
| if (error != nullptr) { | |
| const auto& language_error = Error::Handle( | |
| LanguageError::New(String::Handle(String::New(error, Heap::kOld)), | |
| Report::kError, Heap::kOld)); | |
| Report::LongJump(language_error); | |
| } | |
| code += B->FfiCallFunctionBody(parsed_function()->function(), native_type, | |
| /*first_argument_parameter_offset=*/1); | |
| ASSERT(code.is_closed()); | |
| NullConstant(); // Maintain stack balance. | |
| return code; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildArgumentsCachableIdempotentCall( | |
| intptr_t* argument_count) { | |
| *argument_count = ReadUInt(); // read arguments count. | |
| // List of types. | |
| const intptr_t types_list_length = ReadListLength(); | |
| if (types_list_length != 0) { | |
| FATAL("Type arguments for vm:cachable-idempotent not (yet) supported."); | |
| } | |
| Fragment code; | |
| // List of positional. | |
| intptr_t positional_list_length = ReadListLength(); | |
| for (intptr_t i = 0; i < positional_list_length; ++i) { | |
| code += BuildExpression(); | |
| Definition* target_def = B->Peek(); | |
| if (!target_def->IsConstant()) { | |
| FATAL( | |
| "Arguments for vm:cachable-idempotent must be const, argument on " | |
| "index %" Pd " is not.", | |
| i); | |
| } | |
| } | |
| // List of named. | |
| const intptr_t named_args_len = ReadListLength(); | |
| if (named_args_len != 0) { | |
| FATAL("Named arguments for vm:cachable-idempotent not (yet) supported."); | |
| } | |
| return code; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildCachableIdempotentCall( | |
| TokenPosition position, | |
| const Function& target) { | |
| // The call site must me fore optimized because the cache is untagged. | |
| if (!parsed_function()->function().ForceOptimize()) { | |
| FATAL( | |
| "vm:cachable-idempotent functions can only be called from " | |
| "vm:force-optimize functions."); | |
| } | |
| const auto& target_result_type = AbstractType::Handle(target.result_type()); | |
| if (!target_result_type.IsIntType()) { | |
| FATAL("The return type vm:cachable-idempotent functions must be int.") | |
| } | |
| Fragment code; | |
| Array& argument_names = Array::ZoneHandle(Z); | |
| intptr_t argument_count; | |
| code += BuildArgumentsCachableIdempotentCall(&argument_count); | |
| code += flow_graph_builder_->CachableIdempotentCall( | |
| position, kUnboxedAddress, target, argument_count, argument_names, | |
| /*type_args_len=*/0); | |
| return code; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction( | |
| FfiCallbackKind kind) { | |
| // The call-site must look like this (guaranteed by the FE which inserts it): | |
| // | |
| // FfiCallbackKind::kIsolateLocalStaticCallback: | |
| // _nativeCallbackFunction<NativeSignatureType>(target, exceptionalReturn) | |
| // | |
| // FfiCallbackKind::kIsolateGroupBoundStaticCallback: | |
| // _nativeCallbackFunction<NativeSignatureType>(target, exceptionalReturn) | |
| // | |
| // FfiCallbackKind::kAsyncCallback: | |
| // _nativeAsyncCallbackFunction<NativeSignatureType>() | |
| // | |
| // FfiCallbackKind::kIsolateLocalClosureCallback: | |
| // _nativeIsolateLocalCallbackFunction<NativeSignatureType>( | |
| // exceptionalReturn) | |
| // | |
| // FfiCallbackKind::kIsolateGroupBoundClosureCallback: | |
| // _nativeIsolateGroupBoundCallbackFunction<NativeSignatureType>( | |
| // exceptionalReturn) | |
| // | |
| // The FE also guarantees that the arguments are constants. | |
| const bool has_target = | |
| kind == FfiCallbackKind::kIsolateLocalStaticCallback || | |
| kind == FfiCallbackKind::kIsolateGroupBoundStaticCallback; | |
| const bool has_exceptional_return = kind != FfiCallbackKind::kAsyncCallback; | |
| const intptr_t expected_argc = | |
| static_cast<int>(has_target) + static_cast<int>(has_exceptional_return); | |
| const intptr_t argc = ReadUInt(); // Read argument count. | |
| ASSERT(argc == expected_argc); | |
| const intptr_t list_length = ReadListLength(); // Read types list length. | |
| ASSERT(list_length == 1); // The native signature. | |
| const TypeArguments& type_arguments = | |
| T.BuildTypeArguments(list_length); // Read types. | |
| ASSERT(type_arguments.Length() == 1 && type_arguments.IsInstantiated()); | |
| const FunctionType& native_sig = | |
| FunctionType::CheckedHandle(Z, type_arguments.TypeAt(0)); | |
| Fragment code; | |
| const intptr_t positional_count = | |
| ReadListLength(); // Read positional argument count. | |
| ASSERT(positional_count == expected_argc); | |
| // Read target expression and extract the target function. | |
| Function& target = Function::Handle(Z, Function::null()); | |
| Instance& exceptional_return = Instance::ZoneHandle(Z, Instance::null()); | |
| if (has_target) { | |
| // Build target argument. | |
| code += BuildExpression(); | |
| Definition* target_def = B->Peek(); | |
| ASSERT(target_def->IsConstant()); | |
| const Closure& target_closure = | |
| Closure::Cast(target_def->AsConstant()->value()); | |
| ASSERT(!target_closure.IsNull()); | |
| target = target_closure.function(); | |
| ASSERT(!target.IsNull() && target.IsImplicitClosureFunction()); | |
| target = target.parent_function(); | |
| code += Drop(); | |
| } | |
| if (has_exceptional_return) { | |
| // Build exceptionalReturn argument. | |
| code += BuildExpression(); | |
| Definition* exceptional_return_def = B->Peek(); | |
| ASSERT(exceptional_return_def->IsConstant()); | |
| exceptional_return ^= exceptional_return_def->AsConstant()->value().ptr(); | |
| code += Drop(); | |
| } | |
| const intptr_t named_args_len = | |
| ReadListLength(); // Skip (empty) named arguments list. | |
| ASSERT(named_args_len == 0); | |
| // AbiSpecificTypes can have an incomplete mapping. | |
| const char* error = nullptr; | |
| compiler::ffi::NativeFunctionTypeFromFunctionType(zone_, native_sig, &error); | |
| ReportIfNotNull(error); | |
| const Function& result = Function::ZoneHandle( | |
| Z, compiler::ffi::NativeCallbackFunction(native_sig, target, | |
| exceptional_return, kind)); | |
| code += Constant(result); | |
| return code; | |
| } | |
| Fragment StreamingFlowGraphBuilder::BuildFfiNativeAddressOf() { | |
| const intptr_t argc = ReadUInt(); | |
| ASSERT(argc == 1); | |
| const intptr_t types_length = ReadListLength(); | |
| ASSERT(types_length == 1); | |
| T.BuildTypeArguments(types_length); | |
| const intptr_t positional_count = ReadListLength(); | |
| ASSERT(positional_count == 1); | |
| Fragment frag = BuildExpression(); | |
| ASSERT(frag.entry->IsConstant()); | |
| const auto& native_annotation = | |
| Instance::Cast(frag.entry->AsConstant()->value()); | |
| Drop(); | |
| const auto& pointer_class = | |
| Class::ZoneHandle(Z, IG->object_store()->ffi_pointer_class()); | |
| const auto& type_arguments = | |
| TypeArguments::ZoneHandle(Z, IG->object_store()->type_argument_never()); | |
| Fragment code = Constant(type_arguments); | |
| code += AllocateObject(TokenPosition::kNoSource, pointer_class, 1); | |
| code += LoadLocal(MakeTemporary()); // Duplicate Pointer. | |
| // FfiNativeLookupAddress pushes an unboxed value, which is safe even in | |
| // unoptimized mode because then there is no reordering and we're consuming | |
| // the value directly. | |
| code += flow_graph_builder_->FfiNativeLookupAddress(native_annotation); | |
| code += flow_graph_builder_->StoreNativeField( | |
| Slot::PointerBase_data(), InnerPointerAccess::kCannotBeInnerPointer, | |
| StoreFieldInstr::Kind::kInitializing); | |
| const intptr_t named_arg_count = ReadListLength(); | |
| ASSERT(named_arg_count == 0); | |
| return code; | |
| } | |
| } // namespace kernel | |
| } // namespace dart |