xref: /third_party/node/deps/v8/src/wasm/wasm-debug.cc (revision 1cb0ef41)
1// Copyright 2016 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/wasm/wasm-debug.h"
6
7#include <iomanip>
8#include <unordered_map>
9
10#include "src/base/optional.h"
11#include "src/base/platform/wrappers.h"
12#include "src/codegen/assembler-inl.h"
13#include "src/common/assert-scope.h"
14#include "src/compiler/wasm-compiler.h"
15#include "src/debug/debug-evaluate.h"
16#include "src/execution/frames-inl.h"
17#include "src/heap/factory.h"
18#include "src/wasm/baseline/liftoff-compiler.h"
19#include "src/wasm/baseline/liftoff-register.h"
20#include "src/wasm/module-decoder.h"
21#include "src/wasm/value-type.h"
22#include "src/wasm/wasm-code-manager.h"
23#include "src/wasm/wasm-engine.h"
24#include "src/wasm/wasm-limits.h"
25#include "src/wasm/wasm-module.h"
26#include "src/wasm/wasm-objects-inl.h"
27#include "src/wasm/wasm-opcodes-inl.h"
28#include "src/wasm/wasm-subtyping.h"
29#include "src/wasm/wasm-value.h"
30#include "src/zone/accounting-allocator.h"
31
32namespace v8 {
33namespace internal {
34namespace wasm {
35
36namespace {
37
38using ImportExportKey = std::pair<ImportExportKindCode, uint32_t>;
39
40enum ReturnLocation { kAfterBreakpoint, kAfterWasmCall };
41
42Address FindNewPC(WasmFrame* frame, WasmCode* wasm_code, int byte_offset,
43                  ReturnLocation return_location) {
44  base::Vector<const uint8_t> new_pos_table = wasm_code->source_positions();
45
46  DCHECK_LE(0, byte_offset);
47
48  // Find the size of the call instruction by computing the distance from the
49  // source position entry to the return address.
50  WasmCode* old_code = frame->wasm_code();
51  int pc_offset = static_cast<int>(frame->pc() - old_code->instruction_start());
52  base::Vector<const uint8_t> old_pos_table = old_code->source_positions();
53  SourcePositionTableIterator old_it(old_pos_table);
54  int call_offset = -1;
55  while (!old_it.done() && old_it.code_offset() < pc_offset) {
56    call_offset = old_it.code_offset();
57    old_it.Advance();
58  }
59  DCHECK_LE(0, call_offset);
60  int call_instruction_size = pc_offset - call_offset;
61
62  // If {return_location == kAfterBreakpoint} we search for the first code
63  // offset which is marked as instruction (i.e. not the breakpoint).
64  // If {return_location == kAfterWasmCall} we return the last code offset
65  // associated with the byte offset.
66  SourcePositionTableIterator it(new_pos_table);
67  while (!it.done() && it.source_position().ScriptOffset() != byte_offset) {
68    it.Advance();
69  }
70  if (return_location == kAfterBreakpoint) {
71    while (!it.is_statement()) it.Advance();
72    DCHECK_EQ(byte_offset, it.source_position().ScriptOffset());
73    return wasm_code->instruction_start() + it.code_offset() +
74           call_instruction_size;
75  }
76
77  DCHECK_EQ(kAfterWasmCall, return_location);
78  int code_offset;
79  do {
80    code_offset = it.code_offset();
81    it.Advance();
82  } while (!it.done() && it.source_position().ScriptOffset() == byte_offset);
83  return wasm_code->instruction_start() + code_offset + call_instruction_size;
84}
85
86}  // namespace
87
88void DebugSideTable::Print(std::ostream& os) const {
89  os << "Debug side table (" << num_locals_ << " locals, " << entries_.size()
90     << " entries):\n";
91  for (auto& entry : entries_) entry.Print(os);
92  os << "\n";
93}
94
95void DebugSideTable::Entry::Print(std::ostream& os) const {
96  os << std::setw(6) << std::hex << pc_offset_ << std::dec << " stack height "
97     << stack_height_ << " [";
98  for (auto& value : changed_values_) {
99    os << " " << value.type.name() << ":";
100    switch (value.storage) {
101      case kConstant:
102        os << "const#" << value.i32_const;
103        break;
104      case kRegister:
105        os << "reg#" << value.reg_code;
106        break;
107      case kStack:
108        os << "stack#" << value.stack_offset;
109        break;
110    }
111  }
112  os << " ]\n";
113}
114
115class DebugInfoImpl {
116 public:
117  explicit DebugInfoImpl(NativeModule* native_module)
118      : native_module_(native_module) {}
119
120  DebugInfoImpl(const DebugInfoImpl&) = delete;
121  DebugInfoImpl& operator=(const DebugInfoImpl&) = delete;
122
123  int GetNumLocals(Address pc) {
124    FrameInspectionScope scope(this, pc);
125    if (!scope.is_inspectable()) return 0;
126    return scope.debug_side_table->num_locals();
127  }
128
129  WasmValue GetLocalValue(int local, Address pc, Address fp,
130                          Address debug_break_fp, Isolate* isolate) {
131    FrameInspectionScope scope(this, pc);
132    return GetValue(scope.debug_side_table, scope.debug_side_table_entry, local,
133                    fp, debug_break_fp, isolate);
134  }
135
136  int GetStackDepth(Address pc) {
137    FrameInspectionScope scope(this, pc);
138    if (!scope.is_inspectable()) return 0;
139    int num_locals = scope.debug_side_table->num_locals();
140    int stack_height = scope.debug_side_table_entry->stack_height();
141    return stack_height - num_locals;
142  }
143
144  WasmValue GetStackValue(int index, Address pc, Address fp,
145                          Address debug_break_fp, Isolate* isolate) {
146    FrameInspectionScope scope(this, pc);
147    int num_locals = scope.debug_side_table->num_locals();
148    int value_count = scope.debug_side_table_entry->stack_height();
149    if (num_locals + index >= value_count) return {};
150    return GetValue(scope.debug_side_table, scope.debug_side_table_entry,
151                    num_locals + index, fp, debug_break_fp, isolate);
152  }
153
154  const WasmFunction& GetFunctionAtAddress(Address pc) {
155    FrameInspectionScope scope(this, pc);
156    auto* module = native_module_->module();
157    return module->functions[scope.code->index()];
158  }
159
160  WireBytesRef GetExportName(ImportExportKindCode kind, uint32_t index) {
161    base::MutexGuard guard(&mutex_);
162    if (!export_names_) {
163      export_names_ =
164          std::make_unique<std::map<ImportExportKey, WireBytesRef>>();
165      for (auto exp : native_module_->module()->export_table) {
166        auto exp_key = std::make_pair(exp.kind, exp.index);
167        if (export_names_->find(exp_key) != export_names_->end()) continue;
168        export_names_->insert(std::make_pair(exp_key, exp.name));
169      }
170    }
171    auto it = export_names_->find(std::make_pair(kind, index));
172    if (it != export_names_->end()) return it->second;
173    return {};
174  }
175
176  std::pair<WireBytesRef, WireBytesRef> GetImportName(ImportExportKindCode kind,
177                                                      uint32_t index) {
178    base::MutexGuard guard(&mutex_);
179    if (!import_names_) {
180      import_names_ = std::make_unique<
181          std::map<ImportExportKey, std::pair<WireBytesRef, WireBytesRef>>>();
182      for (auto imp : native_module_->module()->import_table) {
183        import_names_->insert(
184            std::make_pair(std::make_pair(imp.kind, imp.index),
185                           std::make_pair(imp.module_name, imp.field_name)));
186      }
187    }
188    auto it = import_names_->find(std::make_pair(kind, index));
189    if (it != import_names_->end()) return it->second;
190    return {};
191  }
192
193  WireBytesRef GetTypeName(int type_index) {
194    base::MutexGuard guard(&mutex_);
195    if (!type_names_) {
196      type_names_ = std::make_unique<NameMap>(DecodeNameMap(
197          native_module_->wire_bytes(), NameSectionKindCode::kTypeCode));
198    }
199    return type_names_->GetName(type_index);
200  }
201
202  WireBytesRef GetLocalName(int func_index, int local_index) {
203    base::MutexGuard guard(&mutex_);
204    if (!local_names_) {
205      local_names_ = std::make_unique<IndirectNameMap>(DecodeIndirectNameMap(
206          native_module_->wire_bytes(), NameSectionKindCode::kLocalCode));
207    }
208    return local_names_->GetName(func_index, local_index);
209  }
210
211  WireBytesRef GetFieldName(int struct_index, int field_index) {
212    base::MutexGuard guard(&mutex_);
213    if (!field_names_) {
214      field_names_ = std::make_unique<IndirectNameMap>(DecodeIndirectNameMap(
215          native_module_->wire_bytes(), NameSectionKindCode::kFieldCode));
216    }
217    return field_names_->GetName(struct_index, field_index);
218  }
219
220  // If the frame position is not in the list of breakpoints, return that
221  // position. Return 0 otherwise.
222  // This is used to generate a "dead breakpoint" in Liftoff, which is necessary
223  // for OSR to find the correct return address.
224  int DeadBreakpoint(WasmFrame* frame, base::Vector<const int> breakpoints) {
225    const auto& function =
226        native_module_->module()->functions[frame->function_index()];
227    int offset = frame->position() - function.code.offset();
228    if (std::binary_search(breakpoints.begin(), breakpoints.end(), offset)) {
229      return 0;
230    }
231    return offset;
232  }
233
234  // Find the dead breakpoint (see above) for the top wasm frame, if that frame
235  // is in the function of the given index.
236  int DeadBreakpoint(int func_index, base::Vector<const int> breakpoints,
237                     Isolate* isolate) {
238    StackTraceFrameIterator it(isolate);
239    if (it.done() || !it.is_wasm()) return 0;
240    auto* wasm_frame = WasmFrame::cast(it.frame());
241    if (static_cast<int>(wasm_frame->function_index()) != func_index) return 0;
242    return DeadBreakpoint(wasm_frame, breakpoints);
243  }
244
245  WasmCode* RecompileLiftoffWithBreakpoints(int func_index,
246                                            base::Vector<const int> offsets,
247                                            int dead_breakpoint) {
248    DCHECK(!mutex_.TryLock());  // Mutex is held externally.
249
250    ForDebugging for_debugging = offsets.size() == 1 && offsets[0] == 0
251                                     ? kForStepping
252                                     : kWithBreakpoints;
253
254    // Check the cache first.
255    for (auto begin = cached_debugging_code_.begin(), it = begin,
256              end = cached_debugging_code_.end();
257         it != end; ++it) {
258      if (it->func_index == func_index &&
259          it->breakpoint_offsets.as_vector() == offsets &&
260          it->dead_breakpoint == dead_breakpoint) {
261        // Rotate the cache entry to the front (for LRU).
262        for (; it != begin; --it) std::iter_swap(it, it - 1);
263        if (for_debugging == kWithBreakpoints) {
264          // Re-install the code, in case it was replaced in the meantime.
265          native_module_->ReinstallDebugCode(it->code);
266        }
267        return it->code;
268      }
269    }
270
271    // Recompile the function with Liftoff, setting the new breakpoints.
272    // Not thread-safe. The caller is responsible for locking {mutex_}.
273    CompilationEnv env = native_module_->CreateCompilationEnv();
274    auto* function = &native_module_->module()->functions[func_index];
275    base::Vector<const uint8_t> wire_bytes = native_module_->wire_bytes();
276    FunctionBody body{function->sig, function->code.offset(),
277                      wire_bytes.begin() + function->code.offset(),
278                      wire_bytes.begin() + function->code.end_offset()};
279    std::unique_ptr<DebugSideTable> debug_sidetable;
280
281    // Debug side tables for stepping are generated lazily.
282    bool generate_debug_sidetable = for_debugging == kWithBreakpoints;
283    WasmCompilationResult result = ExecuteLiftoffCompilation(
284        &env, body, func_index, for_debugging,
285        LiftoffOptions{}
286            .set_breakpoints(offsets)
287            .set_dead_breakpoint(dead_breakpoint)
288            .set_debug_sidetable(generate_debug_sidetable ? &debug_sidetable
289                                                          : nullptr));
290    // Liftoff compilation failure is a FATAL error. We rely on complete Liftoff
291    // support for debugging.
292    if (!result.succeeded()) FATAL("Liftoff compilation failed");
293    DCHECK_EQ(generate_debug_sidetable, debug_sidetable != nullptr);
294
295    WasmCode* new_code = native_module_->PublishCode(
296        native_module_->AddCompiledCode(std::move(result)));
297
298    DCHECK(new_code->is_inspectable());
299    if (generate_debug_sidetable) {
300      base::MutexGuard lock(&debug_side_tables_mutex_);
301      DCHECK_EQ(0, debug_side_tables_.count(new_code));
302      debug_side_tables_.emplace(new_code, std::move(debug_sidetable));
303    }
304
305    // Insert new code into the cache. Insert before existing elements for LRU.
306    cached_debugging_code_.insert(
307        cached_debugging_code_.begin(),
308        CachedDebuggingCode{func_index, base::OwnedVector<int>::Of(offsets),
309                            dead_breakpoint, new_code});
310    // Increase the ref count (for the cache entry).
311    new_code->IncRef();
312    // Remove exceeding element.
313    if (cached_debugging_code_.size() > kMaxCachedDebuggingCode) {
314      // Put the code in the surrounding CodeRefScope to delay deletion until
315      // after the mutex is released.
316      WasmCodeRefScope::AddRef(cached_debugging_code_.back().code);
317      cached_debugging_code_.back().code->DecRefOnLiveCode();
318      cached_debugging_code_.pop_back();
319    }
320    DCHECK_GE(kMaxCachedDebuggingCode, cached_debugging_code_.size());
321
322    return new_code;
323  }
324
325  void SetBreakpoint(int func_index, int offset, Isolate* isolate) {
326    // Put the code ref scope outside of the mutex, so we don't unnecessarily
327    // hold the mutex while freeing code.
328    WasmCodeRefScope wasm_code_ref_scope;
329
330    // Hold the mutex while modifying breakpoints, to ensure consistency when
331    // multiple isolates set/remove breakpoints at the same time.
332    base::MutexGuard guard(&mutex_);
333
334    // offset == 0 indicates flooding and should not happen here.
335    DCHECK_NE(0, offset);
336
337    // Get the set of previously set breakpoints, to check later whether a new
338    // breakpoint was actually added.
339    std::vector<int> all_breakpoints = FindAllBreakpoints(func_index);
340
341    auto& isolate_data = per_isolate_data_[isolate];
342    std::vector<int>& breakpoints =
343        isolate_data.breakpoints_per_function[func_index];
344    auto insertion_point =
345        std::lower_bound(breakpoints.begin(), breakpoints.end(), offset);
346    if (insertion_point != breakpoints.end() && *insertion_point == offset) {
347      // The breakpoint is already set for this isolate.
348      return;
349    }
350    breakpoints.insert(insertion_point, offset);
351
352    DCHECK(std::is_sorted(all_breakpoints.begin(), all_breakpoints.end()));
353    // Find the insertion position within {all_breakpoints}.
354    insertion_point = std::lower_bound(all_breakpoints.begin(),
355                                       all_breakpoints.end(), offset);
356    bool breakpoint_exists =
357        insertion_point != all_breakpoints.end() && *insertion_point == offset;
358    // If the breakpoint was already set before, then we can just reuse the old
359    // code. Otherwise, recompile it. In any case, rewrite this isolate's stack
360    // to make sure that it uses up-to-date code containing the breakpoint.
361    WasmCode* new_code;
362    if (breakpoint_exists) {
363      new_code = native_module_->GetCode(func_index);
364    } else {
365      all_breakpoints.insert(insertion_point, offset);
366      int dead_breakpoint =
367          DeadBreakpoint(func_index, base::VectorOf(all_breakpoints), isolate);
368      new_code = RecompileLiftoffWithBreakpoints(
369          func_index, base::VectorOf(all_breakpoints), dead_breakpoint);
370    }
371    UpdateReturnAddresses(isolate, new_code, isolate_data.stepping_frame);
372  }
373
374  std::vector<int> FindAllBreakpoints(int func_index) {
375    DCHECK(!mutex_.TryLock());  // Mutex must be held externally.
376    std::set<int> breakpoints;
377    for (auto& data : per_isolate_data_) {
378      auto it = data.second.breakpoints_per_function.find(func_index);
379      if (it == data.second.breakpoints_per_function.end()) continue;
380      for (int offset : it->second) breakpoints.insert(offset);
381    }
382    return {breakpoints.begin(), breakpoints.end()};
383  }
384
385  void UpdateBreakpoints(int func_index, base::Vector<int> breakpoints,
386                         Isolate* isolate, StackFrameId stepping_frame,
387                         int dead_breakpoint) {
388    DCHECK(!mutex_.TryLock());  // Mutex is held externally.
389    WasmCode* new_code = RecompileLiftoffWithBreakpoints(
390        func_index, breakpoints, dead_breakpoint);
391    UpdateReturnAddresses(isolate, new_code, stepping_frame);
392  }
393
394  void FloodWithBreakpoints(WasmFrame* frame, ReturnLocation return_location) {
395    // 0 is an invalid offset used to indicate flooding.
396    constexpr int kFloodingBreakpoints[] = {0};
397    DCHECK(frame->wasm_code()->is_liftoff());
398    // Generate an additional source position for the current byte offset.
399    base::MutexGuard guard(&mutex_);
400    WasmCode* new_code = RecompileLiftoffWithBreakpoints(
401        frame->function_index(), base::ArrayVector(kFloodingBreakpoints), 0);
402    UpdateReturnAddress(frame, new_code, return_location);
403
404    per_isolate_data_[frame->isolate()].stepping_frame = frame->id();
405  }
406
407  bool PrepareStep(WasmFrame* frame) {
408    WasmCodeRefScope wasm_code_ref_scope;
409    wasm::WasmCode* code = frame->wasm_code();
410    if (!code->is_liftoff()) return false;  // Cannot step in TurboFan code.
411    if (IsAtReturn(frame)) return false;    // Will return after this step.
412    FloodWithBreakpoints(frame, kAfterBreakpoint);
413    return true;
414  }
415
416  void PrepareStepOutTo(WasmFrame* frame) {
417    WasmCodeRefScope wasm_code_ref_scope;
418    wasm::WasmCode* code = frame->wasm_code();
419    if (!code->is_liftoff()) return;  // Cannot step out to TurboFan code.
420    FloodWithBreakpoints(frame, kAfterWasmCall);
421  }
422
423  void ClearStepping(WasmFrame* frame) {
424    WasmCodeRefScope wasm_code_ref_scope;
425    base::MutexGuard guard(&mutex_);
426    auto* code = frame->wasm_code();
427    if (code->for_debugging() != kForStepping) return;
428    int func_index = code->index();
429    std::vector<int> breakpoints = FindAllBreakpoints(func_index);
430    int dead_breakpoint = DeadBreakpoint(frame, base::VectorOf(breakpoints));
431    WasmCode* new_code = RecompileLiftoffWithBreakpoints(
432        func_index, base::VectorOf(breakpoints), dead_breakpoint);
433    UpdateReturnAddress(frame, new_code, kAfterBreakpoint);
434  }
435
436  void ClearStepping(Isolate* isolate) {
437    base::MutexGuard guard(&mutex_);
438    auto it = per_isolate_data_.find(isolate);
439    if (it != per_isolate_data_.end()) it->second.stepping_frame = NO_ID;
440  }
441
442  bool IsStepping(WasmFrame* frame) {
443    Isolate* isolate = frame->wasm_instance().GetIsolate();
444    if (isolate->debug()->last_step_action() == StepInto) return true;
445    base::MutexGuard guard(&mutex_);
446    auto it = per_isolate_data_.find(isolate);
447    return it != per_isolate_data_.end() &&
448           it->second.stepping_frame == frame->id();
449  }
450
451  void RemoveBreakpoint(int func_index, int position, Isolate* isolate) {
452    // Put the code ref scope outside of the mutex, so we don't unnecessarily
453    // hold the mutex while freeing code.
454    WasmCodeRefScope wasm_code_ref_scope;
455
456    // Hold the mutex while modifying breakpoints, to ensure consistency when
457    // multiple isolates set/remove breakpoints at the same time.
458    base::MutexGuard guard(&mutex_);
459
460    const auto& function = native_module_->module()->functions[func_index];
461    int offset = position - function.code.offset();
462
463    auto& isolate_data = per_isolate_data_[isolate];
464    std::vector<int>& breakpoints =
465        isolate_data.breakpoints_per_function[func_index];
466    DCHECK_LT(0, offset);
467    auto insertion_point =
468        std::lower_bound(breakpoints.begin(), breakpoints.end(), offset);
469    if (insertion_point == breakpoints.end()) return;
470    if (*insertion_point != offset) return;
471    breakpoints.erase(insertion_point);
472
473    std::vector<int> remaining = FindAllBreakpoints(func_index);
474    // If the breakpoint is still set in another isolate, don't remove it.
475    DCHECK(std::is_sorted(remaining.begin(), remaining.end()));
476    if (std::binary_search(remaining.begin(), remaining.end(), offset)) return;
477    int dead_breakpoint =
478        DeadBreakpoint(func_index, base::VectorOf(remaining), isolate);
479    UpdateBreakpoints(func_index, base::VectorOf(remaining), isolate,
480                      isolate_data.stepping_frame, dead_breakpoint);
481  }
482
483  void RemoveDebugSideTables(base::Vector<WasmCode* const> codes) {
484    base::MutexGuard guard(&debug_side_tables_mutex_);
485    for (auto* code : codes) {
486      debug_side_tables_.erase(code);
487    }
488  }
489
490  DebugSideTable* GetDebugSideTableIfExists(const WasmCode* code) const {
491    base::MutexGuard guard(&debug_side_tables_mutex_);
492    auto it = debug_side_tables_.find(code);
493    return it == debug_side_tables_.end() ? nullptr : it->second.get();
494  }
495
496  static bool HasRemovedBreakpoints(const std::vector<int>& removed,
497                                    const std::vector<int>& remaining) {
498    DCHECK(std::is_sorted(remaining.begin(), remaining.end()));
499    for (int offset : removed) {
500      // Return true if we removed a breakpoint which is not part of remaining.
501      if (!std::binary_search(remaining.begin(), remaining.end(), offset)) {
502        return true;
503      }
504    }
505    return false;
506  }
507
508  void RemoveIsolate(Isolate* isolate) {
509    // Put the code ref scope outside of the mutex, so we don't unnecessarily
510    // hold the mutex while freeing code.
511    WasmCodeRefScope wasm_code_ref_scope;
512
513    base::MutexGuard guard(&mutex_);
514    auto per_isolate_data_it = per_isolate_data_.find(isolate);
515    if (per_isolate_data_it == per_isolate_data_.end()) return;
516    std::unordered_map<int, std::vector<int>> removed_per_function =
517        std::move(per_isolate_data_it->second.breakpoints_per_function);
518    per_isolate_data_.erase(per_isolate_data_it);
519    for (auto& entry : removed_per_function) {
520      int func_index = entry.first;
521      std::vector<int>& removed = entry.second;
522      std::vector<int> remaining = FindAllBreakpoints(func_index);
523      if (HasRemovedBreakpoints(removed, remaining)) {
524        RecompileLiftoffWithBreakpoints(func_index, base::VectorOf(remaining),
525                                        0);
526      }
527    }
528  }
529
530 private:
531  struct FrameInspectionScope {
532    FrameInspectionScope(DebugInfoImpl* debug_info, Address pc)
533        : code(wasm::GetWasmCodeManager()->LookupCode(pc)),
534          pc_offset(static_cast<int>(pc - code->instruction_start())),
535          debug_side_table(code->is_inspectable()
536                               ? debug_info->GetDebugSideTable(code)
537                               : nullptr),
538          debug_side_table_entry(debug_side_table
539                                     ? debug_side_table->GetEntry(pc_offset)
540                                     : nullptr) {
541      DCHECK_IMPLIES(code->is_inspectable(), debug_side_table_entry != nullptr);
542    }
543
544    bool is_inspectable() const { return debug_side_table_entry; }
545
546    wasm::WasmCodeRefScope wasm_code_ref_scope;
547    wasm::WasmCode* code;
548    int pc_offset;
549    const DebugSideTable* debug_side_table;
550    const DebugSideTable::Entry* debug_side_table_entry;
551  };
552
553  const DebugSideTable* GetDebugSideTable(WasmCode* code) {
554    DCHECK(code->is_inspectable());
555    {
556      // Only hold the mutex temporarily. We can't hold it while generating the
557      // debug side table, because compilation takes the {NativeModule} lock.
558      base::MutexGuard guard(&debug_side_tables_mutex_);
559      auto it = debug_side_tables_.find(code);
560      if (it != debug_side_tables_.end()) return it->second.get();
561    }
562
563    // Otherwise create the debug side table now.
564    std::unique_ptr<DebugSideTable> debug_side_table =
565        GenerateLiftoffDebugSideTable(code);
566    DebugSideTable* ret = debug_side_table.get();
567
568    // Check cache again, maybe another thread concurrently generated a debug
569    // side table already.
570    {
571      base::MutexGuard guard(&debug_side_tables_mutex_);
572      auto& slot = debug_side_tables_[code];
573      if (slot != nullptr) return slot.get();
574      slot = std::move(debug_side_table);
575    }
576
577    // Print the code together with the debug table, if requested.
578    code->MaybePrint();
579    return ret;
580  }
581
582  // Get the value of a local (including parameters) or stack value. Stack
583  // values follow the locals in the same index space.
584  WasmValue GetValue(const DebugSideTable* debug_side_table,
585                     const DebugSideTable::Entry* debug_side_table_entry,
586                     int index, Address stack_frame_base,
587                     Address debug_break_fp, Isolate* isolate) const {
588    const auto* value =
589        debug_side_table->FindValue(debug_side_table_entry, index);
590    if (value->is_constant()) {
591      DCHECK(value->type == kWasmI32 || value->type == kWasmI64);
592      return value->type == kWasmI32 ? WasmValue(value->i32_const)
593                                     : WasmValue(int64_t{value->i32_const});
594    }
595
596    if (value->is_register()) {
597      auto reg = LiftoffRegister::from_liftoff_code(value->reg_code);
598      auto gp_addr = [debug_break_fp](Register reg) {
599        return debug_break_fp +
600               WasmDebugBreakFrameConstants::GetPushedGpRegisterOffset(
601                   reg.code());
602      };
603      if (reg.is_gp_pair()) {
604        DCHECK_EQ(kWasmI64, value->type);
605        uint32_t low_word = ReadUnalignedValue<uint32_t>(gp_addr(reg.low_gp()));
606        uint32_t high_word =
607            ReadUnalignedValue<uint32_t>(gp_addr(reg.high_gp()));
608        return WasmValue((uint64_t{high_word} << 32) | low_word);
609      }
610      if (reg.is_gp()) {
611        if (value->type == kWasmI32) {
612          return WasmValue(ReadUnalignedValue<uint32_t>(gp_addr(reg.gp())));
613        } else if (value->type == kWasmI64) {
614          return WasmValue(ReadUnalignedValue<uint64_t>(gp_addr(reg.gp())));
615        } else if (value->type.is_reference()) {
616          Handle<Object> obj(
617              Object(ReadUnalignedValue<Address>(gp_addr(reg.gp()))), isolate);
618          return WasmValue(obj, value->type);
619        } else {
620          UNREACHABLE();
621        }
622      }
623      DCHECK(reg.is_fp() || reg.is_fp_pair());
624      // ifdef here to workaround unreachable code for is_fp_pair.
625#ifdef V8_TARGET_ARCH_ARM
626      int code = reg.is_fp_pair() ? reg.low_fp().code() : reg.fp().code();
627#else
628      int code = reg.fp().code();
629#endif
630      Address spilled_addr =
631          debug_break_fp +
632          WasmDebugBreakFrameConstants::GetPushedFpRegisterOffset(code);
633      if (value->type == kWasmF32) {
634        return WasmValue(ReadUnalignedValue<float>(spilled_addr));
635      } else if (value->type == kWasmF64) {
636        return WasmValue(ReadUnalignedValue<double>(spilled_addr));
637      } else if (value->type == kWasmS128) {
638        return WasmValue(Simd128(ReadUnalignedValue<int16>(spilled_addr)));
639      } else {
640        // All other cases should have been handled above.
641        UNREACHABLE();
642      }
643    }
644
645    // Otherwise load the value from the stack.
646    Address stack_address = stack_frame_base - value->stack_offset;
647    switch (value->type.kind()) {
648      case kI32:
649        return WasmValue(ReadUnalignedValue<int32_t>(stack_address));
650      case kI64:
651        return WasmValue(ReadUnalignedValue<int64_t>(stack_address));
652      case kF32:
653        return WasmValue(ReadUnalignedValue<float>(stack_address));
654      case kF64:
655        return WasmValue(ReadUnalignedValue<double>(stack_address));
656      case kS128:
657        return WasmValue(Simd128(ReadUnalignedValue<int16>(stack_address)));
658      case kRef:
659      case kOptRef:
660      case kRtt: {
661        Handle<Object> obj(Object(ReadUnalignedValue<Address>(stack_address)),
662                           isolate);
663        return WasmValue(obj, value->type);
664      }
665      case kI8:
666      case kI16:
667      case kVoid:
668      case kBottom:
669        UNREACHABLE();
670    }
671  }
672
673  // After installing a Liftoff code object with a different set of breakpoints,
674  // update return addresses on the stack so that execution resumes in the new
675  // code. The frame layout itself should be independent of breakpoints.
676  void UpdateReturnAddresses(Isolate* isolate, WasmCode* new_code,
677                             StackFrameId stepping_frame) {
678    // The first return location is after the breakpoint, others are after wasm
679    // calls.
680    ReturnLocation return_location = kAfterBreakpoint;
681    for (StackTraceFrameIterator it(isolate); !it.done();
682         it.Advance(), return_location = kAfterWasmCall) {
683      // We still need the flooded function for stepping.
684      if (it.frame()->id() == stepping_frame) continue;
685      if (!it.is_wasm()) continue;
686      WasmFrame* frame = WasmFrame::cast(it.frame());
687      if (frame->native_module() != new_code->native_module()) continue;
688      if (frame->function_index() != new_code->index()) continue;
689      if (!frame->wasm_code()->is_liftoff()) continue;
690      UpdateReturnAddress(frame, new_code, return_location);
691    }
692  }
693
694  void UpdateReturnAddress(WasmFrame* frame, WasmCode* new_code,
695                           ReturnLocation return_location) {
696    DCHECK(new_code->is_liftoff());
697    DCHECK_EQ(frame->function_index(), new_code->index());
698    DCHECK_EQ(frame->native_module(), new_code->native_module());
699    DCHECK(frame->wasm_code()->is_liftoff());
700    Address new_pc =
701        FindNewPC(frame, new_code, frame->byte_offset(), return_location);
702#ifdef DEBUG
703    int old_position = frame->position();
704#endif
705#if V8_TARGET_ARCH_X64
706    if (frame->wasm_code()->for_debugging()) {
707      base::Memory<Address>(frame->fp() - kOSRTargetOffset) = new_pc;
708    }
709#else
710    PointerAuthentication::ReplacePC(frame->pc_address(), new_pc,
711                                     kSystemPointerSize);
712#endif
713    // The frame position should still be the same after OSR.
714    DCHECK_EQ(old_position, frame->position());
715  }
716
717  bool IsAtReturn(WasmFrame* frame) {
718    DisallowGarbageCollection no_gc;
719    int position = frame->position();
720    NativeModule* native_module =
721        frame->wasm_instance().module_object().native_module();
722    uint8_t opcode = native_module->wire_bytes()[position];
723    if (opcode == kExprReturn) return true;
724    // Another implicit return is at the last kExprEnd in the function body.
725    int func_index = frame->function_index();
726    WireBytesRef code = native_module->module()->functions[func_index].code;
727    return static_cast<size_t>(position) == code.end_offset() - 1;
728  }
729
730  // Isolate-specific data, for debugging modules that are shared by multiple
731  // isolates.
732  struct PerIsolateDebugData {
733    // Keeps track of the currently set breakpoints (by offset within that
734    // function).
735    std::unordered_map<int, std::vector<int>> breakpoints_per_function;
736
737    // Store the frame ID when stepping, to avoid overwriting that frame when
738    // setting or removing a breakpoint.
739    StackFrameId stepping_frame = NO_ID;
740  };
741
742  NativeModule* const native_module_;
743
744  mutable base::Mutex debug_side_tables_mutex_;
745
746  // DebugSideTable per code object, lazily initialized.
747  std::unordered_map<const WasmCode*, std::unique_ptr<DebugSideTable>>
748      debug_side_tables_;
749
750  // {mutex_} protects all fields below.
751  mutable base::Mutex mutex_;
752
753  // Cache a fixed number of WasmCode objects that were generated for debugging.
754  // This is useful especially in stepping, because stepping code is cleared on
755  // every pause and re-installed on the next step.
756  // This is a LRU cache (most recently used entries first).
757  static constexpr size_t kMaxCachedDebuggingCode = 3;
758  struct CachedDebuggingCode {
759    int func_index;
760    base::OwnedVector<const int> breakpoint_offsets;
761    int dead_breakpoint;
762    WasmCode* code;
763  };
764  std::vector<CachedDebuggingCode> cached_debugging_code_;
765
766  // Names of exports, lazily derived from the exports table.
767  std::unique_ptr<std::map<ImportExportKey, wasm::WireBytesRef>> export_names_;
768
769  // Names of imports, lazily derived from the imports table.
770  std::unique_ptr<std::map<ImportExportKey,
771                           std::pair<wasm::WireBytesRef, wasm::WireBytesRef>>>
772      import_names_;
773
774  // Names of types, lazily decoded from the wire bytes.
775  std::unique_ptr<NameMap> type_names_;
776  // Names of locals, lazily decoded from the wire bytes.
777  std::unique_ptr<IndirectNameMap> local_names_;
778  // Names of struct fields, lazily decoded from the wire bytes.
779  std::unique_ptr<IndirectNameMap> field_names_;
780
781  // Isolate-specific data.
782  std::unordered_map<Isolate*, PerIsolateDebugData> per_isolate_data_;
783};
784
785DebugInfo::DebugInfo(NativeModule* native_module)
786    : impl_(std::make_unique<DebugInfoImpl>(native_module)) {}
787
788DebugInfo::~DebugInfo() = default;
789
790int DebugInfo::GetNumLocals(Address pc) { return impl_->GetNumLocals(pc); }
791
792WasmValue DebugInfo::GetLocalValue(int local, Address pc, Address fp,
793                                   Address debug_break_fp, Isolate* isolate) {
794  return impl_->GetLocalValue(local, pc, fp, debug_break_fp, isolate);
795}
796
797int DebugInfo::GetStackDepth(Address pc) { return impl_->GetStackDepth(pc); }
798
799WasmValue DebugInfo::GetStackValue(int index, Address pc, Address fp,
800                                   Address debug_break_fp, Isolate* isolate) {
801  return impl_->GetStackValue(index, pc, fp, debug_break_fp, isolate);
802}
803
804const wasm::WasmFunction& DebugInfo::GetFunctionAtAddress(Address pc) {
805  return impl_->GetFunctionAtAddress(pc);
806}
807
808WireBytesRef DebugInfo::GetExportName(ImportExportKindCode code,
809                                      uint32_t index) {
810  return impl_->GetExportName(code, index);
811}
812
813std::pair<WireBytesRef, WireBytesRef> DebugInfo::GetImportName(
814    ImportExportKindCode code, uint32_t index) {
815  return impl_->GetImportName(code, index);
816}
817
818WireBytesRef DebugInfo::GetTypeName(int type_index) {
819  return impl_->GetTypeName(type_index);
820}
821
822WireBytesRef DebugInfo::GetLocalName(int func_index, int local_index) {
823  return impl_->GetLocalName(func_index, local_index);
824}
825
826WireBytesRef DebugInfo::GetFieldName(int struct_index, int field_index) {
827  return impl_->GetFieldName(struct_index, field_index);
828}
829
830void DebugInfo::SetBreakpoint(int func_index, int offset,
831                              Isolate* current_isolate) {
832  impl_->SetBreakpoint(func_index, offset, current_isolate);
833}
834
835bool DebugInfo::PrepareStep(WasmFrame* frame) {
836  return impl_->PrepareStep(frame);
837}
838
839void DebugInfo::PrepareStepOutTo(WasmFrame* frame) {
840  impl_->PrepareStepOutTo(frame);
841}
842
843void DebugInfo::ClearStepping(Isolate* isolate) {
844  impl_->ClearStepping(isolate);
845}
846
847void DebugInfo::ClearStepping(WasmFrame* frame) { impl_->ClearStepping(frame); }
848
849bool DebugInfo::IsStepping(WasmFrame* frame) {
850  return impl_->IsStepping(frame);
851}
852
853void DebugInfo::RemoveBreakpoint(int func_index, int offset,
854                                 Isolate* current_isolate) {
855  impl_->RemoveBreakpoint(func_index, offset, current_isolate);
856}
857
858void DebugInfo::RemoveDebugSideTables(base::Vector<WasmCode* const> code) {
859  impl_->RemoveDebugSideTables(code);
860}
861
862DebugSideTable* DebugInfo::GetDebugSideTableIfExists(
863    const WasmCode* code) const {
864  return impl_->GetDebugSideTableIfExists(code);
865}
866
867void DebugInfo::RemoveIsolate(Isolate* isolate) {
868  return impl_->RemoveIsolate(isolate);
869}
870
871}  // namespace wasm
872
873namespace {
874
875// Return the next breakable position at or after {offset_in_func} in function
876// {func_index}, or 0 if there is none.
877// Note that 0 is never a breakable position in wasm, since the first byte
878// contains the locals count for the function.
879int FindNextBreakablePosition(wasm::NativeModule* native_module, int func_index,
880                              int offset_in_func) {
881  AccountingAllocator alloc;
882  Zone tmp(&alloc, ZONE_NAME);
883  wasm::BodyLocalDecls locals(&tmp);
884  const byte* module_start = native_module->wire_bytes().begin();
885  const wasm::WasmFunction& func =
886      native_module->module()->functions[func_index];
887  wasm::BytecodeIterator iterator(module_start + func.code.offset(),
888                                  module_start + func.code.end_offset(),
889                                  &locals);
890  DCHECK_LT(0, locals.encoded_size);
891  if (offset_in_func < 0) return 0;
892  for (; iterator.has_next(); iterator.next()) {
893    if (iterator.pc_offset() < static_cast<uint32_t>(offset_in_func)) continue;
894    if (!wasm::WasmOpcodes::IsBreakable(iterator.current())) continue;
895    return static_cast<int>(iterator.pc_offset());
896  }
897  return 0;
898}
899
900void SetBreakOnEntryFlag(Script script, bool enabled) {
901  if (script.break_on_entry() == enabled) return;
902
903  script.set_break_on_entry(enabled);
904  // Update the "break_on_entry" flag on all live instances.
905  i::WeakArrayList weak_instance_list = script.wasm_weak_instance_list();
906  for (int i = 0; i < weak_instance_list.length(); ++i) {
907    if (weak_instance_list.Get(i)->IsCleared()) continue;
908    i::WasmInstanceObject instance =
909        i::WasmInstanceObject::cast(weak_instance_list.Get(i)->GetHeapObject());
910    instance.set_break_on_entry(enabled);
911  }
912}
913}  // namespace
914
915// static
916bool WasmScript::SetBreakPoint(Handle<Script> script, int* position,
917                               Handle<BreakPoint> break_point) {
918  DCHECK_NE(kOnEntryBreakpointPosition, *position);
919
920  // Find the function for this breakpoint.
921  const wasm::WasmModule* module = script->wasm_native_module()->module();
922  int func_index = GetContainingWasmFunction(module, *position);
923  if (func_index < 0) return false;
924  const wasm::WasmFunction& func = module->functions[func_index];
925  int offset_in_func = *position - func.code.offset();
926
927  int breakable_offset = FindNextBreakablePosition(script->wasm_native_module(),
928                                                   func_index, offset_in_func);
929  if (breakable_offset == 0) return false;
930  *position = func.code.offset() + breakable_offset;
931
932  return WasmScript::SetBreakPointForFunction(script, func_index,
933                                              breakable_offset, break_point);
934}
935
936// static
937void WasmScript::SetInstrumentationBreakpoint(Handle<Script> script,
938                                              Handle<BreakPoint> break_point) {
939  // Special handling for on-entry breakpoints.
940  AddBreakpointToInfo(script, kOnEntryBreakpointPosition, break_point);
941
942  // Update the "break_on_entry" flag on all live instances.
943  SetBreakOnEntryFlag(*script, true);
944}
945
946// static
947bool WasmScript::SetBreakPointOnFirstBreakableForFunction(
948    Handle<Script> script, int func_index, Handle<BreakPoint> break_point) {
949  if (func_index < 0) return false;
950  int offset_in_func = 0;
951
952  int breakable_offset = FindNextBreakablePosition(script->wasm_native_module(),
953                                                   func_index, offset_in_func);
954  if (breakable_offset == 0) return false;
955  return WasmScript::SetBreakPointForFunction(script, func_index,
956                                              breakable_offset, break_point);
957}
958
959// static
960bool WasmScript::SetBreakPointForFunction(Handle<Script> script, int func_index,
961                                          int offset,
962                                          Handle<BreakPoint> break_point) {
963  Isolate* isolate = script->GetIsolate();
964
965  DCHECK_LE(0, func_index);
966  DCHECK_NE(0, offset);
967
968  // Find the function for this breakpoint.
969  wasm::NativeModule* native_module = script->wasm_native_module();
970  const wasm::WasmModule* module = native_module->module();
971  const wasm::WasmFunction& func = module->functions[func_index];
972
973  // Insert new break point into {wasm_breakpoint_infos} of the script.
974  AddBreakpointToInfo(script, func.code.offset() + offset, break_point);
975
976  native_module->GetDebugInfo()->SetBreakpoint(func_index, offset, isolate);
977
978  return true;
979}
980
981namespace {
982
983int GetBreakpointPos(Isolate* isolate, Object break_point_info_or_undef) {
984  if (break_point_info_or_undef.IsUndefined(isolate)) return kMaxInt;
985  return BreakPointInfo::cast(break_point_info_or_undef).source_position();
986}
987
988int FindBreakpointInfoInsertPos(Isolate* isolate,
989                                Handle<FixedArray> breakpoint_infos,
990                                int position) {
991  // Find insert location via binary search, taking care of undefined values on
992  // the right. {position} is either {kOnEntryBreakpointPosition} (which is -1),
993  // or positive.
994  DCHECK(position == WasmScript::kOnEntryBreakpointPosition || position > 0);
995
996  int left = 0;                            // inclusive
997  int right = breakpoint_infos->length();  // exclusive
998  while (right - left > 1) {
999    int mid = left + (right - left) / 2;
1000    Object mid_obj = breakpoint_infos->get(mid);
1001    if (GetBreakpointPos(isolate, mid_obj) <= position) {
1002      left = mid;
1003    } else {
1004      right = mid;
1005    }
1006  }
1007
1008  int left_pos = GetBreakpointPos(isolate, breakpoint_infos->get(left));
1009  return left_pos < position ? left + 1 : left;
1010}
1011
1012}  // namespace
1013
1014// static
1015bool WasmScript::ClearBreakPoint(Handle<Script> script, int position,
1016                                 Handle<BreakPoint> break_point) {
1017  if (!script->has_wasm_breakpoint_infos()) return false;
1018
1019  Isolate* isolate = script->GetIsolate();
1020  Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate);
1021
1022  int pos = FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position);
1023
1024  // Does a BreakPointInfo object already exist for this position?
1025  if (pos == breakpoint_infos->length()) return false;
1026
1027  Handle<BreakPointInfo> info(BreakPointInfo::cast(breakpoint_infos->get(pos)),
1028                              isolate);
1029  BreakPointInfo::ClearBreakPoint(isolate, info, break_point);
1030
1031  // Check if there are no more breakpoints at this location.
1032  if (info->GetBreakPointCount(isolate) == 0) {
1033    // Update array by moving breakpoints up one position.
1034    for (int i = pos; i < breakpoint_infos->length() - 1; i++) {
1035      Object entry = breakpoint_infos->get(i + 1);
1036      breakpoint_infos->set(i, entry);
1037      if (entry.IsUndefined(isolate)) break;
1038    }
1039    // Make sure last array element is empty as a result.
1040    breakpoint_infos->set_undefined(breakpoint_infos->length() - 1);
1041  }
1042
1043  if (break_point->id() == v8::internal::Debug::kInstrumentationId) {
1044    // Special handling for instrumentation breakpoints.
1045    SetBreakOnEntryFlag(*script, false);
1046  } else {
1047    // Remove the breakpoint from DebugInfo and recompile.
1048    wasm::NativeModule* native_module = script->wasm_native_module();
1049    const wasm::WasmModule* module = native_module->module();
1050    int func_index = GetContainingWasmFunction(module, position);
1051    native_module->GetDebugInfo()->RemoveBreakpoint(func_index, position,
1052                                                    isolate);
1053  }
1054
1055  return true;
1056}
1057
1058// static
1059bool WasmScript::ClearBreakPointById(Handle<Script> script, int breakpoint_id) {
1060  if (!script->has_wasm_breakpoint_infos()) {
1061    return false;
1062  }
1063  Isolate* isolate = script->GetIsolate();
1064  Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate);
1065  // If the array exists, it should not be empty.
1066  DCHECK_LT(0, breakpoint_infos->length());
1067
1068  for (int i = 0, e = breakpoint_infos->length(); i < e; ++i) {
1069    Handle<Object> obj(breakpoint_infos->get(i), isolate);
1070    if (obj->IsUndefined(isolate)) {
1071      continue;
1072    }
1073    Handle<BreakPointInfo> breakpoint_info = Handle<BreakPointInfo>::cast(obj);
1074    Handle<BreakPoint> breakpoint;
1075    if (BreakPointInfo::GetBreakPointById(isolate, breakpoint_info,
1076                                          breakpoint_id)
1077            .ToHandle(&breakpoint)) {
1078      DCHECK(breakpoint->id() == breakpoint_id);
1079      return WasmScript::ClearBreakPoint(
1080          script, breakpoint_info->source_position(), breakpoint);
1081    }
1082  }
1083  return false;
1084}
1085
1086// static
1087void WasmScript::ClearAllBreakpoints(Script script) {
1088  script.set_wasm_breakpoint_infos(
1089      ReadOnlyRoots(script.GetIsolate()).empty_fixed_array());
1090  SetBreakOnEntryFlag(script, false);
1091}
1092
1093// static
1094void WasmScript::AddBreakpointToInfo(Handle<Script> script, int position,
1095                                     Handle<BreakPoint> break_point) {
1096  Isolate* isolate = script->GetIsolate();
1097  Handle<FixedArray> breakpoint_infos;
1098  if (script->has_wasm_breakpoint_infos()) {
1099    breakpoint_infos = handle(script->wasm_breakpoint_infos(), isolate);
1100  } else {
1101    breakpoint_infos =
1102        isolate->factory()->NewFixedArray(4, AllocationType::kOld);
1103    script->set_wasm_breakpoint_infos(*breakpoint_infos);
1104  }
1105
1106  int insert_pos =
1107      FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position);
1108
1109  // If a BreakPointInfo object already exists for this position, add the new
1110  // breakpoint object and return.
1111  if (insert_pos < breakpoint_infos->length() &&
1112      GetBreakpointPos(isolate, breakpoint_infos->get(insert_pos)) ==
1113          position) {
1114    Handle<BreakPointInfo> old_info(
1115        BreakPointInfo::cast(breakpoint_infos->get(insert_pos)), isolate);
1116    BreakPointInfo::SetBreakPoint(isolate, old_info, break_point);
1117    return;
1118  }
1119
1120  // Enlarge break positions array if necessary.
1121  bool need_realloc = !breakpoint_infos->get(breakpoint_infos->length() - 1)
1122                           .IsUndefined(isolate);
1123  Handle<FixedArray> new_breakpoint_infos = breakpoint_infos;
1124  if (need_realloc) {
1125    new_breakpoint_infos = isolate->factory()->NewFixedArray(
1126        2 * breakpoint_infos->length(), AllocationType::kOld);
1127    script->set_wasm_breakpoint_infos(*new_breakpoint_infos);
1128    // Copy over the entries [0, insert_pos).
1129    for (int i = 0; i < insert_pos; ++i)
1130      new_breakpoint_infos->set(i, breakpoint_infos->get(i));
1131  }
1132
1133  // Move elements [insert_pos, ...] up by one.
1134  for (int i = breakpoint_infos->length() - 1; i >= insert_pos; --i) {
1135    Object entry = breakpoint_infos->get(i);
1136    if (entry.IsUndefined(isolate)) continue;
1137    new_breakpoint_infos->set(i + 1, entry);
1138  }
1139
1140  // Generate new BreakpointInfo.
1141  Handle<BreakPointInfo> breakpoint_info =
1142      isolate->factory()->NewBreakPointInfo(position);
1143  BreakPointInfo::SetBreakPoint(isolate, breakpoint_info, break_point);
1144
1145  // Now insert new position at insert_pos.
1146  new_breakpoint_infos->set(insert_pos, *breakpoint_info);
1147}
1148
1149// static
1150bool WasmScript::GetPossibleBreakpoints(
1151    wasm::NativeModule* native_module, const v8::debug::Location& start,
1152    const v8::debug::Location& end,
1153    std::vector<v8::debug::BreakLocation>* locations) {
1154  DisallowGarbageCollection no_gc;
1155
1156  const wasm::WasmModule* module = native_module->module();
1157  const std::vector<wasm::WasmFunction>& functions = module->functions;
1158
1159  if (start.GetLineNumber() != 0 || start.GetColumnNumber() < 0 ||
1160      (!end.IsEmpty() &&
1161       (end.GetLineNumber() != 0 || end.GetColumnNumber() < 0 ||
1162        end.GetColumnNumber() < start.GetColumnNumber())))
1163    return false;
1164
1165  // start_func_index, start_offset and end_func_index is inclusive.
1166  // end_offset is exclusive.
1167  // start_offset and end_offset are module-relative byte offsets.
1168  // We set strict to false because offsets may be between functions.
1169  int start_func_index =
1170      GetNearestWasmFunction(module, start.GetColumnNumber());
1171  if (start_func_index < 0) return false;
1172  uint32_t start_offset = start.GetColumnNumber();
1173  int end_func_index;
1174  uint32_t end_offset;
1175
1176  if (end.IsEmpty()) {
1177    // Default: everything till the end of the Script.
1178    end_func_index = static_cast<uint32_t>(functions.size() - 1);
1179    end_offset = functions[end_func_index].code.end_offset();
1180  } else {
1181    // If end is specified: Use it and check for valid input.
1182    end_offset = end.GetColumnNumber();
1183    end_func_index = GetNearestWasmFunction(module, end_offset);
1184    DCHECK_GE(end_func_index, start_func_index);
1185  }
1186
1187  if (start_func_index == end_func_index &&
1188      start_offset > functions[end_func_index].code.end_offset())
1189    return false;
1190  AccountingAllocator alloc;
1191  Zone tmp(&alloc, ZONE_NAME);
1192  const byte* module_start = native_module->wire_bytes().begin();
1193
1194  for (int func_idx = start_func_index; func_idx <= end_func_index;
1195       ++func_idx) {
1196    const wasm::WasmFunction& func = functions[func_idx];
1197    if (func.code.length() == 0) continue;
1198
1199    wasm::BodyLocalDecls locals(&tmp);
1200    wasm::BytecodeIterator iterator(module_start + func.code.offset(),
1201                                    module_start + func.code.end_offset(),
1202                                    &locals);
1203    DCHECK_LT(0u, locals.encoded_size);
1204    for (; iterator.has_next(); iterator.next()) {
1205      uint32_t total_offset = func.code.offset() + iterator.pc_offset();
1206      if (total_offset >= end_offset) {
1207        DCHECK_EQ(end_func_index, func_idx);
1208        break;
1209      }
1210      if (total_offset < start_offset) continue;
1211      if (!wasm::WasmOpcodes::IsBreakable(iterator.current())) continue;
1212      locations->emplace_back(0, total_offset, debug::kCommonBreakLocation);
1213    }
1214  }
1215  return true;
1216}
1217
1218namespace {
1219
1220bool CheckBreakPoint(Isolate* isolate, Handle<BreakPoint> break_point,
1221                     StackFrameId frame_id) {
1222  if (break_point->condition().length() == 0) return true;
1223
1224  HandleScope scope(isolate);
1225  Handle<String> condition(break_point->condition(), isolate);
1226  Handle<Object> result;
1227  // The Wasm engine doesn't perform any sort of inlining.
1228  const int inlined_jsframe_index = 0;
1229  const bool throw_on_side_effect = false;
1230  if (!DebugEvaluate::Local(isolate, frame_id, inlined_jsframe_index, condition,
1231                            throw_on_side_effect)
1232           .ToHandle(&result)) {
1233    isolate->clear_pending_exception();
1234    return false;
1235  }
1236  return result->BooleanValue(isolate);
1237}
1238
1239}  // namespace
1240
1241// static
1242MaybeHandle<FixedArray> WasmScript::CheckBreakPoints(Isolate* isolate,
1243                                                     Handle<Script> script,
1244                                                     int position,
1245                                                     StackFrameId frame_id) {
1246  if (!script->has_wasm_breakpoint_infos()) return {};
1247
1248  Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate);
1249  int insert_pos =
1250      FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position);
1251  if (insert_pos >= breakpoint_infos->length()) return {};
1252
1253  Handle<Object> maybe_breakpoint_info(breakpoint_infos->get(insert_pos),
1254                                       isolate);
1255  if (maybe_breakpoint_info->IsUndefined(isolate)) return {};
1256  Handle<BreakPointInfo> breakpoint_info =
1257      Handle<BreakPointInfo>::cast(maybe_breakpoint_info);
1258  if (breakpoint_info->source_position() != position) return {};
1259
1260  Handle<Object> break_points(breakpoint_info->break_points(), isolate);
1261  if (!break_points->IsFixedArray()) {
1262    if (!CheckBreakPoint(isolate, Handle<BreakPoint>::cast(break_points),
1263                         frame_id)) {
1264      return {};
1265    }
1266    Handle<FixedArray> break_points_hit = isolate->factory()->NewFixedArray(1);
1267    break_points_hit->set(0, *break_points);
1268    return break_points_hit;
1269  }
1270
1271  Handle<FixedArray> array = Handle<FixedArray>::cast(break_points);
1272  Handle<FixedArray> break_points_hit =
1273      isolate->factory()->NewFixedArray(array->length());
1274  int break_points_hit_count = 0;
1275  for (int i = 0; i < array->length(); ++i) {
1276    Handle<BreakPoint> break_point(BreakPoint::cast(array->get(i)), isolate);
1277    if (CheckBreakPoint(isolate, break_point, frame_id)) {
1278      break_points_hit->set(break_points_hit_count++, *break_point);
1279    }
1280  }
1281  if (break_points_hit_count == 0) return {};
1282  break_points_hit->Shrink(isolate, break_points_hit_count);
1283  return break_points_hit;
1284}
1285
1286}  // namespace internal
1287}  // namespace v8
1288