// 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* default_values = new ZoneGrowableArray(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) { SkipVariableDeclaration(); // read ith variable declaration. } // 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 declaration VariableDeclarationHelper helper(this); helper.ReadUntilExcluding(VariableDeclarationHelper::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) { SkipVariableDeclaration(); // read ith variable declaration. } for (intptr_t i = 0; i < optional_parameter_count; ++i) { Instance* default_value; // Read ith variable declaration VariableDeclarationHelper helper(this); helper.ReadUntilExcluding(VariableDeclarationHelper::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 { instructions += flow_graph_builder_->StoreFieldGuarded( field, StoreFieldInstr::Kind::kInitializing); } 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 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 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(*a) - static_cast(*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()); // 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 declaration VariableDeclarationHelper helper(this); helper.ReadUntilExcluding(VariableDeclarationHelper::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); if (dart_function.IsGeneric()) { prologue += LoadLocal(fn_type_args); prologue += LoadLocal(closure); prologue += LoadNativeField(Slot::Closure_function_type_arguments()); 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 { prologue += LoadLocal(closure); prologue += LoadNativeField(Slot::Closure_function_type_arguments()); 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 [VariableDeclaration]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); case UntaggedFunction::kIrregexpFunction: break; } 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); } // Mark forwarding stubs. switch (function.kind()) { case UntaggedFunction::kRegularFunction: case UntaggedFunction::kImplicitClosureFunction: case UntaggedFunction::kGetterFunction: case UntaggedFunction::kSetterFunction: case UntaggedFunction::kClosureFunction: case UntaggedFunction::kConstructor: case UntaggedFunction::kDynamicInvocationForwarder: ReadForwardingStubTarget(function); break; default: break; } 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; case UntaggedFunction::kIrregexpFunction: UNREACHABLE(); break; } } void StreamingFlowGraphBuilder::ReadForwardingStubTarget( const Function& function) { if (PeekTag() == kProcedure) { AlternativeReadingScope alt(&reader_); ProcedureHelper procedure_helper(this); procedure_helper.ReadUntilExcluding(ProcedureHelper::kFunction); if (procedure_helper.IsForwardingStub() && !procedure_helper.IsAbstract()) { const NameIndex target_name = procedure_helper.concrete_forwarding_stub_target_; ASSERT(target_name != NameIndex::kInvalidName); const String& name = function.IsSetterFunction() ? H.DartSetterName(target_name) : H.DartProcedureName(target_name); const Function* forwarding_target = &Function::ZoneHandle(Z, H.LookupMethodByMember(target_name, name)); ASSERT(!forwarding_target->IsNull()); parsed_function()->MarkForwardingStub(forwarding_target); } } } 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 kVariableDeclaration: return BuildVariableDeclaration(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& 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& 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(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(); 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(); const String& getter_name = H.DartGetterName(library_reference, name_index); const String& method_name = H.DartMethodName(library_reference, name_index); SkipInterfaceMemberNameReference(); // skip target_reference. // 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()) { 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()) { 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 += LoadLocal(parsed_function()->receiver_var()); 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 = 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()) { 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 { // receiver instructions += LoadLocal(parsed_function()->receiver_var()); 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. } 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_); SkipVariableDeclaration(); // FunctionNode follows the variable declaration. const intptr_t function_node_kernel_offset = ReaderOffset(); target_function = ClosureFunctionsCache::LookupClosureFunction( Function::Handle(Z, parsed_function()->function().GetOutermostFunction()), function_node_kernel_offset); 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(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); intptr_t type_args_len = 0; { AlternativeReadingScope alt(&reader_); SkipName(); // skip method name ReadUInt(); // read argument count. type_args_len = ReadListLength(); // read types list length. } Class& klass = GetSuperOrDie(); // Search the superclass chain for the selector. const String& method_name = ReadNameAsMethodName(); // read name. // Figure out selector signature. intptr_t argument_count; Array& argument_names = Array::Handle(Z); { AlternativeReadingScope alt(&reader_); argument_count = ReadUInt(); 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(); } } Function& function = FindMatchingFunction( klass, method_name, type_args_len, argument_count + 1 /* account for 'this' */, argument_names); if (function.IsNull()) { 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_); 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); } } // receiver instructions += LoadLocal(parsed_function()->receiver_var()); 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; } const Class& klass = Class::ZoneHandle(Z, target.Owner()); if (target.IsGenerativeConstructor() || target.IsFactory()) { // The VM requires a TypeArguments object as first parameter for // every factory constructor. ++argument_count; } if (target.IsCachableIdempotent()) { return BuildCachableIdempotentCall(position, target); } const auto recognized_kind = target.recognized_kind(); switch (recognized_kind) { case MethodRecognizer::kNativeEffect: return BuildNativeEffect(); 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; LocalVariable* instance_variable = nullptr; 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; // If we cross the Kernel -> VM core library boundary, a [StaticInvocation] // can appear, but the thing we're calling is not a static method, but a // factory constructor. // The `H.LookupStaticmethodByKernelProcedure` will potentially resolve to the // forwarded constructor. // In that case we'll make an instance and pass it as first argument. // // TODO(27590): Get rid of this after we're using core libraries compiled // into Kernel. intptr_t type_args_len = 0; if (target.IsGenerativeConstructor()) { if (klass.NumTypeArguments() > 0) { const TypeArguments& type_arguments = PeekArgumentsInstantiatedType(klass); instructions += TranslateInstantiatedTypeArguments(type_arguments); instructions += AllocateObject(position, klass, 1); } else { instructions += AllocateObject(position, klass, 0); } instance_variable = MakeTemporary(); instructions += LoadLocal(instance_variable); } else if (target.IsFactory()) { // The VM requires currently a TypeArguments object as first parameter for // every factory constructor :-/ ! // // TODO(27590): Get rid of this after we're using core libraries compiled // into Kernel. const TypeArguments& type_arguments = PeekArgumentsInstantiatedType(klass); instructions += TranslateInstantiatedTypeArguments(type_arguments); } else 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(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.IsTopTypeForInstanceOf()) { // 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.IsTopTypeForSubtyping())) { // 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, 2, ICData::kStatic); 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._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(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, 2, ICData::kStatic); } 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. return BuildFunctionNode(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 += BuildVariableDeclaration(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. 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); 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(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(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::BuildFutureNullValue( TokenPosition* position) { if (position != nullptr) *position = TokenPosition::kNoSource; const Class& future = Class::Handle(Z, IG->object_store()->future_class()); ASSERT(!future.IsNull()); const auto& error = future.EnsureIsFinalized(thread()); ASSERT(error == Error::null()); Function& constructor = Function::ZoneHandle( Z, Resolver::ResolveFunction(Z, future, Symbols::FutureValue())); ASSERT(!constructor.IsNull()); Fragment instructions; instructions += BuildNullLiteral(position); instructions += StaticCall(TokenPosition::kNoSource, constructor, /* argument_count = */ 1, ICData::kStatic); return instructions; } 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; // Create a copy of the closure. Fragment instructions = BuildExpression(); LocalVariable* original_closure = MakeTemporary(); // Load the target function and context and allocate the closure. instructions += LoadLocal(original_closure); instructions += flow_graph_builder_->LoadNativeField(Slot::Closure_function()); instructions += LoadLocal(original_closure); instructions += flow_graph_builder_->LoadNativeField(Slot::Closure_context()); instructions += LoadLocal(original_closure); instructions += flow_graph_builder_->LoadNativeField( Slot::Closure_instantiator_type_arguments()); instructions += flow_graph_builder_->AllocateClosure( position, /*has_instantiator_type_args=*/true, /*is_generic=*/false, /*is_tear_off=*/false); LocalVariable* new_closure = MakeTemporary(); intptr_t num_type_args = ReadListLength(); const TypeArguments& type_args = T.BuildTypeArguments(num_type_args); instructions += TranslateInstantiatedTypeArguments(type_args); LocalVariable* type_args_vec = MakeTemporary("type_args"); // Check the bounds. // // TODO(sjindel): We should be able to skip this check in many cases, e.g. // when the closure is coming from a tearoff of a top-level method or from a // local closure. instructions += LoadLocal(original_closure); instructions += LoadLocal(type_args_vec); const Library& dart_internal = Library::Handle(Z, Library::InternalLibrary()); const Function& bounds_check_function = Function::ZoneHandle( Z, dart_internal.LookupFunctionAllowPrivate( Symbols::BoundsCheckForPartialInstantiation())); ASSERT(!bounds_check_function.IsNull()); instructions += StaticCall(TokenPosition::kNoSource, bounds_check_function, 2, ICData::kStatic); instructions += Drop(); instructions += LoadLocal(new_closure); instructions += LoadLocal(type_args_vec); instructions += flow_graph_builder_->StoreNativeField( Slot::Closure_delayed_type_arguments(), StoreFieldInstr::Kind::kInitializing); instructions += DropTemporary(&type_args_vec); // Copy over the function type arguments. instructions += LoadLocal(new_closure); instructions += LoadLocal(original_closure); instructions += flow_graph_builder_->LoadNativeField( Slot::Closure_function_type_arguments()); instructions += flow_graph_builder_->StoreNativeField( Slot::Closure_function_type_arguments(), StoreFieldInstr::Kind::kInitializing); instructions += DropTempsPreserveTop(1); // Drop old closure. 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.IsTopTypeForSubtyping()) { 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. 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); 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; 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; } 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; 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); 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: // // } // // This means that the 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& 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& case_bodies = helper->case_bodies(); const intptr_t expression_count = helper->expressions().length(); const GrowableArray& 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 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& 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 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 = ] // goto B2 // B3: [catch entry, try_handler_index = ] // 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. 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(); SkipVariableDeclaration(); // 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(); SkipVariableDeclaration(); // read stack trace. } { CatchBlock block(flow_graph_builder_, CurrentException(), CurrentStackTrace(), try_handler_index); catch_handler_body += BuildStatementWithBranchCoverage(); // read body. // 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 : // // _AsyncStarStreamController controller = :suspend_state._functionData; // if (controller.add()) { // return; // } // if (suspend()) { // return; // } // // Generate the following code for yield* : // // _AsyncStarStreamController controller = :suspend_state._functionData; // if (controller.addStream()) { // 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 : // // _SyncStarIterator iterator = :suspend_state._functionData; // iterator._current = ; // suspend(); // // Generate the following code for yield* : // // _SyncStarIterator iterator = :suspend_state._functionData; // iterator._yieldStarIterable = ; // 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::BuildVariableDeclaration( TokenPosition* position) { intptr_t kernel_position_no_tag = ReaderOffset() + data_program_offset_; LocalVariable* variable = LookupVariable(kernel_position_no_tag); VariableDeclarationHelper helper(this); helper.ReadUntilExcluding(VariableDeclarationHelper::kType); T.BuildType(); // read type. bool has_initializer = (ReadTag() != kNothing); 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) { 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 (position != nullptr) *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_; SkipVariableDeclaration(); Fragment instructions = DebugStepCheck(pos); instructions += BuildFunctionNode(offset); instructions += StoreLocal(pos, LookupVariable(variable_offset)); instructions += Drop(); return instructions; } Fragment StreamingFlowGraphBuilder::BuildFunctionNode( 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(), 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(); Fragment instructions; instructions += Constant(function); if (scopes()->IsClosureWithEmptyContext(func_node_offset)) { instructions += NullConstant(); } else { instructions += LoadLocal(parsed_function()->current_context_var()); } // The function signature can have uninstantiated class type parameters. const bool has_instantiator_type_args = !function.HasInstantiatedSignature(kCurrentClass); if (has_instantiator_type_args) { instructions += LoadInstantiatorTypeArguments(); } instructions += flow_graph_builder_->AllocateClosure( function.token_pos(), has_instantiator_type_args, function.IsGeneric(), /*is_tear_off=*/false); LocalVariable* closure = MakeTemporary(); // 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::Closure_function_type_arguments(), StoreFieldInstr::Kind::kInitializing); return instructions; } Fragment StreamingFlowGraphBuilder::BuildNativeEffect() { 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(target, exceptionalReturn) // // FfiCallbackKind::kIsolateGroupBoundStaticCallback: // _nativeCallbackFunction(target, exceptionalReturn) // // FfiCallbackKind::kAsyncCallback: // _nativeAsyncCallbackFunction() // // FfiCallbackKind::kIsolateLocalClosureCallback: // _nativeIsolateLocalCallbackFunction( // exceptionalReturn) // // FfiCallbackKind::kIsolateGroupBoundClosureCallback: // _nativeIsolateGroupBoundCallbackFunction( // 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(has_target) + static_cast(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