// Copyright (c) 2012, 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. #if !defined(DART_PRECOMPILED_RUNTIME) #include "vm/scopes.h" #include "vm/compiler/backend/slot.h" #include "vm/object.h" #include "vm/stack_frame.h" #include "vm/symbols.h" namespace dart { DEFINE_FLAG(bool, share_enclosing_context, true, "Allocate captured variables in the existing context of an " "enclosing scope (up to innermost loop) and spare the allocation " "of a local context."); int SourceLabel::FunctionLevel() const { ASSERT(owner() != NULL); return owner()->function_level(); } LocalScope::LocalScope(LocalScope* parent, int function_level, int loop_level) : parent_(parent), child_(NULL), sibling_(NULL), function_level_(function_level), loop_level_(loop_level), context_level_(LocalScope::kUninitializedContextLevel), begin_token_pos_(TokenPosition::kNoSource), end_token_pos_(TokenPosition::kNoSource), variables_(), labels_(), context_variables_(), context_slots_(new (Thread::Current()->zone()) ZoneGrowableArray()), referenced_() { // Hook this node into the children of the parent, unless the parent has a // different function_level, since the local scope of a nested function can // be discarded after it has been parsed. if ((parent != NULL) && (parent->function_level() == function_level)) { sibling_ = parent->child_; parent->child_ = this; } } bool LocalScope::IsNestedWithin(LocalScope* scope) const { const LocalScope* current_scope = this; while (current_scope != NULL) { if (current_scope == scope) { return true; } current_scope = current_scope->parent(); } return false; } bool LocalScope::AddVariable(LocalVariable* variable) { ASSERT(variable != NULL); if (LocalLookupVariable(variable->name()) != NULL) { return false; } variables_.Add(variable); if (variable->owner() == NULL) { // Variables must be added to their owner scope first. Subsequent calls // to 'add' treat the variable as an alias. variable->set_owner(this); } return true; } bool LocalScope::InsertParameterAt(intptr_t pos, LocalVariable* parameter) { ASSERT(parameter != NULL); if (LocalLookupVariable(parameter->name()) != NULL) { return false; } variables_.InsertAt(pos, parameter); // InsertParameterAt is not used to add aliases of parameters. ASSERT(parameter->owner() == NULL); parameter->set_owner(this); return true; } bool LocalScope::AddLabel(SourceLabel* label) { if (LocalLookupLabel(label->name()) != NULL) { return false; } labels_.Add(label); if (label->owner() == NULL) { // Labels must be added to their owner scope first. Subsequent calls // to 'add' treat the label as an alias. label->set_owner(this); } return true; } void LocalScope::MoveLabel(SourceLabel* label) { ASSERT(LocalLookupLabel(label->name()) == NULL); ASSERT(label->kind() == SourceLabel::kForward); labels_.Add(label); label->set_owner(this); } NameReference* LocalScope::FindReference(const String& name) const { ASSERT(name.IsSymbol()); intptr_t num_references = referenced_.length(); for (intptr_t i = 0; i < num_references; i++) { if (name.ptr() == referenced_[i]->name().ptr()) { return referenced_[i]; } } return NULL; } void LocalScope::AddReferencedName(TokenPosition token_pos, const String& name) { if (LocalLookupVariable(name) != NULL) { return; } NameReference* ref = FindReference(name); if (ref != NULL) { ref->set_token_pos(token_pos); return; } ref = new NameReference(token_pos, name); referenced_.Add(ref); // Add name reference in innermost enclosing scopes that do not // define a local variable with this name. LocalScope* scope = this->parent(); while (scope != NULL && (scope->LocalLookupVariable(name) == NULL)) { scope->referenced_.Add(ref); scope = scope->parent(); } } TokenPosition LocalScope::PreviousReferencePos(const String& name) const { NameReference* ref = FindReference(name); if (ref != NULL) { return ref->token_pos(); } return TokenPosition::kNoSource; } void LocalScope::AllocateContextVariable(LocalVariable* variable, LocalScope** context_owner) { ASSERT(variable->is_captured()); ASSERT(variable->owner() == this); // The context level in the owner scope of a captured variable indicates at // code generation time how far to walk up the context chain in order to // access the variable from the current context level. if ((*context_owner) == NULL) { ASSERT(num_context_variables() == 0); // This scope becomes the current context owner. set_context_level(1); *context_owner = this; } else if (!FLAG_share_enclosing_context && ((*context_owner) != this)) { // The captured variable is in a child scope of the context owner and we do // not share contexts. // This scope will allocate and chain a new context. ASSERT(num_context_variables() == 0); // This scope becomes the current context owner. set_context_level((*context_owner)->context_level() + 1); *context_owner = this; } else if ((*context_owner)->loop_level() < loop_level()) { ASSERT(FLAG_share_enclosing_context); // The captured variable is at a deeper loop level than the current context. // This scope will allocate and chain a new context. ASSERT(num_context_variables() == 0); // This scope becomes the current context owner. set_context_level((*context_owner)->context_level() + 1); *context_owner = this; } else { // Allocate the captured variable in the current context. if (!HasContextLevel()) { ASSERT(variable->owner() != *context_owner); set_context_level((*context_owner)->context_level()); } else { ASSERT(context_level() == (*context_owner)->context_level()); } } (*context_owner)->AddContextVariable(variable); } void LocalScope::AddContextVariable(LocalVariable* variable) { variable->set_index(VariableIndex(context_variables_.length())); context_variables_.Add(variable); context_slots_->Add( &Slot::GetContextVariableSlotFor(Thread::Current(), *variable)); } VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index, int num_parameters, VariableIndex first_local_index, LocalScope* context_owner, bool* found_captured_variables) { // We should not allocate variables of nested functions while compiling an // enclosing function. ASSERT(function_level() == 0); ASSERT(num_parameters >= 0); // Parameters must be listed first and must all appear in the top scope. ASSERT(num_parameters <= num_variables()); int pos = 0; // Current variable position. VariableIndex next_index = first_parameter_index; // Current free frame index. LocalVariable* await_jump_var = nullptr; LocalVariable* async_future = nullptr; LocalVariable* controller = nullptr; LocalVariable* chained_future = nullptr; LocalVariable* is_sync = nullptr; for (intptr_t i = 0; i < num_variables(); i++) { LocalVariable* variable = VariableAt(i); if (variable->owner() == this) { if (variable->is_captured()) { if (variable->name().Equals(Symbols::AwaitJumpVar())) { await_jump_var = variable; } else if (variable->name().Equals(Symbols::AsyncFuture())) { async_future = variable; } else if (variable->name().Equals(Symbols::Controller())) { controller = variable; } else if (variable->is_chained_future()) { chained_future = variable; } else if (variable->name().Equals(Symbols::is_sync())) { is_sync = variable; } } } } // If we are in an async/async* function, force :await_jump_var and // :async_future to be at fixed locations in the slot. if (await_jump_var != nullptr) { AllocateContextVariable(await_jump_var, &context_owner); *found_captured_variables = true; ASSERT(await_jump_var->index().value() == Context::kAwaitJumpVarIndex); } if (async_future != nullptr) { AllocateContextVariable(async_future, &context_owner); *found_captured_variables = true; ASSERT(async_future->index().value() == Context::kAsyncFutureIndex); } if (controller != nullptr) { AllocateContextVariable(controller, &context_owner); *found_captured_variables = true; ASSERT(controller->index().value() == Context::kControllerIndex); } if (chained_future != nullptr) { AllocateContextVariable(chained_future, &context_owner); *found_captured_variables = true; ASSERT(chained_future->index().value() == chained_future->expected_context_index()); } if (is_sync != nullptr) { AllocateContextVariable(is_sync, &context_owner); *found_captured_variables = true; ASSERT(is_sync->index().value() == Context::kIsSyncIndex); } while (pos < num_parameters) { LocalVariable* parameter = VariableAt(pos); pos++; // Parsing formal parameter default values may add local variable aliases // to the local scope before the formal parameters are added. However, // the parameters get inserted in front of the aliases, therefore, no // aliases can be encountered among the first num_parameters variables. ASSERT(parameter->owner() == this); if (parameter->is_captured()) { // A captured parameter has a slot allocated in the frame and one in the // context, where it gets copied to. The parameter index reflects the // context allocation index. next_index = VariableIndex(next_index.value() - 1); AllocateContextVariable(parameter, &context_owner); *found_captured_variables = true; } else { parameter->set_index(next_index); next_index = VariableIndex(next_index.value() - 1); } } // No overlapping of parameters and locals. ASSERT(next_index.value() >= first_local_index.value()); next_index = first_local_index; while (pos < num_variables()) { LocalVariable* variable = VariableAt(pos); if (variable->owner() == this) { if (variable->is_captured()) { // Skip the variables already pre-allocated above. if (variable != await_jump_var && variable != async_future && variable != controller && variable != chained_future && variable != is_sync) { AllocateContextVariable(variable, &context_owner); *found_captured_variables = true; } } else { variable->set_index(next_index); next_index = VariableIndex(next_index.value() - 1); } } pos++; } // Allocate variables of all children. VariableIndex min_index = next_index; LocalScope* child = this->child(); while (child != NULL) { // Ignored, since no parameters. const VariableIndex dummy_parameter_index(0); // No parameters in children scopes. const int num_parameters_in_child = 0; VariableIndex child_next_index = child->AllocateVariables( dummy_parameter_index, num_parameters_in_child, next_index, context_owner, found_captured_variables); if (child_next_index.value() < min_index.value()) { min_index = child_next_index; } child = child->sibling(); } return min_index; } // The parser creates internal variables that start with ":" static bool IsFilteredIdentifier(const String& str) { ASSERT(str.Length() > 0); if (str.ptr() == Symbols::AsyncOperation().ptr()) { // Keep :async_op for asynchronous debugging. return false; } if (str.ptr() == Symbols::AsyncFuture().ptr()) { // Keep :async_future for asynchronous debugging. return false; } if (str.ptr() == Symbols::ControllerStream().ptr()) { // Keep :controller_stream for asynchronous debugging. return false; } if (str.ptr() == Symbols::AwaitJumpVar().ptr()) { // Keep :await_jump_var for asynchronous debugging. return false; } if (str.ptr() == Symbols::is_sync().ptr()) { // Keep :is_sync for asynchronous debugging. return false; } if (str.ptr() == Symbols::FunctionTypeArgumentsVar().ptr()) { // Keep :function_type_arguments for accessing type variables in debugging. return false; } return str.CharAt(0) == ':'; } LocalVarDescriptorsPtr LocalScope::GetVarDescriptors( const Function& func, ZoneGrowableArray* context_level_array) { LocalVarDescriptorsBuilder vars; vars.AddDeoptIdToContextLevelMappings(context_level_array); // First enter all variables from scopes of outer functions. const ContextScope& context_scope = ContextScope::Handle(func.context_scope()); if (!context_scope.IsNull()) { ASSERT(func.HasParent()); for (int i = 0; i < context_scope.num_variables(); i++) { String& name = String::Handle(context_scope.NameAt(i)); UntaggedLocalVarDescriptors::VarInfoKind kind; if (!IsFilteredIdentifier(name)) { kind = UntaggedLocalVarDescriptors::kContextVar; } else { continue; } LocalVarDescriptorsBuilder::VarDesc desc; desc.name = &name; desc.info.set_kind(kind); desc.info.scope_id = context_scope.ContextLevelAt(i); desc.info.declaration_pos = context_scope.DeclarationTokenIndexAt(i); desc.info.begin_pos = begin_token_pos(); desc.info.end_pos = end_token_pos(); ASSERT(desc.info.begin_pos <= desc.info.end_pos); desc.info.set_index(context_scope.ContextIndexAt(i)); vars.Add(desc); } } // Now collect all variables from local scopes. int16_t scope_id = 0; CollectLocalVariables(&vars, &scope_id); return vars.Done(); } // Add visible variables that are declared in this scope to vars, then // collect visible variables of children, followed by siblings. void LocalScope::CollectLocalVariables(LocalVarDescriptorsBuilder* vars, int16_t* scope_id) { (*scope_id)++; for (int i = 0; i < this->variables_.length(); i++) { LocalVariable* var = variables_[i]; if ((var->owner() == this) && !var->is_invisible()) { if (var->name().ptr() == Symbols::CurrentContextVar().ptr()) { // This is the local variable in which the function saves its // own context before calling a closure function. LocalVarDescriptorsBuilder::VarDesc desc; desc.name = &var->name(); desc.info.set_kind(UntaggedLocalVarDescriptors::kSavedCurrentContext); desc.info.scope_id = 0; desc.info.declaration_pos = TokenPosition::kMinSource; desc.info.begin_pos = TokenPosition::kMinSource; desc.info.end_pos = TokenPosition::kMinSource; desc.info.set_index(var->index().value()); vars->Add(desc); } else if (!IsFilteredIdentifier(var->name())) { // This is a regular Dart variable, either stack-based or captured. LocalVarDescriptorsBuilder::VarDesc desc; desc.name = &var->name(); if (var->is_captured()) { desc.info.set_kind(UntaggedLocalVarDescriptors::kContextVar); ASSERT(var->owner() != NULL); ASSERT(var->owner()->context_level() >= 0); desc.info.scope_id = var->owner()->context_level(); } else { desc.info.set_kind(UntaggedLocalVarDescriptors::kStackVar); desc.info.scope_id = *scope_id; } desc.info.set_index(var->index().value()); desc.info.declaration_pos = var->declaration_token_pos(); desc.info.begin_pos = var->token_pos(); desc.info.end_pos = var->owner()->end_token_pos(); vars->Add(desc); } } } LocalScope* child = this->child(); while (child != NULL) { child->CollectLocalVariables(vars, scope_id); child = child->sibling(); } } SourceLabel* LocalScope::LocalLookupLabel(const String& name) const { ASSERT(name.IsSymbol()); for (intptr_t i = 0; i < labels_.length(); i++) { SourceLabel* label = labels_[i]; if (label->name().ptr() == name.ptr()) { return label; } } return NULL; } LocalVariable* LocalScope::LocalLookupVariable(const String& name) const { ASSERT(name.IsSymbol()); for (intptr_t i = 0; i < variables_.length(); i++) { LocalVariable* var = variables_[i]; ASSERT(var->name().IsSymbol()); if (var->name().ptr() == name.ptr()) { return var; } } return NULL; } LocalVariable* LocalScope::LookupVariable(const String& name, bool test_only) { LocalScope* current_scope = this; while (current_scope != NULL) { LocalVariable* var = current_scope->LocalLookupVariable(name); // If testing only, return the variable even if invisible. if ((var != NULL) && (!var->is_invisible_ || test_only)) { if (!test_only && (var->owner()->function_level() != function_level())) { CaptureVariable(var); } return var; } current_scope = current_scope->parent(); } return NULL; } void LocalScope::CaptureVariable(LocalVariable* variable) { ASSERT(variable != NULL); // The variable must exist in an enclosing scope, not necessarily in this one. variable->set_is_captured(); const int variable_function_level = variable->owner()->function_level(); LocalScope* scope = this; while (scope->function_level() != variable_function_level) { // Insert an alias of the variable in the top scope of each function // level so that the variable is found in the context. LocalScope* parent_scope = scope->parent(); while ((parent_scope != NULL) && (parent_scope->function_level() == scope->function_level())) { scope = parent_scope; parent_scope = scope->parent(); } // An alias may already have been added in this scope, and in that case, // in parent scopes as needed. If so, we are done. if (!scope->AddVariable(variable)) { return; } ASSERT(variable->owner() != scope); // Item is an alias. scope = parent_scope; } } SourceLabel* LocalScope::LookupLabel(const String& name) { LocalScope* current_scope = this; while (current_scope != NULL) { SourceLabel* label = current_scope->LocalLookupLabel(name); if (label != NULL) { return label; } current_scope = current_scope->parent(); } return NULL; } SourceLabel* LocalScope::LookupInnermostLabel(Token::Kind jump_kind) { ASSERT((jump_kind == Token::kCONTINUE) || (jump_kind == Token::kBREAK)); LocalScope* current_scope = this; while (current_scope != NULL) { for (intptr_t i = 0; i < current_scope->labels_.length(); i++) { SourceLabel* label = current_scope->labels_[i]; if ((label->kind() == SourceLabel::kWhile) || (label->kind() == SourceLabel::kFor) || (label->kind() == SourceLabel::kDoWhile) || ((jump_kind == Token::kBREAK) && (label->kind() == SourceLabel::kSwitch))) { return label; } } current_scope = current_scope->parent(); } return NULL; } LocalScope* LocalScope::LookupSwitchScope() { LocalScope* current_scope = this->parent(); int this_level = this->function_level(); while (current_scope != NULL && current_scope->function_level() == this_level) { for (int i = 0; i < current_scope->labels_.length(); i++) { SourceLabel* label = current_scope->labels_[i]; if (label->kind() == SourceLabel::kSwitch) { // This scope contains a label that is bound to a switch statement, // so it is the scope of the a statement body. return current_scope; } } current_scope = current_scope->parent(); } // We did not find a switch statement scope at the same function level. return NULL; } SourceLabel* LocalScope::CheckUnresolvedLabels() { for (int i = 0; i < this->labels_.length(); i++) { SourceLabel* label = this->labels_[i]; if (label->kind() == SourceLabel::kForward) { LocalScope* outer_switch = LookupSwitchScope(); if (outer_switch == NULL) { return label; } else { outer_switch->MoveLabel(label); } } } return NULL; } int LocalScope::NumCapturedVariables() const { // It is not necessary to traverse parent scopes, since we are only interested // in the captured variables referenced in this scope. If this scope is the // top scope at function level 1 and it (or its children scopes) references a // captured variable declared in a parent scope at function level 0, it will // contain an alias for that variable. // Since code generation for nested functions is postponed until first // invocation, the function level of the closure scope can only be 1. ASSERT(function_level() == 1); int num_captured = 0; for (int i = 0; i < num_variables(); i++) { LocalVariable* variable = VariableAt(i); // Count the aliases of captured variables belonging to outer scopes. if (variable->owner()->function_level() != 1) { ASSERT(variable->is_captured()); ASSERT(variable->owner()->function_level() == 0); num_captured++; } } return num_captured; } ContextScopePtr LocalScope::PreserveOuterScope( int current_context_level) const { // Since code generation for nested functions is postponed until first // invocation, the function level of the closure scope can only be 1. ASSERT(function_level() == 1); // Count the number of referenced captured variables. intptr_t num_captured_vars = NumCapturedVariables(); // Create a ContextScope with space for num_captured_vars descriptors. const ContextScope& context_scope = ContextScope::Handle(ContextScope::New(num_captured_vars, false)); // Create a descriptor for each referenced captured variable of enclosing // functions to preserve its name and its context allocation information. int captured_idx = 0; for (int i = 0; i < num_variables(); i++) { LocalVariable* variable = VariableAt(i); // Preserve the aliases of captured variables belonging to outer scopes. if (variable->owner()->function_level() != 1) { context_scope.SetTokenIndexAt(captured_idx, variable->token_pos()); context_scope.SetDeclarationTokenIndexAt( captured_idx, variable->declaration_token_pos()); context_scope.SetNameAt(captured_idx, variable->name()); context_scope.ClearFlagsAt(captured_idx); context_scope.SetIsFinalAt(captured_idx, variable->is_final()); context_scope.SetIsLateAt(captured_idx, variable->is_late()); if (variable->is_late()) { context_scope.SetLateInitOffsetAt(captured_idx, variable->late_init_offset()); } context_scope.SetIsConstAt(captured_idx, variable->IsConst()); if (variable->IsConst()) { context_scope.SetConstValueAt(captured_idx, *variable->ConstValue()); } else { context_scope.SetTypeAt(captured_idx, variable->type()); } context_scope.SetContextIndexAt(captured_idx, variable->index().value()); // Adjust the context level relative to the current context level, // since the context of the current scope will be at level 0 when // compiling the nested function. int adjusted_context_level = variable->owner()->context_level() - current_context_level; context_scope.SetContextLevelAt(captured_idx, adjusted_context_level); captured_idx++; } } ASSERT(context_scope.num_variables() == captured_idx); // Verify count. return context_scope.ptr(); } LocalScope* LocalScope::RestoreOuterScope(const ContextScope& context_scope) { // The function level of the outer scope is one less than the function level // of the current function, which is 0. LocalScope* outer_scope = new LocalScope(NULL, -1, 0); // Add all variables as aliases to the outer scope. for (int i = 0; i < context_scope.num_variables(); i++) { LocalVariable* variable; if (context_scope.IsConstAt(i)) { variable = new LocalVariable(context_scope.DeclarationTokenIndexAt(i), context_scope.TokenIndexAt(i), String::ZoneHandle(context_scope.NameAt(i)), Object::dynamic_type()); variable->SetConstValue( Instance::ZoneHandle(context_scope.ConstValueAt(i))); } else { variable = new LocalVariable(context_scope.DeclarationTokenIndexAt(i), context_scope.TokenIndexAt(i), String::ZoneHandle(context_scope.NameAt(i)), AbstractType::ZoneHandle(context_scope.TypeAt(i))); } variable->set_is_captured(); variable->set_index(VariableIndex(context_scope.ContextIndexAt(i))); if (context_scope.IsFinalAt(i)) { variable->set_is_final(); } if (context_scope.IsLateAt(i)) { variable->set_is_late(); variable->set_late_init_offset(context_scope.LateInitOffsetAt(i)); } // Create a fake owner scope describing the index and context level of the // variable. Function level and loop level are unused (set to 0), since // context level has already been assigned. LocalScope* owner_scope = new LocalScope(NULL, 0, 0); owner_scope->set_context_level(context_scope.ContextLevelAt(i)); owner_scope->AddVariable(variable); outer_scope->AddVariable(variable); // As alias. ASSERT(variable->owner() == owner_scope); } return outer_scope; } void LocalScope::CaptureLocalVariables(LocalScope* top_scope) { ASSERT(top_scope->function_level() == function_level()); LocalScope* scope = this; while (scope != top_scope->parent()) { for (intptr_t i = 0; i < scope->num_variables(); i++) { LocalVariable* variable = scope->VariableAt(i); if (variable->is_forced_stack() || (variable->name().ptr() == Symbols::ExceptionVar().ptr()) || (variable->name().ptr() == Symbols::SavedTryContextVar().ptr()) || (variable->name().ptr() == Symbols::ArgDescVar().ptr()) || (variable->name().ptr() == Symbols::FunctionTypeArgumentsVar().ptr())) { // Don't capture those variables because the VM expects them to be on // the stack. continue; } scope->CaptureVariable(variable); } scope = scope->parent(); } } ContextScopePtr LocalScope::CreateImplicitClosureScope(const Function& func) { static const intptr_t kNumCapturedVars = 1; // Create a ContextScope with space for kNumCapturedVars descriptors. const ContextScope& context_scope = ContextScope::Handle(ContextScope::New(kNumCapturedVars, true)); // Create a descriptor for 'this' variable. context_scope.SetTokenIndexAt(0, func.token_pos()); context_scope.SetDeclarationTokenIndexAt(0, func.token_pos()); context_scope.SetNameAt(0, Symbols::This()); context_scope.ClearFlagsAt(0); context_scope.SetIsFinalAt(0, true); context_scope.SetIsConstAt(0, false); const AbstractType& type = AbstractType::Handle(func.ParameterTypeAt(0)); context_scope.SetTypeAt(0, type); context_scope.SetContextIndexAt(0, 0); context_scope.SetContextLevelAt(0, 0); ASSERT(context_scope.num_variables() == kNumCapturedVars); // Verify count. return context_scope.ptr(); } bool LocalVariable::Equals(const LocalVariable& other) const { if (HasIndex() && other.HasIndex() && (index() == other.index())) { if (is_captured() == other.is_captured()) { if (!is_captured()) { return true; } if (owner()->context_level() == other.owner()->context_level()) { return true; } } } return false; } void LocalVarDescriptorsBuilder::AddAll(Zone* zone, const LocalVarDescriptors& var_descs) { for (intptr_t i = 0, n = var_descs.Length(); i < n; ++i) { VarDesc desc; desc.name = &String::Handle(zone, var_descs.GetName(i)); var_descs.GetInfo(i, &desc.info); Add(desc); } } void LocalVarDescriptorsBuilder::AddDeoptIdToContextLevelMappings( ZoneGrowableArray* context_level_array) { // Record deopt-id -> context-level mappings, using ranges of deopt-ids with // the same context-level. [context_level_array] contains (deopt_id, // context_level) tuples. for (intptr_t start = 0; start < context_level_array->length();) { intptr_t start_deopt_id = (*context_level_array)[start]; intptr_t start_context_level = (*context_level_array)[start + 1]; intptr_t end = start; intptr_t end_deopt_id = start_deopt_id; for (intptr_t peek = start + 2; peek < context_level_array->length(); peek += 2) { intptr_t peek_deopt_id = (*context_level_array)[peek]; intptr_t peek_context_level = (*context_level_array)[peek + 1]; // The range encoding assumes the tuples have ascending deopt_ids. ASSERT(peek_deopt_id > end_deopt_id); if (peek_context_level != start_context_level) break; end = peek; end_deopt_id = peek_deopt_id; } VarDesc desc; desc.name = &Symbols::Empty(); // No name. desc.info.set_kind(UntaggedLocalVarDescriptors::kContextLevel); desc.info.scope_id = 0; // We repurpose the token position fields to store deopt IDs in this case. desc.info.begin_pos = TokenPosition::Deserialize(start_deopt_id); desc.info.end_pos = TokenPosition::Deserialize(end_deopt_id); desc.info.set_index(start_context_level); Add(desc); start = end + 2; } } LocalVarDescriptorsPtr LocalVarDescriptorsBuilder::Done() { if (vars_.is_empty()) { return Object::empty_var_descriptors().ptr(); } const LocalVarDescriptors& var_desc = LocalVarDescriptors::Handle(LocalVarDescriptors::New(vars_.length())); for (int i = 0; i < vars_.length(); i++) { var_desc.SetVar(i, *(vars_[i].name), &vars_[i].info); } return var_desc.ptr(); } } // namespace dart #endif // !defined(DART_PRECOMPILED_RUNTIME)