// 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/compiler/ffi/callback.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/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 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); 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_); if (field_helper.IsConst()) { // This will read the initializer. body += Constant( Instance::ZoneHandle(Z, constant_reader_.ReadConstantExpression())); } else { body += SetupCapturedParameters(parsed_function()->function()); body += BuildExpression(); // read initializer. } body += Return(TokenPosition::kNoSource); PrologueInfo prologue_info(-1, -1); if (B->IsCompiledForOsr()) { B->graph_entry_->RelinkToOsrEntry(Z, B->last_used_block_id_ + 1); } return new (Z) FlowGraph(*parsed_function(), B->graph_entry_, B->last_used_block_id_, prologue_info); } void StreamingFlowGraphBuilder::EvaluateConstFieldValue(const Field& field) { ASSERT(field.is_const() && field.IsUninitialized()); FieldHelper field_helper(this); field_helper.ReadUntilExcluding(FieldHelper::kInitializer); Tag initializer_tag = ReadTag(); // read first part of initializer. ASSERT(initializer_tag == kSomething); Instance& value = Instance::Handle(Z, constant_reader_.ReadConstantExpression()); field.SetStaticValue(value); } 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()->IsMutatorThread()) { field.RecordStore(Object::null_object()); } else { ASSERT(field.is_nullable(/* silence_assert = */ true)); } 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_->StoreInstanceFieldGuarded( field, StoreInstanceFieldInstr::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()->IsMutatorThread()) { field.RecordStore(Object::null_object()); } else { ASSERT(field.is_nullable(/* silence_assert = */ true)); } return Fragment(); } Fragment instructions; instructions += LoadLocal(parsed_function()->receiver_var()); instructions += flow_graph_builder_->Constant(Object::sentinel()); instructions += flow_graph_builder_->StoreInstanceField( field, StoreInstanceFieldInstr::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(); 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 // StoreInstanceField 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); ExternalTypedData& kernel_data = ExternalTypedData::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.KernelData(); 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.raw()), 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.raw()), /*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(); bool isSynthetic = ReadBool(); // read isSynthetic flag. switch (tag) { case kInvalidInitializer: UNIMPLEMENTED(); return Fragment(); case kFieldInitializer: { ReadCanonicalNameReference(); instructions += BuildFieldInitializer( Field::ZoneHandle(Z, initializer_fields[i]->raw()), /*only_for_size_effects=*/false); break; } case kAssertInitializer: { instructions += BuildStatement(); break; } case kSuperInitializer: { 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_parameter_count = */ NULL); // 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_parameter_count = */ NULL); // read arguments. argument_count += 1; const Function& target = Function::ZoneHandle( Z, H.LookupConstructorByKernelConstructor(canonical_target)); instructions += StaticCall( isSynthetic ? TokenPosition::kNoSource : position, target, argument_count, argument_names, ICData::kStatic); instructions += Drop(); break; } case kLocalInitializer: { // 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::SetAsyncStackTrace( const Function& dart_function) { if (!FLAG_causal_async_stacks || !(dart_function.IsAsyncClosure() || dart_function.IsAsyncGenClosure())) { return {}; } // The code we are building will be executed right after we enter // the function and before any nested contexts are allocated. ASSERT(B->context_depth_ == scopes()->yield_jump_variable->owner()->context_level()); Fragment instructions; LocalScope* scope = parsed_function()->scope(); const Function& target = Function::ZoneHandle( Z, I->object_store()->async_set_thread_stack_trace()); ASSERT(!target.IsNull()); // Fetch and load :async_stack_trace LocalVariable* async_stack_trace_var = scope->LookupVariable(Symbols::AsyncStackTraceVar(), false); ASSERT((async_stack_trace_var != NULL) && async_stack_trace_var->is_captured()); Fragment code; code += LoadLocal(async_stack_trace_var); // Call _asyncSetThreadStackTrace code += StaticCall(TokenPosition::kNoSource, target, /* argument_count = */ 1, ICData::kStatic); code += Drop(); return code; } Fragment StreamingFlowGraphBuilder::TypeArgumentsHandling( const Function& dart_function) { Fragment prologue = B->BuildDefaultTypeHandling(dart_function); if (dart_function.IsClosureFunction() && dart_function.NumParentTypeParameters() > 0) { LocalVariable* closure = parsed_function()->ParameterVariable(0); // Function with yield points can not be generic itself but the outer // function can be. ASSERT(yield_continuations().is_empty() || !dart_function.IsGeneric()); LocalVariable* fn_type_args = parsed_function()->function_type_arguments(); ASSERT(fn_type_args != NULL && closure != NULL); if (dart_function.IsGeneric()) { prologue += LoadLocal(fn_type_args); prologue += LoadLocal(closure); prologue += LoadNativeField(Slot::Closure_function_type_arguments()); prologue += IntConstant(dart_function.NumParentTypeParameters()); prologue += IntConstant(dart_function.NumTypeParameters() + dart_function.NumParentTypeParameters()); 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::CompleteBodyWithYieldContinuations( Fragment body) { // The code we are building will be executed right after we enter // the function and before any nested contexts are allocated. // Reset current context_depth_ to match this. const intptr_t current_context_depth = B->context_depth_; B->context_depth_ = scopes()->yield_jump_variable->owner()->context_level(); // Prepend an entry corresponding to normal entry to the function. yield_continuations().InsertAt( 0, YieldContinuation(new (Z) DropTempsInstr(0, NULL), kInvalidTryIndex)); yield_continuations()[0].entry->LinkTo(body.entry); // Load :await_jump_var into a temporary. Fragment dispatch; dispatch += LoadLocal(scopes()->yield_jump_variable); dispatch += StoreLocal(TokenPosition::kNoSource, scopes()->switch_variable); dispatch += Drop(); const intptr_t continuation_count = yield_continuations().length(); IndirectGotoInstr* indirect_goto; if (FLAG_async_igoto_threshold >= 0 && continuation_count >= FLAG_async_igoto_threshold) { const auto& offsets = TypedData::ZoneHandle( Z, TypedData::New(kTypedDataInt32ArrayCid, continuation_count, Heap::kOld)); dispatch += Constant(offsets); dispatch += LoadLocal(scopes()->switch_variable); // Ideally this would just be LoadIndexedTypedData(kTypedDataInt32ArrayCid), // but that doesn't work in unoptimised code. // The optimiser will turn this into that in any case. dispatch += InstanceCall(TokenPosition::kNoSource, Symbols::IndexToken(), Token::kINDEX, /*argument_count=*/2); Value* offset_from_start = Pop(); indirect_goto = new (Z) IndirectGotoInstr(&offsets, offset_from_start); dispatch <<= indirect_goto; for (intptr_t i = 0; i < continuation_count; i++) { if (i >= 1) { Fragment resumption; // Every continuation after the first is not a normal entry but a // resumption. // Restore :current_context_var from :await_ctx_var. // Note: after this point context_depth_ does not match current context // depth so we should not access any local variables anymore. resumption += LoadLocal(scopes()->yield_context_variable); resumption += StoreLocal(TokenPosition::kNoSource, parsed_function()->current_context_var()); resumption += Drop(); Instruction* next = yield_continuations()[i].entry->next(); yield_continuations()[i].entry->LinkTo(resumption.entry); resumption <<= next; } IndirectEntryInstr* indirect_entry = B->BuildIndirectEntry( /*indirect_id=*/i, yield_continuations()[i].try_index); indirect_entry->LinkTo(yield_continuations()[i].entry->next()); TargetEntryInstr* target = B->BuildTargetEntry(); Fragment(target) + Goto(indirect_entry); indirect_goto->AddSuccessor(target); } } else { BlockEntryInstr* block = nullptr; for (intptr_t i = 0; i < continuation_count; i++) { if (i == 1) { // This is not a normal entry but a resumption. Restore // :current_context_var from :await_ctx_var. // Note: after this point context_depth_ does not match current context // depth so we should not access any local variables anymore. dispatch += LoadLocal(scopes()->yield_context_variable); dispatch += StoreLocal(TokenPosition::kNoSource, parsed_function()->current_context_var()); dispatch += Drop(); } if (i == (continuation_count - 1)) { // We reached the last possibility, no need to build more ifs. // Continue to the last continuation. // Note: continuations start with nop DropTemps instruction // which acts like an anchor, so we need to skip it. block->set_try_index(yield_continuations()[i].try_index); dispatch <<= yield_continuations()[i].entry->next(); break; } // Build comparison: // // if (:await_jump_var == i) { // -> yield_continuations()[i] // } else ... // TargetEntryInstr* then; TargetEntryInstr* otherwise; dispatch += LoadLocal(scopes()->switch_variable); dispatch += IntConstant(i); dispatch += B->BranchIfStrictEqual(&then, &otherwise); // True branch is linked to appropriate continuation point. // Note: continuations start with nop DropTemps instruction // which acts like an anchor, so we need to skip it. then->LinkTo(yield_continuations()[i].entry->next()); then->set_try_index(yield_continuations()[i].try_index); // False branch will contain the next comparison. dispatch = Fragment(dispatch.entry, otherwise); block = otherwise; } } B->context_depth_ = current_context_depth; return dispatch; } 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.HasOptionalParameters() && raw_parameter.owner() == scope) || (!function.HasOptionalParameters() && raw_parameter.owner() == NULL)); 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_->StoreInstanceField( TokenPosition::kNoSource, Slot::GetContextVariableSlotFor(thread(), *variable), StoreInstanceFieldInstr::Kind::kInitializing); } } body += Drop(); // The context. } return body; } // If we run in checked mode or strong mode, we have to check the type of the // passed arguments. // // TODO(#34162): If we're building an extra entry-point to skip // type checks, we should substitute Redefinition nodes for the AssertAssignable // instructions to ensure that the argument types are known. void StreamingFlowGraphBuilder::CheckArgumentTypesAsNecessary( const Function& dart_function, intptr_t type_parameters_offset, Fragment* explicit_checks, Fragment* implicit_checks, Fragment* implicit_redefinitions) { if (dart_function.NeedsTypeArgumentTypeChecks()) { B->BuildTypeArgumentTypeChecks( MethodCanSkipTypeChecksForNonCovariantTypeArguments(dart_function) ? TypeChecksToBuild::kCheckCovariantTypeParameterBounds : TypeChecksToBuild::kCheckAllTypeParameterBounds, implicit_checks); } if (dart_function.NeedsArgumentTypeChecks()) { B->BuildArgumentTypeChecks(explicit_checks, implicit_checks, implicit_redefinitions); } } 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().raw()) && (dart_function.Owner() != I->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_native()) { body += B->NativeFunctionBody(dart_function, first_parameter); } else if (has_body) { body += BuildStatement(); } else if (dart_function.is_external()) { body += ThrowNoSuchMethodError(dart_function); } if (body.is_open()) { body += NullConstant(); body += Return(dart_function.end_token_pos()); } return body; } Fragment StreamingFlowGraphBuilder::BuildEveryTimePrologue( const Function& dart_function, TokenPosition token_position, intptr_t type_parameters_offset) { Fragment F; F += CheckStackOverflowInPrologue(dart_function); F += DebugStepCheckInPrologue(dart_function, token_position); F += SetAsyncStackTrace(dart_function); F += B->InitConstantParameters(); return F; } Fragment StreamingFlowGraphBuilder::BuildFirstTimePrologue( const Function& dart_function, LocalVariable* first_parameter, intptr_t type_parameters_offset) { Fragment F; 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& first_time_prologue, const Fragment& every_time_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. There is a non-empty "first-time" prologue. // // 3. The "every-time" prologue has more than two instructions (DebugStepCheck // and CheckStackOverflow). // // TODO(#34162): For regular closures we can often avoid the // PrologueBuilder-prologue on non-dynamic invocations. if (!PrologueBuilder::HasEmptyPrologue(dart_function) || !type_args_handling.is_empty() || !first_time_prologue.is_empty() || !(every_time_prologue.entry == every_time_prologue.current || every_time_prologue.current->previous() == every_time_prologue.entry)) { return UncheckedEntryPointStyle::kSharedWithVariable; } return UncheckedEntryPointStyle::kSeparate; } FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction( bool is_constructor) { const Function& dart_function = parsed_function()->function(); intptr_t type_parameters_offset = 0; LocalVariable* first_parameter = nullptr; TokenPosition token_position = TokenPosition::kNoSource; { AlternativeReadingScope alt(&reader_); FunctionNodeHelper function_node_helper(this); function_node_helper.ReadUntilExcluding( FunctionNodeHelper::kTypeParameters); type_parameters_offset = ReaderOffset(); function_node_helper.ReadUntilExcluding( FunctionNodeHelper::kPositionalParameters); 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_; } 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); // The 'every_time_prologue' runs first and is run when resuming from yield // points. const Fragment every_time_prologue = BuildEveryTimePrologue( dart_function, token_position, type_parameters_offset); // The 'first_time_prologue' run after 'every_time_prologue' and is *not* run // when resuming from yield points. const Fragment first_time_prologue = BuildFirstTimePrologue( dart_function, first_parameter, type_parameters_offset); // 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 explicit_type_checks; Fragment implicit_type_checks; Fragment implicit_redefinitions; CheckArgumentTypesAsNecessary(dart_function, type_parameters_offset, &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) + B->BuildNullAssertions() + BuildFunctionBody(dart_function, first_parameter, is_constructor); auto extra_entry_point_style = ChooseEntryPointStyle( dart_function, implicit_type_checks, first_time_prologue, every_time_prologue, type_args_handling); Fragment function(instruction_cursor); if (yield_continuations().is_empty()) { FunctionEntryInstr* extra_entry = nullptr; switch (extra_entry_point_style) { case UncheckedEntryPointStyle::kNone: { function += every_time_prologue + first_time_prologue + type_args_handling + implicit_type_checks + explicit_type_checks + body; break; } case UncheckedEntryPointStyle::kSeparate: { ASSERT(instruction_cursor == normal_entry); ASSERT(first_time_prologue.is_empty()); ASSERT(type_args_handling.is_empty()); const Fragment prologue_copy = BuildEveryTimePrologue( dart_function, token_position, type_parameters_offset); extra_entry = B->BuildSeparateUncheckedEntryPoint( normal_entry, /*normal_prologue=*/every_time_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 += every_time_prologue; prologue += first_time_prologue; prologue += type_args_handling; prologue += explicit_type_checks; extra_entry = B->BuildSharedUncheckedEntryPoint( /*shared_prologue_linked_in=*/prologue, /*skippable_checks=*/implicit_type_checks, /*redefinitions_if_skipped=*/implicit_redefinitions, /*body=*/body); break; } } if (extra_entry != nullptr) { B->RecordUncheckedEntryPoint(graph_entry, extra_entry); } } else { // If the function's body contains any yield points, build switch statement // that selects a continuation point based on the value of :await_jump_var. ASSERT(explicit_type_checks.is_empty()); // If the function is generic, type_args_handling might require access to // (possibly captured) 'this' for preparing default type arguments, in which // case we can't run it before the 'first_time_prologue'. ASSERT(!dart_function.IsGeneric()); // TODO(#34162): We can probably ignore the implicit checks // here as well since the arguments are passed from generated code. function += every_time_prologue + type_args_handling + CompleteBodyWithYieldContinuations(first_time_prologue + implicit_type_checks + body); } // When compiling for OSR, use a depth first search to find the OSR // entry and make graph entry jump to it instead of normal entry. // Catch entries are always considered reachable, even if they // become unreachable after OSR. if (flow_graph_builder_->IsCompiledForOsr()) { graph_entry->RelinkToOsrEntry(Z, flow_graph_builder_->last_used_block_id_ + 1); } return new (Z) FlowGraph(*parsed_function(), graph_entry, flow_graph_builder_->last_used_block_id_, prologue_info); } FlowGraph* StreamingFlowGraphBuilder::BuildGraph() { ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull()); ASSERT(flow_graph_builder_ != nullptr); const Function& function = parsed_function()->function(); // Setup a [ActiveClassScope] and a [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); ActiveTypeParametersScope active_type_params(active_class(), function, Z); ParseKernelASTFunction(); switch (function.kind()) { case FunctionLayout::kRegularFunction: case FunctionLayout::kGetterFunction: case FunctionLayout::kSetterFunction: case FunctionLayout::kClosureFunction: case FunctionLayout::kConstructor: { if (B->IsRecognizedMethodForFlowGraph(function)) { return B->BuildGraphOfRecognizedMethod(function); } return BuildGraphOfFunction(function.IsGenerativeConstructor()); } case FunctionLayout::kImplicitGetter: case FunctionLayout::kImplicitStaticGetter: case FunctionLayout::kImplicitSetter: { const Field& field = Field::Handle(Z, function.accessor_field()); if (field.is_const() && field.IsUninitialized()) { EvaluateConstFieldValue(field); } return B->BuildGraphOfFieldAccessor(function); } case FunctionLayout::kFieldInitializer: return BuildGraphOfFieldInitializer(); case FunctionLayout::kDynamicInvocationForwarder: return B->BuildGraphOfDynamicInvocationForwarder(function); case FunctionLayout::kMethodExtractor: return flow_graph_builder_->BuildGraphOfMethodExtractor(function); case FunctionLayout::kNoSuchMethodDispatcher: return flow_graph_builder_->BuildGraphOfNoSuchMethodDispatcher(function); case FunctionLayout::kInvokeFieldDispatcher: return flow_graph_builder_->BuildGraphOfInvokeFieldDispatcher(function); case FunctionLayout::kImplicitClosureFunction: return flow_graph_builder_->BuildGraphOfImplicitClosureFunction(function); case FunctionLayout::kFfiTrampoline: return flow_graph_builder_->BuildGraphOfFfiTrampoline(function); case FunctionLayout::kSignatureFunction: case FunctionLayout::kIrregexpFunction: break; } UNREACHABLE(); return NULL; } void StreamingFlowGraphBuilder::ParseKernelASTFunction() { const Function& function = parsed_function()->function(); const intptr_t kernel_offset = function.kernel_offset(); ASSERT(kernel_offset >= 0); SetOffset(kernel_offset); // Mark forwarding stubs. switch (function.kind()) { case FunctionLayout::kRegularFunction: case FunctionLayout::kImplicitClosureFunction: case FunctionLayout::kGetterFunction: case FunctionLayout::kSetterFunction: case FunctionLayout::kClosureFunction: case FunctionLayout::kConstructor: case FunctionLayout::kDynamicInvocationForwarder: ReadForwardingStubTarget(function); break; default: break; } set_scopes(parsed_function()->EnsureKernelScopes()); switch (function.kind()) { case FunctionLayout::kRegularFunction: case FunctionLayout::kGetterFunction: case FunctionLayout::kSetterFunction: case FunctionLayout::kClosureFunction: case FunctionLayout::kConstructor: case FunctionLayout::kImplicitClosureFunction: ReadUntilFunctionNode(); SetupDefaultParameterValues(); break; case FunctionLayout::kImplicitGetter: case FunctionLayout::kImplicitStaticGetter: case FunctionLayout::kImplicitSetter: case FunctionLayout::kFieldInitializer: case FunctionLayout::kMethodExtractor: case FunctionLayout::kNoSuchMethodDispatcher: case FunctionLayout::kInvokeFieldDispatcher: case FunctionLayout::kFfiTrampoline: break; case FunctionLayout::kDynamicInvocationForwarder: if (PeekTag() != kField) { ReadUntilFunctionNode(); SetupDefaultParameterValues(); } break; case FunctionLayout::kSignatureFunction: case FunctionLayout::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.forwarding_stub_super_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::BuildExpression(TokenPosition* position) { 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 kPropertyGet: return BuildPropertyGet(position); case kPropertySet: return BuildPropertySet(position); case kSuperPropertyGet: return BuildSuperPropertyGet(position); case kSuperPropertySet: return BuildSuperPropertySet(position); case kStaticGet: return BuildStaticGet(position); case kStaticSet: return BuildStaticSet(position); case kMethodInvocation: return BuildMethodInvocation(position); 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 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: return BuildConstantExpression(position, tag); case kInstantiation: return BuildPartialTearoffInstantiation(position); case kLoadLibrary: return BuildLibraryPrefixAction(position, Symbols::LoadLibrary()); case kCheckLibraryIsLoaded: return BuildLibraryPrefixAction(position, Symbols::CheckLoaded()); case kConstStaticInvocation: case kConstConstructorInvocation: case kConstListLiteral: case kConstSetLiteral: case kConstMapLiteral: case kSymbolLiteral: // Const invocations and const literals are removed by the // constant evaluator. case kListConcatenation: case kSetConcatenation: case kMapConcatenation: case kInstanceCreation: case kFileUriExpression: // Collection concatenation, instance creation operations and // in-expression URI changes are internal to the front end and // removed by the constant evaluator. default: ReportUnexpectedTag("expression", tag); UNREACHABLE(); } return Fragment(); } Fragment StreamingFlowGraphBuilder::BuildStatement() { Tag tag = ReadTag(); // read tag. switch (tag) { case kExpressionStatement: return BuildExpressionStatement(); case kBlock: return BuildBlock(); case kEmptyStatement: return BuildEmptyStatement(); case kAssertBlock: return BuildAssertBlock(); case kAssertStatement: return BuildAssertStatement(); case kLabeledStatement: return BuildLabeledStatement(); case kBreakStatement: return BuildBreakStatement(); case kWhileStatement: return BuildWhileStatement(); case kDoStatement: return BuildDoStatement(); case kForStatement: return BuildForStatement(); case kForInStatement: return BuildForInStatement(false); case kAsyncForInStatement: return BuildForInStatement(true); case kSwitchStatement: return BuildSwitchStatement(); case kContinueSwitchStatement: return BuildContinueSwitchStatement(); case kIfStatement: return BuildIfStatement(); case kReturnStatement: return BuildReturnStatement(); case kTryCatch: return BuildTryCatch(); case kTryFinally: return BuildTryFinally(); case kYieldStatement: return BuildYieldStatement(); case kVariableDeclaration: return BuildVariableDeclaration(); case kFunctionDeclaration: return BuildFunctionDeclaration(); default: ReportUnexpectedTag("statement", tag); UNREACHABLE(); } return Fragment(); } void StreamingFlowGraphBuilder::ReportUnexpectedTag(const char* variant, Tag tag) { if ((flow_graph_builder_ == NULL) || (parsed_function() == NULL)) { KernelReaderHelper::ReportUnexpectedTag(variant, tag); } else { 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() { if (translation_helper_.info().kernel_binary_version() >= 34) { return reader_.ReadVariance(); } return kCovariant; } void StreamingFlowGraphBuilder::loop_depth_inc() { ++flow_graph_builder_->loop_depth_; } void StreamingFlowGraphBuilder::loop_depth_dec() { --flow_graph_builder_->loop_depth_; } intptr_t StreamingFlowGraphBuilder::for_in_depth() { return flow_graph_builder_->for_in_depth_; } void StreamingFlowGraphBuilder::for_in_depth_inc() { ++flow_graph_builder_->for_in_depth_; } void StreamingFlowGraphBuilder::for_in_depth_dec() { --flow_graph_builder_->for_in_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_; } 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_; } GrowableArray& StreamingFlowGraphBuilder::yield_continuations() { return flow_graph_builder_->yield_continuations_; } Value* StreamingFlowGraphBuilder::stack() { return flow_graph_builder_->stack_; } 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(); } LocalVariable* StreamingFlowGraphBuilder::LookupVariable( intptr_t kernel_offset) { return flow_graph_builder_->LookupVariable(kernel_offset); } LocalVariable* StreamingFlowGraphBuilder::MakeTemporary() { return flow_graph_builder_->MakeTemporary(); } 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::ResolveDynamicForReceiverClassAllowPrivate( 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); } Fragment StreamingFlowGraphBuilder::Return(TokenPosition position, intptr_t yield_index) { return flow_graph_builder_->Return(position, /*omit_result_type_check=*/false, yield_index); } Fragment StreamingFlowGraphBuilder::EvaluateAssertion() { return flow_graph_builder_->EvaluateAssertion(); } Fragment StreamingFlowGraphBuilder::RethrowException(TokenPosition position, int catch_try_index) { return flow_graph_builder_->RethrowException(position, catch_try_index); } Fragment StreamingFlowGraphBuilder::ThrowNoSuchMethodError( const Function& target) { return flow_graph_builder_->ThrowNoSuchMethodError(target); } 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, bool clear_the_temp /* = true */) { return flow_graph_builder_->CheckNull(position, receiver, function_name, clear_the_temp); } Fragment StreamingFlowGraphBuilder::StaticCall(TokenPosition position, const Function& target, intptr_t argument_count, ICData::RebindRule rebind_rule) { if (!target.AreValidArgumentCounts(0, argument_count, 0, nullptr)) { return flow_graph_builder_->ThrowNoSuchMethodError(target); } 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)) { return flow_graph_builder_->ThrowNoSuchMethodError(target); } return flow_graph_builder_->StaticCall( position, target, argument_count, argument_names, rebind_rule, result_type, type_args_count, use_unchecked_entry); } 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) { 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); } 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) { return flow_graph_builder_->LoadNativeField(field); } 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::ThrowTypeError() { return flow_graph_builder_->ThrowTypeError(); } 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->for_in_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 += BuildStatementAt(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 flow_graph_builder_->CatchBlockEntry(handler_types, handler_index, needs_stacktrace, is_synthesized); } Fragment StreamingFlowGraphBuilder::TryCatch(int try_handler_index) { return flow_graph_builder_->TryCatch(try_handler_index); } Fragment StreamingFlowGraphBuilder::Drop() { return flow_graph_builder_->Drop(); } 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::BuildImplicitClosureCreation( const Function& target) { return flow_graph_builder_->BuildImplicitClosureCreation(target); } Fragment StreamingFlowGraphBuilder::CheckBoolean(TokenPosition position) { return flow_graph_builder_->CheckBoolean(position); } Fragment StreamingFlowGraphBuilder::CheckArgumentType( LocalVariable* variable, const AbstractType& type) { return flow_graph_builder_->CheckAssignable( type, variable->name(), AssertAssignableInstr::kParameterCheck); } 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); negate = !negate; } TestFragment result; if (PeekTag() == kLogicalExpression) { // Handle '&&' and '||' operators specially to implement short circuit // evaluation. SkipBytes(1); // tag. 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->NegateComparison(); negate = false; } branch = new (Z) BranchInstr(compare, flow_graph_builder_->GetNextDeoptId()); branch->comparison()->ClearTempIndex(); ASSERT(instructions.current->previous() != nullptr); instructions.current = instructions.current->previous(); } else { instructions += CheckBoolean(position); instructions += Constant(Bool::True()); Value* right_value = Pop(); Value* left_value = Pop(); StrictCompareInstr* compare = new (Z) StrictCompareInstr( TokenPosition::kNoSource, 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 == NULL) argument_count = &dummy; *argument_count = ReadUInt(); // read arguments count. // List of types. SkipListOfDartTypes(); // read list of types. { AlternativeReadingScope _(&reader_); if (positional_count == NULL) 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 != NULL && 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 != NULL) { 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 != NULL) *position = pos; const String& message = H.DartString(ReadStringReference()); // Invalid expression message has pointer to the source code, no need to // report it twice. 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, *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(); initialize += StoreLocal(position, variable); initialize += Drop(); initialize += Goto(join); } else { // The variable has no initializer, so throw a LateInitializationError. Fragment initialize(is_uninitialized); initialize += flow_graph_builder_->ThrowLateInitializationError( position, variable->name()); initialize += Goto(join); } } { // 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 != NULL) *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 != NULL) *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, *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, variable->name()); already_initialized += Goto(join); } instructions = Fragment(instructions.entry, join); } else { instructions += StoreLocal(position, variable); } return instructions; } Fragment StreamingFlowGraphBuilder::BuildPropertyGet(TokenPosition* p) { const intptr_t offset = ReaderOffset() - 1; // Include the tag. const TokenPosition position = ReadPosition(); // read position. if (p != NULL) *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. LocalVariable* receiver = NULL; if (direct_call.check_receiver_for_null_) { // Duplicate receiver for CheckNull before it is consumed by PushArgument. receiver = MakeTemporary(); instructions += LoadLocal(receiver); } const String& getter_name = ReadNameAsGetterName(); // read name. const Function* interface_target = &Function::null_function(); const Function* tearoff_interface_target = &Function::null_function(); const NameIndex itarget_name = ReadInterfaceMemberNameReference(); // read interface_target_reference. if (!H.IsRoot(itarget_name) && (H.IsGetter(itarget_name) || H.IsField(itarget_name))) { interface_target = &Function::ZoneHandle( Z, H.LookupMethodByMember(itarget_name, H.DartGetterName(itarget_name))); ASSERT(getter_name.raw() == interface_target->name()); } else if (!H.IsRoot(itarget_name) && H.IsMethod(itarget_name)) { tearoff_interface_target = &Function::ZoneHandle( Z, H.LookupMethodByMember(itarget_name, H.DartMethodName(itarget_name))); } if (direct_call.check_receiver_for_null_) { instructions += CheckNull(position, receiver, getter_name); } const String* mangled_name = &getter_name; const Function* direct_call_target = &direct_call.target_; if (H.IsRoot(itarget_name)) { mangled_name = &String::ZoneHandle( Z, Function::CreateDynamicInvocationForwarderName(getter_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, 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, *interface_target, *tearoff_interface_target, &result_type); } if (direct_call.check_receiver_for_null_) { instructions += DropTempsPreserveTop(1); // Drop receiver, preserve result. } return instructions; } Fragment StreamingFlowGraphBuilder::BuildPropertySet(TokenPosition* p) { const intptr_t offset = ReaderOffset() - 1; // Include the tag. 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; if (PeekTag() == kThisExpression) { is_unchecked_call = true; } instructions += BuildExpression(); // read receiver. LocalVariable* receiver = nullptr; if (direct_call.check_receiver_for_null_) { // Duplicate receiver for CheckNull before it is consumed by PushArgument. receiver = MakeTemporary(); instructions += LoadLocal(receiver); } const String& setter_name = ReadNameAsSetterName(); // read name. instructions += BuildExpression(); // read value. instructions += StoreLocal(TokenPosition::kNoSource, variable); const Function* interface_target = &Function::null_function(); const NameIndex itarget_name = ReadInterfaceMemberNameReference(); // read interface_target_reference. if (!H.IsRoot(itarget_name)) { interface_target = &Function::ZoneHandle( Z, H.LookupMethodByMember(itarget_name, H.DartSetterName(itarget_name))); ASSERT(setter_name.raw() == interface_target->name()); } if (direct_call.check_receiver_for_null_) { instructions += CheckNull(position, receiver, setter_name); } const String* mangled_name = &setter_name; const Function* direct_call_target = &direct_call.target_; if (H.IsRoot(itarget_name)) { 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_count=*/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, *interface_target, Function::null_function(), /*result_type=*/nullptr, /*use_unchecked_entry=*/is_unchecked_call, &call_site_attributes); } instructions += Drop(); // Drop result of the setter invocation. if (direct_call.check_receiver_for_null_) { instructions += Drop(); // Drop receiver. } 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.raw()); 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.raw())); // - 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.raw())); // - 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 != NULL) *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 with context // which captures `this`. return BuildImplicitClosureCreation(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.raw()), /* 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.raw()), /* 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 != NULL) *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.raw()), /* 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.raw()), /* 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 != NULL) *p = position; const InferredTypeMetadata result_type = inferred_type_metadata_helper_.GetInferredType(offset); NameIndex target = ReadCanonicalNameReference(); // read target_reference. if (H.IsField(target)) { const Field& field = Field::ZoneHandle(Z, H.LookupFieldByKernelField(target)); 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().raw()); return Constant(Instance::ZoneHandle(Z, field.StaticValue())); } 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); } } } else { const Function& function = Function::ZoneHandle(Z, H.LookupStaticMethodByKernelProcedure(target)); 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 Fragment(); } Fragment StreamingFlowGraphBuilder::BuildStaticSet(TokenPosition* p) { TokenPosition position = ReadPosition(); // read position. if (p != NULL) *p = position; NameIndex target = ReadCanonicalNameReference(); // read target_reference. if (H.IsField(target)) { const Field& field = Field::ZoneHandle(Z, H.LookupFieldByKernelField(target)); const Class& owner = Class::Handle(Z, field.Owner()); const String& setter_name = H.DartSetterName(target); const Function& setter = Function::ZoneHandle(Z, owner.LookupStaticFunction(setter_name)); Fragment instructions = BuildExpression(); // read expression. if (NeedsDebugStepCheck(stack(), position)) { instructions = DebugStepCheck(position) + instructions; } LocalVariable* variable = MakeTemporary(); instructions += LoadLocal(variable); if (!setter.IsNull() && field.NeedsSetter()) { instructions += StaticCall(position, setter, 1, ICData::kStatic); instructions += Drop(); } else { instructions += StoreStaticField(position, field); } return instructions; } else { ASSERT(H.IsProcedure(target)); // Evaluate the expression on the right hand side. Fragment instructions = BuildExpression(); // read expression. LocalVariable* variable = MakeTemporary(); // Prepare argument. instructions += LoadLocal(variable); // Invoke the setter function. const Function& function = Function::ZoneHandle(Z, H.LookupStaticMethodByKernelProcedure(target)); instructions += StaticCall(position, function, 1, ICData::kStatic); // Drop the unused result & leave the stored value on the stack. return instructions + Drop(); } } Fragment StreamingFlowGraphBuilder::BuildMethodInvocation(TokenPosition* p) { const intptr_t offset = ReaderOffset() - 1; // Include the tag. const TokenPosition position = ReadPosition(); // read position. if (p != NULL) *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_closure_call = false; bool is_unchecked_call = result_type.IsSkipCheck(); if (call_site_attributes.receiver_type != nullptr) { if (call_site_attributes.receiver_type->IsFunctionType()) { AlternativeReadingScope alt(&reader_); SkipExpression(); // skip receiver is_unchecked_closure_call = ReadNameAsMethodName().Equals(Symbols::Call()); } else if (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; LocalVariable* type_arguments_temp = NULL; { 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); if (direct_call.check_receiver_for_null_ || is_unchecked_closure_call) { // Don't yet push type arguments if we need to check receiver for null. // In this case receiver will be duplicated so instead of pushing // type arguments here we need to push it between receiver_temp // and actual receiver. See the code below. type_arguments_temp = MakeTemporary(); } } 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. if (PeekTag() == kThisExpression) { 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(NULL /* named */, NULL /* arg count */, NULL /* 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 = NULL; if (direct_call.check_receiver_for_null_ || is_unchecked_closure_call) { // Duplicate receiver for CheckNull before it is consumed by PushArgument. receiver_temp = MakeTemporary(); if (type_arguments_temp != NULL) { // If call has type arguments then push them before pushing the receiver. // The stack will contain: // // [type_arguments_temp][receiver_temp][type_arguments][receiver] ... // instructions += LoadLocal(type_arguments_temp); } 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 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; } const Function* interface_target = &Function::null_function(); const NameIndex itarget_name = ReadInterfaceMemberNameReference(); // read interface_target_reference. // 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.IsField(itarget_name) && !H.IsGetter(itarget_name)) { interface_target = &Function::ZoneHandle( Z, H.LookupMethodByMember(itarget_name, H.DartProcedureName(itarget_name))); ASSERT(name.raw() == interface_target->name()); ASSERT(!interface_target->IsGetterFunction()); } // TODO(sjindel): Avoid the check for null on unchecked closure calls if TFA // allows. if (direct_call.check_receiver_for_null_ || is_unchecked_closure_call) { // Receiver temp is needed to load the function to call from the closure. instructions += CheckNull(position, receiver_temp, name, /*clear_temp=*/!is_unchecked_closure_call); } 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 (H.IsRoot(itarget_name) && (name.raw() != Symbols::EqualOperator().raw())) { 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_unchecked_closure_call) { // Lookup the function in the closure. instructions += LoadLocal(receiver_temp); instructions += LoadNativeField(Slot::Closure_function()); if (parsed_function()->function().is_debuggable()) { ASSERT(!parsed_function()->function().is_native()); instructions += DebugStepCheck(position); } instructions += B->ClosureCall(position, type_args_len, argument_count, argument_names, /*use_unchecked_entry=*/true); } else 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()); } // Drop temporaries preserving result on the top of the stack. ASSERT((receiver_temp != NULL) || (type_arguments_temp == NULL)); if (receiver_temp != NULL) { const intptr_t num_temps = (receiver_temp != NULL ? 1 : 0) + (type_arguments_temp != NULL ? 1 : 0); instructions += DropTempsPreserveTop(num_temps); } // 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.raw() == Symbols::AssignIndexToken().raw()) { instructions += Drop(); instructions += NullConstant(); } return instructions; } Fragment StreamingFlowGraphBuilder::BuildSuperMethodInvocation( TokenPosition* p) { const intptr_t offset = ReaderOffset() - 1; // Include the tag. const TokenPosition position = ReadPosition(); // read position. if (p != NULL) *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.raw()), /* 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 = */ NULL); // read arguments. ++argument_count; // include receiver SkipInterfaceMemberNameReference(); // interfaceTargetReference return instructions + StaticCall(position, Function::ZoneHandle(Z, function.raw()), argument_count, argument_names, ICData::kSuper, &result_type, type_args_len, /*use_unchecked_entry_point=*/true); } } Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(TokenPosition* p) { const intptr_t offset = ReaderOffset() - 1; // Include the tag. TokenPosition position = ReadPosition(); // read position. if (p != NULL) *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)); 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; } const auto recognized_kind = target.recognized_kind(); if (recognized_kind == MethodRecognizer::kFfiAsFunctionInternal) { return BuildFfiAsFunctionInternal(); } else if (CompilerState::Current().is_aot() && recognized_kind == MethodRecognizer::kFfiNativeCallbackFunction) { return BuildFfiNativeCallbackFunction(); } Fragment instructions; LocalVariable* instance_variable = NULL; const bool special_case_nop_async_stack_trace_helper = !FLAG_causal_async_stacks && recognized_kind == MethodRecognizer::kAsyncStackTraceHelper; const bool special_case_unchecked_cast = klass.IsTopLevel() && (klass.library() == Library::InternalLibrary()) && (target.name() == Symbols::UnsafeCast().raw()); const bool special_case_identical = klass.IsTopLevel() && (klass.library() == Library::CoreLibrary()) && (target.name() == Symbols::Identical().raw()); const bool special_case = special_case_identical || special_case_unchecked_cast || special_case_nop_async_stack_trace_helper; // 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, NULL /* arg count */, NULL /* positional arg count */); // read arguments. ASSERT(!special_case || target.AreValidArguments(type_args_len, argument_count, argument_names, NULL)); // 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_nop_async_stack_trace_helper) { ASSERT(argument_count == 1); instructions += Drop(); instructions += NullConstant(); } 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(); } } return instructions; } Fragment StreamingFlowGraphBuilder::BuildConstructorInvocation( TokenPosition* p) { TokenPosition position = ReadPosition(); // read position. if (p != NULL) *p = position; NameIndex kernel_name = ReadCanonicalNameReference(); // read target_reference. Class& klass = Class::ZoneHandle( Z, H.LookupClassByKernelClass(H.EnclosingName(kernel_name))); Fragment instructions; if (klass.NumTypeArguments() > 0) { if (!klass.IsGeneric()) { Type& type = Type::ZoneHandle(Z, T.ReceiverType(klass).raw()); // TODO(27590): Can we move this code into [ReceiverType]? type ^= ClassFinalizer::FinalizeType(type, ClassFinalizer::kFinalize); TypeArguments& canonicalized_type_arguments = TypeArguments::ZoneHandle(Z, type.arguments()); canonicalized_type_arguments = canonicalized_type_arguments.Canonicalize(thread(), nullptr); instructions += Constant(canonicalized_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 = */ NULL); // read arguments. const Function& target = Function::ZoneHandle( Z, H.LookupConstructorByKernelConstructor(klass, kernel_name)); ++argument_count; instructions += StaticCall(position, target, argument_count, argument_names, ICData::kStatic, /* result_type = */ NULL); return instructions + Drop(); } Fragment StreamingFlowGraphBuilder::BuildNot(TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; TokenPosition operand_position = TokenPosition::kNoSource; Fragment instructions = BuildExpression(&operand_position); // read expression. instructions += CheckBoolean(operand_position); 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(), /* clear_the_temp = */ false); 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); negated = !negated; } Fragment right_value(op == kAnd ? left.CreateTrueSuccessor(flow_graph_builder_) : left.CreateFalseSuccessor(flow_graph_builder_)); if (PeekTag() == kLogicalExpression) { SkipBytes(1); // 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. // Check if the top of the stack is known to be a non-nullable boolean. // Note that in strong mode we know that any value that reaches here // is at least a nullable boolean - so there is no need to compare // with true like in Dart 1. Definition* top = stack()->definition(); const bool is_bool = top->IsStrictCompare() || top->IsBooleanNegate(); if (!is_bool) { right_value += CheckBoolean(position); } 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* position) { if (position != NULL) *position = TokenPosition::kNoSource; 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* position) { if (position != NULL) *position = TokenPosition::kNoSource; 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(); 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::BuildIsExpression(TokenPosition* p) { TokenPosition position = ReadPosition(); // read position. if (p != NULL) *p = position; if (translation_helper_.info().kernel_binary_version() >= 38) { // We do not use the library mode for the type test, which is indicated by // the flag kIsExpressionFlagForNonNullableByDefault. ReadFlags(); } Fragment instructions = BuildExpression(); // read operand. const AbstractType& type = T.BuildType(); // read type. // 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.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::BuildAsExpression(TokenPosition* p) { TokenPosition position = ReadPosition(); // read position. if (p != NULL) *p = position; const uint8_t flags = ReadFlags(); // read flags. const bool is_type_error = (flags & kAsExpressionFlagTypeError) != 0; Fragment instructions = BuildExpression(); // read operand. const AbstractType& type = T.BuildType(); // read type. if (type.IsInstantiated() && type.IsTopTypeForSubtyping()) { // We already evaluated the operand on the left and just leave it there as // the result of the `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 += LoadLocal(MakeTemporary()); instructions += B->AssertAssignableLoadTypeArguments( position, type, is_type_error ? Symbols::Empty() : Symbols::InTypeCast(), AssertAssignableInstr::kInsertedByFrontend); instructions += Drop(); } return instructions; } Fragment StreamingFlowGraphBuilder::BuildTypeLiteral(TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; 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) { if (position != NULL) *position = TokenPosition::kNoSource; return LoadLocal(parsed_function()->receiver_var()); } Fragment StreamingFlowGraphBuilder::BuildRethrow(TokenPosition* p) { TokenPosition position = ReadPosition(); // read position. if (p != NULL) *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 != NULL) *p = position; Fragment instructions; instructions += BuildExpression(); // read expression. if (NeedsDebugStepCheck(stack(), position)) { instructions = DebugStepCheck(position) + instructions; } instructions += ThrowException(position); ASSERT(instructions.is_closed()); return instructions; } Fragment StreamingFlowGraphBuilder::BuildListLiteral(TokenPosition* p) { TokenPosition position = ReadPosition(); // read position. if (p != NULL) *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); if (length == 0) { instructions += IntConstant(0); instructions += StaticCall( position, Function::ZoneHandle(Z, I->object_store()->growable_list_factory()), 2, ICData::kStatic); return instructions; } 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& factory_class = Class::Handle(Z, Library::LookupCoreClass(Symbols::List())); const Function& factory_method = Function::ZoneHandle( Z, factory_class.LookupFactory( Library::PrivateCoreLibName(Symbols::ListLiteralFactory()))); 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 != NULL) *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::BuildFunctionExpression() { ReadPosition(); // read position. return BuildFunctionNode(TokenPosition::kNoSource, StringIndex()); } Fragment StreamingFlowGraphBuilder::BuildLet(TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; Fragment instructions = BuildVariableDeclaration(); // read variable. instructions += BuildExpression(); // read body. return instructions; } Fragment StreamingFlowGraphBuilder::BuildBlockExpression() { block_expression_depth_inc(); const intptr_t offset = ReaderOffset() - 1; // Include the tag. Fragment instructions; instructions += EnterScope(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) { if (position != NULL) *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()) { H.ReportError(script_, TokenPosition::kNoSource, "Integer literal %s is out of range", value.ToCString()); UNREACHABLE(); } return Constant(integer); } Fragment StreamingFlowGraphBuilder::BuildStringLiteral( TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; return Constant(H.DartSymbolPlain( ReadStringReference())); // read index into string table. } Fragment StreamingFlowGraphBuilder::BuildIntLiteral(uint8_t payload, TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; int64_t value = static_cast(payload) - SpecializedIntLiteralBias; return IntConstant(value); } Fragment StreamingFlowGraphBuilder::BuildIntLiteral(bool is_negative, TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; int64_t value = is_negative ? -static_cast(ReadUInt()) : ReadUInt(); // read value. return IntConstant(value); } Fragment StreamingFlowGraphBuilder::BuildDoubleLiteral( TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; Double& constant = Double::ZoneHandle( Z, Double::NewCanonical(ReadDouble())); // read double. return Constant(constant); } Fragment StreamingFlowGraphBuilder::BuildBoolLiteral(bool value, TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; return Constant(Bool::Get(value)); } Fragment StreamingFlowGraphBuilder::BuildNullLiteral(TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; return Constant(Instance::ZoneHandle(Z, Instance::null())); } Fragment StreamingFlowGraphBuilder::BuildFutureNullValue( TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; const Class& future = Class::Handle(Z, I->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(); } if (position != nullptr) *position = p; const intptr_t constant_offset = ReadUInt(); Fragment result = Constant( Object::ZoneHandle(Z, constant_reader_.ReadConstant(constant_offset))); return result; } Fragment StreamingFlowGraphBuilder::BuildPartialTearoffInstantiation( TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; // Create a copy of the closure. Fragment instructions = BuildExpression(); LocalVariable* original_closure = MakeTemporary(); instructions += AllocateObject( TokenPosition::kNoSource, Class::ZoneHandle(Z, I->object_store()->closure_class()), 0); 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(); // 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_->StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_delayed_type_arguments(), StoreInstanceFieldInstr::Kind::kInitializing); instructions += Drop(); // Drop type args. // Copy over the target function. instructions += LoadLocal(new_closure); instructions += LoadLocal(original_closure); instructions += flow_graph_builder_->LoadNativeField(Slot::Closure_function()); instructions += flow_graph_builder_->StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_function(), StoreInstanceFieldInstr::Kind::kInitializing); // Copy over the instantiator type arguments. instructions += LoadLocal(new_closure); instructions += LoadLocal(original_closure); instructions += flow_graph_builder_->LoadNativeField( Slot::Closure_instantiator_type_arguments()); instructions += flow_graph_builder_->StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_instantiator_type_arguments(), StoreInstanceFieldInstr::Kind::kInitializing); // 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_->StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_function_type_arguments(), StoreInstanceFieldInstr::Kind::kInitializing); // Copy over the context. instructions += LoadLocal(new_closure); instructions += LoadLocal(original_closure); instructions += flow_graph_builder_->LoadNativeField(Slot::Closure_context()); instructions += flow_graph_builder_->StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_context(), StoreInstanceFieldInstr::Kind::kInitializing); instructions += DropTempsPreserveTop(1); // Drop old closure. return instructions; } Fragment StreamingFlowGraphBuilder::BuildLibraryPrefixAction( TokenPosition* position, const String& selector) { const intptr_t dependency_index = ReadUInt(); const Library& current_library = Library::Handle( Z, Class::Handle(Z, parsed_function()->function().origin()).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(TokenPosition::kNoSource, function, 1, ICData::kStatic); return instructions; } Fragment StreamingFlowGraphBuilder::BuildExpressionStatement() { Fragment instructions = BuildExpression(); // read expression. instructions += Drop(); return instructions; } Fragment StreamingFlowGraphBuilder::BuildBlock() { intptr_t offset = ReaderOffset() - 1; // Include the tag. Fragment instructions; instructions += EnterScope(offset); ReadPosition(); 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() { if (!I->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()) { instructions += BuildStatement(); // read ith statement. } else { SkipStatement(); // read ith statement. } } instructions += ExitScope(offset); return instructions; } Fragment StreamingFlowGraphBuilder::BuildAssertStatement() { if (!I->asserts()) { SetOffset(ReaderOffset() - 1); // Include the tag. SkipStatement(); // read this statement. return Fragment(); } TargetEntryInstr* then; TargetEntryInstr* otherwise; Fragment instructions; // Asserts can be of the following two kinds: // // * `assert(expr)` // * `assert(() { ... })` // // The call to `_AssertionError._evaluateAssertion()` will take care of both // and returns a boolean. instructions += BuildExpression(); // read condition. const TokenPosition condition_start_offset = ReadPosition(); // read condition start offset. const TokenPosition condition_end_offset = ReadPosition(); // read condition end offset. instructions += EvaluateAssertion(); instructions += CheckBoolean(condition_start_offset); instructions += Constant(Bool::True()); instructions += BranchIfEqual(&then, &otherwise, false); const Class& klass = Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::AssertionError())); ASSERT(!klass.IsNull()); const Function& target = Function::ZoneHandle( Z, klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNew())); ASSERT(!target.IsNull()); // Build equivalent of `throw _AssertionError._throwNew(start, end, message)` // expression. We build throw (even through _throwNew already throws) because // call is not a valid last instruction for the block. Blocks can only // terminate with explicit control flow instructions (Branch, Goto, Return // or Throw). Fragment otherwise_fragment(otherwise); 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 += ThrowException(TokenPosition::kNoSource); otherwise_fragment += Drop(); return Fragment(instructions.entry, then); } Fragment StreamingFlowGraphBuilder::BuildLabeledStatement() { // There can be serveral 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(); // 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 = ReadPosition(); // read position. intptr_t target_index = ReadUInt(); // read target index. TryFinallyBlock* outer_finally = NULL; 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(), position)) { instructions += DebugStepCheck(position); } instructions += TranslateFinallyFinalizers(outer_finally, target_context_depth); if (instructions.is_open()) { instructions += Goto(destination); } return instructions; } Fragment StreamingFlowGraphBuilder::BuildWhileStatement() { ASSERT(block_expression_depth() == 0); // no while in block-expr loop_depth_inc(); const TokenPosition position = ReadPosition(); // read position. TestFragment condition = TranslateConditionForControl(); // read condition. const Fragment body = BuildStatement(); // 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); ASSERT(B->GetStackDepth() == 0); loop += CheckStackOverflow(position); 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() { ASSERT(block_expression_depth() == 0); // no do-while in block-expr loop_depth_inc(); const TokenPosition position = ReadPosition(); // read position. Fragment body = BuildStatement(); // read body. if (body.is_closed()) { SkipExpression(); // read condition. loop_depth_dec(); return body; } TestFragment condition = TranslateConditionForControl(); JoinEntryInstr* join = BuildJoinEntry(); Fragment loop(join); ASSERT(B->GetStackDepth() == 0); loop += CheckStackOverflow(position); 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() { intptr_t offset = ReaderOffset() - 1; // Include the tag. const TokenPosition position = ReadPosition(); // read position. 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(); // 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 += BuildStatement(); // 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(position); // 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::BuildForInStatement(bool async) { intptr_t offset = ReaderOffset() - 1; // Include the tag. const TokenPosition position = ReadPosition(); // read position. TokenPosition body_position = ReadPosition(); // read body position. intptr_t variable_kernel_position = ReaderOffset() + data_program_offset_; SkipVariableDeclaration(); // read variable. TokenPosition iterable_position = TokenPosition::kNoSource; Fragment instructions = BuildExpression(&iterable_position); // read iterable. const String& iterator_getter = String::ZoneHandle(Z, Field::GetterSymbol(Symbols::Iterator())); instructions += InstanceCall(iterable_position, iterator_getter, Token::kGET, 1); LocalVariable* iterator = scopes()->iterator_variables[for_in_depth()]; instructions += StoreLocal(TokenPosition::kNoSource, iterator); instructions += Drop(); for_in_depth_inc(); loop_depth_inc(); Fragment condition = LoadLocal(iterator); condition += InstanceCall(iterable_position, Symbols::MoveNext(), Token::kILLEGAL, 1); TargetEntryInstr* body_entry; TargetEntryInstr* loop_exit; condition += BranchIfTrue(&body_entry, &loop_exit, false); Fragment body(body_entry); body += EnterScope(offset); body += LoadLocal(iterator); const String& current_getter = String::ZoneHandle(Z, Field::GetterSymbol(Symbols::Current())); body += InstanceCall(body_position, current_getter, Token::kGET, 1); body += StoreLocal(TokenPosition::kNoSource, LookupVariable(variable_kernel_position)); body += Drop(); body += BuildStatement(); // read body. body += ExitScope(offset); if (body.is_open()) { JoinEntryInstr* join = BuildJoinEntry(); instructions += Goto(join); body += Goto(join); Fragment loop(join); loop += CheckStackOverflow(position); // may have non-empty stack loop += condition; } else { instructions += condition; } loop_depth_dec(); for_in_depth_dec(); return Fragment(instructions.entry, loop_exit); } Fragment StreamingFlowGraphBuilder::BuildSwitchStatement() { ReadPosition(); // read position. // We need the number of cases. So start by getting that, then go back. intptr_t offset = ReaderOffset(); SkipExpression(); // temporarily skip condition int case_count = ReadListLength(); // read number of cases. SetOffset(offset); SwitchBlock block(flow_graph_builder_, case_count); // Instead of using a variable we should reuse the expression on the stack, // since it won't be assigned again, we don't need phi nodes. Fragment head_instructions = BuildExpression(); // read condition. head_instructions += StoreLocal(TokenPosition::kNoSource, scopes()->switch_variable); head_instructions += Drop(); case_count = ReadListLength(); // read number of cases. // Phase 1: Generate bodies and try to find out whether a body will be target // of a jump due to: // * `continue case_label` // * `case e1: case e2: body` Fragment* body_fragments = Z->Alloc(case_count); intptr_t* case_expression_offsets = Z->Alloc(case_count); int default_case = -1; for (intptr_t i = 0; i < case_count; ++i) { case_expression_offsets[i] = ReaderOffset(); int expression_count = ReadListLength(); // read number of expressions. for (intptr_t j = 0; j < expression_count; ++j) { ReadPosition(); // read jth position. SkipExpression(); // read jth expression. } bool is_default = ReadBool(); // read is_default. if (is_default) default_case = i; Fragment& body_fragment = body_fragments[i] = BuildStatement(); // read body. if (body_fragment.entry == NULL) { // Make a NOP in order to ensure linking works properly. body_fragment = NullConstant(); body_fragment += Drop(); } // The Dart language specification mandates fall-throughs in [SwitchCase]es // to be runtime errors. if (!is_default && body_fragment.is_open() && (i < (case_count - 1))) { const Class& klass = Class::ZoneHandle( Z, Library::LookupCoreClass(Symbols::FallThroughError())); ASSERT(!klass.IsNull()); const auto& error = klass.EnsureIsFinalized(thread()); ASSERT(error == Error::null()); GrowableHandlePtrArray pieces(Z, 3); pieces.Add(Symbols::FallThroughError()); pieces.Add(Symbols::Dot()); pieces.Add(H.DartSymbolObfuscate("_create")); const Function& constructor = Function::ZoneHandle( Z, klass.LookupConstructorAllowPrivate(String::ZoneHandle( Z, Symbols::FromConcatAll(H.thread(), pieces)))); ASSERT(!constructor.IsNull()); const String& url = H.DartSymbolPlain( parsed_function()->function().ToLibNamePrefixedQualifiedCString()); // Create instance of _FallThroughError body_fragment += AllocateObject(TokenPosition::kNoSource, klass, 0); LocalVariable* instance = MakeTemporary(); // Call _FallThroughError._create constructor. body_fragment += LoadLocal(instance); // this body_fragment += Constant(url); // url body_fragment += NullConstant(); // line body_fragment += StaticCall(TokenPosition::kNoSource, constructor, 3, ICData::kStatic); body_fragment += Drop(); // Throw the exception body_fragment += ThrowException(TokenPosition::kNoSource); body_fragment += Drop(); } // 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) { block.DestinationDirect(i); } } intptr_t end_offset = ReaderOffset(); // Phase 2: Generate everything except the real bodies: // * 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) Fragment current_instructions = head_instructions; for (intptr_t i = 0; i < case_count; ++i) { SetOffset(case_expression_offsets[i]); int expression_count = ReadListLength(); // read length of expressions. 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 += body_fragments[i]; } else { current_instructions += body_fragments[i]; } } else { JoinEntryInstr* body_join = NULL; if (block.HadJumper(i)) { body_join = block.DestinationDirect(i); body_fragments[i] = Fragment(body_join) + body_fragments[i]; } for (intptr_t j = 0; j < expression_count; ++j) { TargetEntryInstr* then; TargetEntryInstr* otherwise; TokenPosition position = ReadPosition(); // read jth position. current_instructions += Constant( Instance::ZoneHandle(Z, constant_reader_.ReadConstantExpression())); current_instructions += LoadLocal(scopes()->switch_variable); current_instructions += InstanceCall(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 != NULL) { // 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 signle branch to the body, so we will just append // the body fragment. then_fragment += body_fragments[i]; } current_instructions = Fragment(otherwise); } } } if (case_count > 0 && default_case < 0) { // 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 = body_fragments[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(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. } SetOffset(end_offset); return Fragment(head_instructions.entry, current_instructions.current); } Fragment StreamingFlowGraphBuilder::BuildContinueSwitchStatement() { TokenPosition position = ReadPosition(); // read position. intptr_t target_index = ReadUInt(); // read target index. TryFinallyBlock* outer_finally = NULL; 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(), position)) { instructions += DebugStepCheck(position); } instructions += Goto(entry); } return instructions; } Fragment StreamingFlowGraphBuilder::BuildIfStatement() { ReadPosition(); // read position. TestFragment condition = TranslateConditionForControl(); Fragment then_fragment(condition.CreateTrueSuccessor(flow_graph_builder_)); then_fragment += BuildStatement(); // read then. Fragment otherwise_fragment( condition.CreateFalseSuccessor(flow_graph_builder_)); otherwise_fragment += BuildStatement(); // 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 = ReadPosition(); // read position. Tag tag = ReadTag(); // read first part of expression. bool inside_try_finally = try_finally_block() != NULL; Fragment instructions = tag == kNothing ? NullConstant() : BuildExpression(); // read rest of expression. if (instructions.is_open()) { if (inside_try_finally) { ASSERT(scopes()->finally_return_variable != NULL); const Function& function = parsed_function()->function(); if (NeedsDebugStepCheck(function, position)) { instructions += DebugStepCheck(position); } instructions += StoreLocal(position, scopes()->finally_return_variable); instructions += Drop(); instructions += TranslateFinallyFinalizers(NULL, -1); if (instructions.is_open()) { instructions += LoadLocal(scopes()->finally_return_variable); instructions += Return(TokenPosition::kNoSource); } } else { instructions += Return(position); } } else { Pop(); } return instructions; } Fragment StreamingFlowGraphBuilder::BuildTryCatch() { ASSERT(block_expression_depth() == 0); // no try-catch in block-expr InlineBailout("kernel::FlowgraphBuilder::VisitTryCatch"); intptr_t try_handler_index = AllocateTryIndex(); Fragment try_body = TryCatch(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 += BuildStatement(); // 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 position = 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 += BuildStatement(); // 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( position, 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() { ASSERT(block_expression_depth() == 0); // no try-finally in block-expr // 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"); // 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 = TryCatch(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 += BuildStatement(); // 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 += BuildStatement(); // 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); finally_body += BuildStatement(); // read finalizer if (finally_body.is_open()) { finally_body += LoadLocal(CurrentException()); finally_body += LoadLocal(CurrentStackTrace()); finally_body += RethrowException(TokenPosition::kNoSource, try_handler_index); Drop(); } catch_depth_dec(); return Fragment(try_body.entry, after_try); } Fragment StreamingFlowGraphBuilder::BuildYieldStatement() { TokenPosition position = ReadPosition(); // read position. uint8_t flags = ReadByte(); // read flags. ASSERT(flags == kNativeYieldFlags); // Must have been desugared. // Setup yield/continue point: // // ... // :await_jump_var = index; // :await_ctx_var = :current_context_var // return // // Continuation: // Drop(1) // ... // // BuildGraphOfFunction will create a dispatch that jumps to // Continuation<:await_jump_var> upon entry to the function. // const intptr_t new_yield_pos = yield_continuations().length() + 1; Fragment instructions = IntConstant(new_yield_pos); instructions += StoreLocal(TokenPosition::kNoSource, scopes()->yield_jump_variable); instructions += Drop(); instructions += LoadLocal(parsed_function()->current_context_var()); instructions += StoreLocal(TokenPosition::kNoSource, scopes()->yield_context_variable); instructions += Drop(); instructions += BuildExpression(); // read expression. instructions += Return(position, new_yield_pos); // Note: DropTempsInstr serves as an anchor instruction. It will not // be linked into the resulting graph. DropTempsInstr* anchor = new (Z) DropTempsInstr(0, NULL); yield_continuations().Add(YieldContinuation(anchor, CurrentTryIndex())); Fragment continuation(instructions.entry, anchor); RELEASE_ASSERT(parsed_function()->function().IsAsyncClosure() || parsed_function()->function().IsAsyncGenClosure() || parsed_function()->function().IsSyncGenClosure()); // TODO(43900): Only emit this when needed. { // If function is {async, async gen, sync yielding} closure it takes three // parameters where the second and the third are exception and stack_trace. // Check if exception is non-null and rethrow it. // // :sync_op(:iterator, [:exception, :stack_trace]) { // or: // :async_op(:result, [:exception, :stack_trace]) { // ... // Continuation: // if (:exception != null) rethrow(:exception, :stack_trace); // ... // } // LocalVariable* exception_var = parsed_function()->ParameterVariable(2); LocalVariable* stack_trace_var = parsed_function()->ParameterVariable(3); ASSERT(exception_var->name().raw() == Symbols::ExceptionParameter().raw()); ASSERT(stack_trace_var->name().raw() == Symbols::StackTraceParameter().raw()); TargetEntryInstr* no_error; TargetEntryInstr* error; continuation += LoadLocal(exception_var); continuation += BranchIfNull(&no_error, &error); Fragment rethrow(/*instruction=*/error); rethrow += LoadLocal(exception_var); rethrow += LoadLocal(stack_trace_var); rethrow += RethrowException(position, kInvalidTryIndex); Drop(); // Set current to the end of the no_error branch. continuation = Fragment(/*entry=*/continuation.entry, /*current=*/no_error); } return continuation; } Fragment StreamingFlowGraphBuilder::BuildVariableDeclaration() { 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 if (helper.IsConst()) { // Read const initializer form current position. const Instance& constant_value = Instance::ZoneHandle(Z, constant_reader_.ReadConstantExpression()); variable->SetConstValue(constant_value); instructions += Constant(constant_value); } 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. TokenPosition debug_position = Utils::Maximum(helper.position_, helper.equals_position_); if (NeedsDebugStepCheck(stack(), debug_position)) { instructions = DebugStepCheck(debug_position) + instructions; } instructions += StoreLocal(helper.position_, variable); instructions += Drop(); return instructions; } Fragment StreamingFlowGraphBuilder::BuildFunctionDeclaration() { TokenPosition position = ReadPosition(); // read position. intptr_t variable_offset = ReaderOffset() + data_program_offset_; // read variable declaration. VariableDeclarationHelper helper(this); helper.ReadUntilExcluding(VariableDeclarationHelper::kEnd); Fragment instructions = DebugStepCheck(position); instructions += BuildFunctionNode(position, helper.name_index_); instructions += StoreLocal(position, LookupVariable(variable_offset)); instructions += Drop(); return instructions; } Fragment StreamingFlowGraphBuilder::BuildFunctionNode( TokenPosition parent_position, StringIndex name_index) { intptr_t offset = ReaderOffset(); FunctionNodeHelper function_node_helper(this); function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kTypeParameters); TokenPosition position = function_node_helper.position_; bool declaration = name_index >= 0; if (declaration) { position = parent_position; } if (!position.IsReal()) { // Positions has to be unique in regards to the parent. // A non-real at this point is probably -1, we cannot blindly use that // as others might use it too. Create a new dummy non-real TokenPosition. position = TokenPosition(offset).ToSynthetic(); } // The VM has a per-isolate table of functions indexed by the enclosing // function and token position. Function& function = Function::ZoneHandle(Z); // NOTE: This is not TokenPosition in the general sense! function = I->LookupClosureFunction(parsed_function()->function(), position); if (function.IsNull()) { for (intptr_t i = 0; i < scopes()->function_scopes.length(); ++i) { if (scopes()->function_scopes[i].kernel_offset != offset) { continue; } const String* name; if (declaration) { name = &H.DartSymbolObfuscate(name_index); } else { name = &Symbols::AnonymousClosure(); } // NOTE: This is not TokenPosition in the general sense! if (!closure_owner_.IsNull()) { function = Function::NewClosureFunctionWithKind( FunctionLayout::kClosureFunction, *name, parsed_function()->function(), position, closure_owner_); } else { function = Function::NewClosureFunction( *name, parsed_function()->function(), position); } function.set_is_debuggable(function_node_helper.dart_async_marker_ == FunctionNodeHelper::kSync); switch (function_node_helper.dart_async_marker_) { case FunctionNodeHelper::kSyncStar: function.set_modifier(FunctionLayout::kSyncGen); break; case FunctionNodeHelper::kAsync: function.set_modifier(FunctionLayout::kAsync); function.set_is_inlinable(!FLAG_causal_async_stacks); break; case FunctionNodeHelper::kAsyncStar: function.set_modifier(FunctionLayout::kAsyncGen); function.set_is_inlinable(!FLAG_causal_async_stacks); break; default: // no special modifier break; } function.set_is_generated_body(function_node_helper.async_marker_ == FunctionNodeHelper::kSyncYielding); // sync* functions contain two nested synthetic functions, the first of // which (sync_op_gen) is a regular sync function so we need to manually // label it generated: if (function.parent_function() != Function::null()) { const auto& parent = Function::Handle(function.parent_function()); if (parent.IsSyncGenerator()) { function.set_is_generated_body(true); } } // Note: Is..() methods use the modifiers set above, so order matters. if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) { function.set_is_inlinable(!FLAG_causal_async_stacks && !FLAG_lazy_async_stacks); } function.set_end_token_pos(function_node_helper.end_position_); LocalScope* scope = scopes()->function_scopes[i].scope; const ContextScope& context_scope = ContextScope::Handle( Z, scope->PreserveOuterScope(flow_graph_builder_->context_depth_)); function.set_context_scope(context_scope); function.set_kernel_offset(offset); type_translator_.SetupFunctionParameters(Class::Handle(Z), function, false, // is_method true, // is_closure &function_node_helper); // type_translator_.SetupUnboxingInfoMetadata is not called here at the // moment because closures do not have unboxed parameters and return value function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kEnd); // Finalize function type. Type& signature_type = Type::Handle(Z, function.SignatureType()); signature_type ^= ClassFinalizer::FinalizeType(signature_type); function.SetSignatureType(signature_type); I->AddClosureFunction(function); break; } } function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kEnd); Fragment instructions = flow_graph_builder_->AllocateClosure(TokenPosition::kNoSource, function); LocalVariable* closure = MakeTemporary(); // The function signature can have uninstantiated class type parameters. if (!function.HasInstantiatedSignature(kCurrentClass)) { instructions += LoadLocal(closure); instructions += LoadInstantiatorTypeArguments(); instructions += flow_graph_builder_->StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_instantiator_type_arguments(), StoreInstanceFieldInstr::Kind::kInitializing); } // 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_->StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_function_type_arguments(), StoreInstanceFieldInstr::Kind::kInitializing); if (function.IsGeneric()) { // Only generic functions need to have properly initialized // delayed_type_arguments. instructions += LoadLocal(closure); instructions += Constant(Object::empty_type_arguments()); instructions += flow_graph_builder_->StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_delayed_type_arguments(), StoreInstanceFieldInstr::Kind::kInitializing); } // Store the function and the context in the closure. instructions += LoadLocal(closure); instructions += Constant(function); instructions += flow_graph_builder_->StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_function(), StoreInstanceFieldInstr::Kind::kInitializing); instructions += LoadLocal(closure); instructions += LoadLocal(parsed_function()->current_context_var()); instructions += flow_graph_builder_->StoreInstanceField( TokenPosition::kNoSource, Slot::Closure_context(), StoreInstanceFieldInstr::Kind::kInitializing); return instructions; } Fragment StreamingFlowGraphBuilder::BuildFfiAsFunctionInternal() { const intptr_t argc = ReadUInt(); // read argument count. ASSERT(argc == 1); // pointer const intptr_t list_length = ReadListLength(); // read types list length. ASSERT(list_length == 2); // dart signature, then native signature const TypeArguments& type_arguments = T.BuildTypeArguments(list_length); // read types. Fragment code; const intptr_t positional_count = ReadListLength(); // read positional argument count ASSERT(positional_count == 1); code += BuildExpression(); // build first positional argument (pointer) const intptr_t named_args_len = ReadListLength(); // skip (empty) named arguments list ASSERT(named_args_len == 0); code += B->BuildFfiAsFunctionInternalCall(type_arguments); return code; } Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction() { // The call-site must look like this (guaranteed by the FE which inserts it): // // _nativeCallbackFunction(target, exceptionalReturn) // // The FE also guarantees that all three arguments are constants. const intptr_t argc = ReadUInt(); // read argument count ASSERT(argc == 2); // target, exceptionalReturn const intptr_t list_length = ReadListLength(); // read types list length ASSERT(list_length == 1); // native signature const TypeArguments& type_arguments = T.BuildTypeArguments(list_length); // read types. ASSERT(type_arguments.Length() == 1 && type_arguments.IsInstantiated()); const Function& native_sig = Function::Handle( Z, Type::CheckedHandle(Z, type_arguments.TypeAt(0)).signature()); Fragment code; const intptr_t positional_count = ReadListLength(); // read positional argument count ASSERT(positional_count == 2); // Read target expression and extract the target function. code += BuildExpression(); // build first positional argument (target) Definition* target_def = B->Peek(); ASSERT(target_def->IsConstant()); const Closure& target_closure = Closure::Cast(target_def->AsConstant()->value()); ASSERT(!target_closure.IsNull()); Function& target = Function::Handle(Z, target_closure.function()); ASSERT(!target.IsNull() && target.IsImplicitClosureFunction()); target = target.parent_function(); code += Drop(); // Build second positional argument (exceptionalReturn). code += BuildExpression(); Definition* exceptional_return_def = B->Peek(); ASSERT(exceptional_return_def->IsConstant()); const Instance& exceptional_return = Instance::Cast(exceptional_return_def->AsConstant()->value()); code += Drop(); const intptr_t named_args_len = ReadListLength(); // skip (empty) named arguments list ASSERT(named_args_len == 0); const Function& result = Function::ZoneHandle(Z, compiler::ffi::NativeCallbackFunction( native_sig, target, exceptional_return)); code += Constant(result); auto& ffi_callback_functions = GrowableObjectArray::Handle(Z); ffi_callback_functions ^= I->object_store()->ffi_callback_functions(); if (ffi_callback_functions.IsNull()) { ffi_callback_functions ^= GrowableObjectArray::New(); I->object_store()->set_ffi_callback_functions(ffi_callback_functions); } ffi_callback_functions.Add(result); return code; } } // namespace kernel } // namespace dart