/*
 * Copyright 2016 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef wasm_debug_h
#define wasm_debug_h

#include "js/ColumnNumber.h"  // JS::LimitedColumnNumberOneOrigin
#include "js/HashTable.h"
#include "wasm/WasmCode.h"
#include "wasm/WasmCodegenTypes.h"
#include "wasm/WasmConstants.h"
#include "wasm/WasmExprType.h"
#include "wasm/WasmModule.h"
#include "wasm/WasmTypeDecls.h"
#include "wasm/WasmValType.h"

namespace js {

class Debugger;
class WasmBreakpointSite;
class WasmInstanceObject;

namespace wasm {

// The generated source location for the AST node/expression. The offset field
// refers an offset in an binary format file.

struct ExprLoc {
  uint32_t lineno;
  uint32_t column;
  uint32_t offset;
  ExprLoc() : lineno(0), column(0), offset(0) {}
  ExprLoc(uint32_t lineno_, uint32_t column_, uint32_t offset_)
      : lineno(lineno_), column(column_), offset(offset_) {}
};

using StepperCounters =
    HashMap<uint32_t, uint32_t, DefaultHasher<uint32_t>, SystemAllocPolicy>;
using WasmBreakpointSiteMap =
    HashMap<uint32_t, WasmBreakpointSite*, DefaultHasher<uint32_t>,
            SystemAllocPolicy>;

/*
 * [SMDOC] Wasm debug traps
 *
 * There is a single debug-trap handler for the process, WasmHandleDebugTrap
 * in WasmBuiltins.cpp.  That function is invoked through the Debug Trap Stub,
 * of which there is one per module, generated by GenerateDebugStub in
 * WasmStubs.cpp.  When any function in an instance needs to debug-trap for
 * any reason (enter frame, leave frame, breakpoint, or single-stepping) then
 * a pointer to the Debug Trap Stub is installed in the Instance.
 * Debug-enabled code will look for this pointer and call it if (1) it is not
 * null and (2) subject to filtering as follows.
 *
 * WasmHandleDebugTrap may therefore be called very frequently when any
 * function in the instance is being debugged, and must filter the trap
 * against the tables in the DebugState.  It can make use of the return
 * address for the call, which identifies the site uniquely.
 *
 * In order to greatly reduce the frequency of calls to the Debug Trap Stub,
 * an array of flag bits, one per function, is attached to the instance.  The
 * code at the breakable point calls a stub at the end of the function (the
 * Per Function Debug Stub) to check whether the bit is set for the function.
 * If it is not set, the per-function stub can return to its caller
 * immediately; if the bit is set, the per-function stub will jump to the
 * installed (per-module) Debug Trap Stub.
 *
 * See also [SMDOC] "Wasm debug traps -- code details"
 */

class DebugState {
  const SharedCode code_;
  const SharedModule module_;

  // State maintained when debugging is enabled.

  bool enterFrameTrapsEnabled_;
  uint32_t enterAndLeaveFrameTrapsCounter_;
  WasmBreakpointSiteMap breakpointSites_;
  StepperCounters stepperCounters_;

  void enableDebuggingForFunction(Instance* instance, uint32_t funcIndex);
  void disableDebuggingForFunction(Instance* instance, uint32_t funcIndex);
  void enableDebugTrapping(Instance* instance);
  void disableDebugTrapping(Instance* instance);

 public:
  DebugState(const Code& code, const Module& module);

  void trace(JSTracer* trc);
  void finalize(JS::GCContext* gcx);

  const BytecodeSource& bytecode() const { return module_->debugBytecode(); }

  [[nodiscard]] bool getLineOffsets(size_t lineno, Vector<uint32_t>* offsets);
  [[nodiscard]] bool getAllColumnOffsets(Vector<ExprLoc>* offsets);
  [[nodiscard]] bool getOffsetLocation(
      uint32_t offset, uint32_t* lineno,
      JS::LimitedColumnNumberOneOrigin* column);

  // The Code can track enter/leave frame events. Any such event triggers
  // debug trap. The enter/leave frame events enabled or disabled across
  // all functions.

  void adjustEnterAndLeaveFrameTrapsState(JSContext* cx, Instance* instance,
                                          bool enabled);
  void ensureEnterFrameTrapsState(JSContext* cx, Instance* instance,
                                  bool enabled);
  bool enterFrameTrapsEnabled() const { return enterFrameTrapsEnabled_; }

  // When the Code is debugEnabled, individual breakpoints can be enabled or
  // disabled at instruction offsets.

  bool hasBreakpointTrapAtOffset(uint32_t offset);
  void toggleBreakpointTrap(JSRuntime* rt, Instance* instance, uint32_t offset,
                            bool enabled);
  WasmBreakpointSite* getBreakpointSite(uint32_t offset) const;
  WasmBreakpointSite* getOrCreateBreakpointSite(JSContext* cx,
                                                Instance* instance,
                                                uint32_t offset);
  bool hasBreakpointSite(uint32_t offset);
  void destroyBreakpointSite(JS::GCContext* gcx, Instance* instance,
                             uint32_t offset);
  void clearBreakpointsIn(JS::GCContext* gcx, WasmInstanceObject* instance,
                          js::Debugger* dbg, JSObject* handler);

  // When the Code is debug-enabled, single-stepping mode can be toggled on
  // the granularity of individual functions.

  bool stepModeEnabled(uint32_t funcIndex) const;
  [[nodiscard]] bool incrementStepperCount(JSContext* cx, Instance* instance,
                                           uint32_t funcIndex);
  void decrementStepperCount(JS::GCContext* gcx, Instance* instance,
                             uint32_t funcIndex);

  // Stack inspection helpers.

  [[nodiscard]] bool debugGetLocalTypes(uint32_t funcIndex,
                                        ValTypeVector* locals,
                                        size_t* argsLength,
                                        StackResults* stackResults);
  [[nodiscard]] bool getGlobal(Instance& instance, uint32_t globalIndex,
                               MutableHandleValue vp);

  // Debug URL helpers.

  [[nodiscard]] bool getSourceMappingURL(JSContext* cx,
                                         MutableHandleString result) const;

  // Accessors for commonly used elements of linked structures.

  const CodeBlock& debugCode() const { return code_->debugCodeBlock(); }
  const CodeSegment& debugSegment() const {
    return *code_->debugCodeBlock().segment;
  }
  const CodeMetadata& codeMeta() const { return code_->codeMeta(); }
  const CodeTailMetadata& codeTailMeta() const { return code_->codeTailMeta(); }

  uint32_t funcToCodeRangeIndex(uint32_t funcIndex) const {
    return debugCode().funcToCodeRange[funcIndex];
  }

  // about:memory reporting:

  void addSizeOfMisc(mozilla::MallocSizeOf mallocSizeOf,
                     CodeMetadata::SeenSet* seenCodeMeta,
                     Code::SeenSet* seenCode, size_t* code, size_t* data) const;
};

using UniqueDebugState = UniquePtr<DebugState>;

}  // namespace wasm
}  // namespace js

#endif  // wasm_debug_h
