From 827551f8e0bb2284e93b698257d58563eb36c535 Mon Sep 17 00:00:00 2001 From: Tess Strickland Date: Mon, 15 Sep 2025 08:14:47 -0700 Subject: [PATCH] [vm,dyn_modules] Fix ActivationFrame::ContextLevel for bytecode frames. Instead of using the computed LocalVarDescriptors when the frame is interpreted, use the LocalVariableInfo in the serialized bytecode to find which Scope is currently active. Add printing of local variable information to the bytecode disassembler. TEST=pkg/vm_service/test Change-Id: I7bd15056e4e2a947ad16ffb83a50447c2ba59994 Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try,vm-aot-dyn-linux-debug-x64-try,vm-aot-dyn-linux-product-x64-try,vm-dyn-mac-debug-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/449340 Reviewed-by: Alexander Markov Commit-Queue: Tess Strickland --- runtime/vm/bytecode_reader.cc | 7 ++ runtime/vm/bytecode_reader.h | 2 + .../vm/compiler/assembler/disassembler_kbc.cc | 80 +++++++++++++++++++ runtime/vm/debugger.cc | 71 +++++++++++----- runtime/vm/object.h | 12 ++- 5 files changed, 151 insertions(+), 21 deletions(-) diff --git a/runtime/vm/bytecode_reader.cc b/runtime/vm/bytecode_reader.cc index 277472e60a5..3ae4741549a 100644 --- a/runtime/vm/bytecode_reader.cc +++ b/runtime/vm/bytecode_reader.cc @@ -2902,6 +2902,13 @@ void BytecodeReader::CollectScriptTokenPositionsFromBytecode( #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) +const char* BytecodeLocalVariablesIterator::kKindNames[] = { + "Invalid", + "Scope", + "VariableDeclaration", + "ContextVariable", +}; + LocalVarDescriptorsPtr BytecodeReader::ComputeLocalVarDescriptors( Zone* zone, const Function& function, diff --git a/runtime/vm/bytecode_reader.h b/runtime/vm/bytecode_reader.h index 180b58c0b08..532b3b98320 100644 --- a/runtime/vm/bytecode_reader.h +++ b/runtime/vm/bytecode_reader.h @@ -594,6 +594,7 @@ class BytecodeLocalVariablesIterator : ValueObject { kContextVariable, }; + static const char* kKindNames[]; static const intptr_t kKindMask = 0xF; static const intptr_t kIsCapturedFlag = 1 << 4; @@ -641,6 +642,7 @@ class BytecodeLocalVariablesIterator : ValueObject { bool IsDone() const { return entries_remaining_ < 0; } intptr_t Kind() const { return cur_kind_and_flags_ & kKindMask; } + const char* KindName() const { return kKindNames[Kind()]; } bool IsScope() const { return Kind() == kScope; } bool IsVariableDeclaration() const { return Kind() == kVariableDeclaration; } bool IsContextVariable() const { return Kind() == kContextVariable; } diff --git a/runtime/vm/compiler/assembler/disassembler_kbc.cc b/runtime/vm/compiler/assembler/disassembler_kbc.cc index 1f65699a413..1807619e526 100644 --- a/runtime/vm/compiler/assembler/disassembler_kbc.cc +++ b/runtime/vm/compiler/assembler/disassembler_kbc.cc @@ -354,6 +354,23 @@ void KernelBytecodeDisassembler::Disassemble(uword start, #endif } +#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) +static const int kLocalVariableKindMaxWidth = strlen( + bytecode::BytecodeLocalVariablesIterator::kKindNames + [bytecode::BytecodeLocalVariablesIterator::kVariableDeclaration]); + +static const int kLocalVariableColumnWidths[] = { + kLocalVariableKindMaxWidth, // kind + 14, // start pc + 14, // end pc + 7, // context level + 7, // index + 7, // start token pos + 7, // end token pos + 7, // decl token pos +}; +#endif + void KernelBytecodeDisassembler::Disassemble(const Function& function) { #if !defined(PRODUCT) ASSERT(function.HasBytecode()); @@ -397,6 +414,69 @@ void KernelBytecodeDisassembler::Disassemble(const Function& function) { THR_Print("}\n"); } + if (bytecode.HasLocalVariablesInfo()) { +#if !defined(DART_PRECOMPILED_RUNTIME) + auto& name = String::Handle(zone); + auto& type = AbstractType::Handle(zone); + THR_Print("Local variable information for function '%s' {\n", + function_fullname); + // "*" in a printf format specifier tells it to read the field width from + // the printf argument list. + THR_Print( + " %*s %*s %*s %*s %*s %*s %*s %*s name\n", + kLocalVariableColumnWidths[0], "kind", kLocalVariableColumnWidths[1], + "start pc", kLocalVariableColumnWidths[2], "end pc", + kLocalVariableColumnWidths[3], "ctx", kLocalVariableColumnWidths[4], + "index", kLocalVariableColumnWidths[5], "start", + kLocalVariableColumnWidths[6], "end", kLocalVariableColumnWidths[7], + "decl"); + bytecode::BytecodeLocalVariablesIterator iter(zone, bytecode); + while (iter.MoveNext()) { + THR_Print(" %*s %-#*" Px "", kLocalVariableColumnWidths[0], + iter.KindName(), kLocalVariableColumnWidths[1], + base + iter.StartPC()); + if (iter.IsVariableDeclaration() || iter.IsScope()) { + THR_Print(" %-#*" Px "", kLocalVariableColumnWidths[2], + base + iter.EndPC()); + } else { + THR_Print(" %*s", kLocalVariableColumnWidths[2], ""); + } + if (iter.IsScope()) { + THR_Print(" %*" Pd "", kLocalVariableColumnWidths[3], + iter.ContextLevel()); + } else { + THR_Print(" %*s", kLocalVariableColumnWidths[3], ""); + } + if (iter.IsContextVariable() || iter.IsVariableDeclaration()) { + THR_Print(" %*" Pd "", kLocalVariableColumnWidths[4], iter.Index()); + } else { + THR_Print(" %*s", kLocalVariableColumnWidths[4], ""); + } + if (iter.IsVariableDeclaration() || iter.IsScope()) { + THR_Print(" %*s %*s", kLocalVariableColumnWidths[5], + iter.StartTokenPos().ToCString(), + kLocalVariableColumnWidths[6], + iter.EndTokenPos().ToCString()); + + } else { + THR_Print(" %*s %*s", kLocalVariableColumnWidths[5], "", + kLocalVariableColumnWidths[6], ""); + } + if (iter.IsVariableDeclaration()) { + name = iter.Name(); + type = iter.Type(); + THR_Print(" %*s %s: %s%s", kLocalVariableColumnWidths[7], + iter.DeclarationTokenPos().ToCString(), name.ToCString(), + type.ToCString(), iter.IsCaptured() ? " (captured)" : ""); + } + THR_Print("\n"); + } + THR_Print("}\n"); +#else + UNREACHABLE(); +#endif + } + THR_Print("Exception Handlers for function '%s' {\n", function_fullname); const ExceptionHandlers& handlers = ExceptionHandlers::Handle(zone, bytecode.exception_handlers()); diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc index 89f4a9a6dfe..689595df3a7 100644 --- a/runtime/vm/debugger.cc +++ b/runtime/vm/debugger.cc @@ -636,7 +636,15 @@ void ActivationFrame::PrintDescriptorsError(const char* message) { OS::PrintErr("deopt_id_ %" Px "\n", deopt_id_); OS::PrintErr("context_level_ %" Px "\n", context_level_); OS::PrintErr("token_pos_ %s\n", token_pos_.ToCString()); - { + if (IsInterpreted()) { +#if defined(DART_DYNAMIC_MODULES) + DisassembleToStdout formatter; + bytecode().Disassemble(&formatter); + OS::PrintErr("%s\n", var_descriptors_.ToCString()); +#else + UNREACHABLE(); +#endif + } else { DisassembleToStdout formatter; code().Disassemble(&formatter); PcDescriptors::Handle(code().pc_descriptors()).Print(); @@ -658,24 +666,51 @@ intptr_t ActivationFrame::ContextLevel() { const Context& ctx = GetSavedCurrentContext(); if (context_level_ < 0 && !ctx.IsNull()) { ASSERT(IsInterpreted() || !code().is_optimized()); - GetVarDescriptors(); - intptr_t deopt_id = DeoptId(); - if (deopt_id == DeoptId::kNone) { - PrintDescriptorsError("Missing deopt id"); - } - intptr_t var_desc_len = var_descriptors_.Length(); bool found = false; - // We store the deopt ids as real token positions. - const auto to_compare = TokenPosition::Deserialize(deopt_id); - for (intptr_t cur_idx = 0; cur_idx < var_desc_len; cur_idx++) { - UntaggedLocalVarDescriptors::VarInfo var_info; - var_descriptors_.GetInfo(cur_idx, &var_info); - const int8_t kind = var_info.kind(); - if ((kind == UntaggedLocalVarDescriptors::kContextLevel) && - to_compare.IsWithin(var_info.begin_pos, var_info.end_pos)) { - context_level_ = var_info.index(); - found = true; - break; + if (IsInterpreted()) { +#if defined(DART_DYNAMIC_MODULES) && !defined(PRODUCT) && \ + !defined(DART_PRECOMPILED_RUNTIME) + const intptr_t pc_offset = pc() - PayloadStart(); + DEBUG_ONLY(intptr_t closest_start = 0); + bytecode::BytecodeLocalVariablesIterator local_vars( + Thread::Current()->zone(), bytecode()); + while (local_vars.MoveNext()) { + if (local_vars.IsScope()) { + if (local_vars.StartPC() <= pc_offset && + pc_offset < local_vars.EndPC()) { + DEBUG_ASSERT(!found || local_vars.StartPC() > closest_start); + found = true; + context_level_ = local_vars.ContextLevel(); + DEBUG_ONLY(closest_start = local_vars.StartPC()); + } else if (local_vars.StartPC() > pc_offset) { + // The scopes in the local variables info are ordered by starting + // PC offset, so no need to search further. + break; + } + } + } +#else + UNREACHABLE(); +#endif + } else { + GetVarDescriptors(); + intptr_t var_desc_len = var_descriptors_.Length(); + // We store the deopt ids as real token positions. + intptr_t deopt_id = DeoptId(); + if (deopt_id == DeoptId::kNone) { + PrintDescriptorsError("Missing deopt id"); + } + const TokenPosition to_compare = TokenPosition::Deserialize(deopt_id); + for (intptr_t cur_idx = 0; cur_idx < var_desc_len; cur_idx++) { + UntaggedLocalVarDescriptors::VarInfo var_info; + var_descriptors_.GetInfo(cur_idx, &var_info); + const int8_t kind = var_info.kind(); + if ((kind == UntaggedLocalVarDescriptors::kContextLevel) && + to_compare.IsWithin(var_info.begin_pos, var_info.end_pos)) { + context_level_ = var_info.index(); + found = true; + break; + } } } if (!found) { diff --git a/runtime/vm/object.h b/runtime/vm/object.h index 255f891eb5b..eeebf08dc65 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -7601,9 +7601,6 @@ class Bytecode : public Object { void set_local_variables_binary_offset(intptr_t value) const { StoreNonPointer(&untag()->local_variables_binary_offset_, value); } - bool HasLocalVariablesInfo() const { - return (local_variables_binary_offset() != 0); - } LocalVarDescriptorsPtr var_descriptors() const { return untag()->var_descriptors(); @@ -7616,6 +7613,15 @@ class Bytecode : public Object { // Will compute local var descriptors if necessary. LocalVarDescriptorsPtr GetLocalVarDescriptors() const; #endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) + + bool HasLocalVariablesInfo() const { +#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) + return (local_variables_binary_offset() != 0); +#else + return false; +#endif + } + const char* Name() const; const char* QualifiedName() const; const char* FullyQualifiedName() const;