https://github.com/mozilla/gecko-dev
Raw File
Tip revision: ed635421756a45e0fddd91f20f0cc7fa68aab395 authored by Mihai Tabara on 22 September 2020, 12:14:25 UTC
Bug 1666434 - remove all cron jobs because EOL.DONTBUILD CLOSED TREE r=bhearsum a=release
Tip revision: ed63542
IonCode.h
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef jit_IonCode_h
#define jit_IonCode_h

#include "mozilla/Atomics.h"
#include "mozilla/MemoryReporting.h"

#include "jstypes.h"

#include "gc/Heap.h"
#include "jit/ExecutableAllocator.h"
#include "jit/ICStubSpace.h"
#include "jit/IonOptimizationLevels.h"
#include "jit/IonTypes.h"
#include "js/UbiNode.h"
#include "vm/TraceLogging.h"

namespace js {
namespace jit {

class IonBuilder;
class JitAllocPolicy;
class JitCode;
class MacroAssembler;

using ObjectVector = Vector<JSObject*, 4, JitAllocPolicy>;
using TraceLoggerEventVector = Vector<TraceLoggerEvent, 0, SystemAllocPolicy>;

// Header at start of raw code buffer
struct JitCodeHeader {
  // Link back to corresponding gcthing
  JitCode* jitCode_;

  // !!! NOTE !!!
  // If we are running on AMD Bobcat, insert a NOP-slide at end of the JitCode
  // header so we can try to recover when the CPU screws up the branch landing
  // site. See Bug 1281759.
  void* nops_;

  void init(JitCode* jitCode);

  static JitCodeHeader* FromExecutable(uint8_t* buffer) {
    return (JitCodeHeader*)(buffer - sizeof(JitCodeHeader));
  }
};

class JitCode : public gc::TenuredCell {
 protected:
  uint8_t* code_;
  ExecutablePool* pool_;
  uint32_t bufferSize_;  // Total buffer size. Does not include headerSize_.
  uint32_t insnSize_;    // Instruction stream size.
  uint32_t dataSize_;    // Size of the read-only data area.
  uint32_t jumpRelocTableBytes_;  // Size of the jump relocation table.
  uint32_t dataRelocTableBytes_;  // Size of the data relocation table.
  uint8_t headerSize_ : 5;        // Number of bytes allocated before codeStart.
  uint8_t kind_ : 3;              // jit::CodeKind, for the memory reporters.
  bool invalidated_ : 1;     // Whether the code object has been invalidated.
                             // This is necessary to prevent GC tracing.
  bool hasBytecodeMap_ : 1;  // Whether the code object has been registered with
                             // native=>bytecode mapping tables.

  JitCode() = delete;
  JitCode(uint8_t* code, uint32_t bufferSize, uint32_t headerSize,
          ExecutablePool* pool, CodeKind kind)
      : code_(code),
        pool_(pool),
        bufferSize_(bufferSize),
        insnSize_(0),
        dataSize_(0),
        jumpRelocTableBytes_(0),
        dataRelocTableBytes_(0),
        headerSize_(headerSize),
        kind_(uint8_t(kind)),
        invalidated_(false),
        hasBytecodeMap_(false) {
    MOZ_ASSERT(CodeKind(kind_) == kind);
    MOZ_ASSERT(headerSize_ == headerSize);
  }

  uint32_t dataOffset() const { return insnSize_; }
  uint32_t jumpRelocTableOffset() const { return dataOffset() + dataSize_; }
  uint32_t dataRelocTableOffset() const {
    return jumpRelocTableOffset() + jumpRelocTableBytes_;
  }

 public:
  uint8_t* raw() const { return code_; }
  uint8_t* rawEnd() const { return code_ + insnSize_; }
  bool containsNativePC(const void* addr) const {
    const uint8_t* addr_u8 = (const uint8_t*)addr;
    return raw() <= addr_u8 && addr_u8 < rawEnd();
  }
  size_t instructionsSize() const { return insnSize_; }
  size_t bufferSize() const { return bufferSize_; }
  size_t headerSize() const { return headerSize_; }

  void traceChildren(JSTracer* trc);
  void finalize(FreeOp* fop);
  void setInvalidated() { invalidated_ = true; }

  void setHasBytecodeMap() { hasBytecodeMap_ = true; }

  // If this JitCode object has been, effectively, corrupted due to
  // invalidation patching, then we have to remember this so we don't try and
  // trace relocation entries that may now be corrupt.
  bool invalidated() const { return !!invalidated_; }

  template <typename T>
  T as() const {
    return JS_DATA_TO_FUNC_PTR(T, raw());
  }

  void copyFrom(MacroAssembler& masm);

  static JitCode* FromExecutable(uint8_t* buffer) {
    JitCode* code = JitCodeHeader::FromExecutable(buffer)->jitCode_;
    MOZ_ASSERT(code->raw() == buffer);
    return code;
  }

  static size_t offsetOfCode() { return offsetof(JitCode, code_); }

  uint8_t* jumpRelocTable() { return code_ + jumpRelocTableOffset(); }

  // Allocates a new JitCode object which will be managed by the GC. If no
  // object can be allocated, nullptr is returned. On failure, |pool| is
  // automatically released, so the code may be freed.
  template <AllowGC allowGC>
  static JitCode* New(JSContext* cx, uint8_t* code, uint32_t bufferSize,
                      uint32_t headerSize, ExecutablePool* pool, CodeKind kind);

 public:
  static const JS::TraceKind TraceKind = JS::TraceKind::JitCode;
};

class SnapshotWriter;
class RecoverWriter;
class SafepointWriter;
class SafepointIndex;
class OsiIndex;
class IonIC;

// An IonScript attaches Ion-generated information to a JSScript.
struct IonScript {
 private:
  // Code pointer containing the actual method.
  HeapPtrJitCode method_;

  // Entrypoint for OSR, or nullptr.
  jsbytecode* osrPc_;

  // Offset to OSR entrypoint from method_->raw(), or 0.
  uint32_t osrEntryOffset_;

  // Offset to entrypoint skipping type arg check from method_->raw().
  uint32_t skipArgCheckEntryOffset_;

  // Offset of the invalidation epilogue (which pushes this IonScript
  // and calls the invalidation thunk).
  uint32_t invalidateEpilogueOffset_;

  // The offset immediately after the IonScript immediate.
  // NOTE: technically a constant delta from
  // |invalidateEpilogueOffset_|, so we could hard-code this
  // per-platform if we want.
  uint32_t invalidateEpilogueDataOffset_;

  // Number of times this script bailed out without invalidation.
  uint32_t numBailouts_;

  // Flag set if IonScript was compiled with profiling enabled.
  bool hasProfilingInstrumentation_;

  // Flag for if this script is getting recompiled.
  uint32_t recompiling_;

  // Any kind of data needed by the runtime, these can be either cache
  // information or profiling info.
  uint32_t runtimeData_;
  uint32_t runtimeSize_;

  // State for polymorphic caches in the compiled code. All caches are stored
  // in the runtimeData buffer and indexed by the icIndex which gives a
  // relative offset in the runtimeData array.
  uint32_t icIndex_;
  uint32_t icEntries_;

  // Map code displacement to safepoint / OSI-patch-delta.
  uint32_t safepointIndexOffset_;
  uint32_t safepointIndexEntries_;

  // Offset to and length of the safepoint table in bytes.
  uint32_t safepointsStart_;
  uint32_t safepointsSize_;

  // Number of bytes this function reserves on the stack.
  uint32_t frameSlots_;

  // Number of bytes used passed in as formal arguments or |this|.
  uint32_t argumentSlots_;

  // Frame size is the value that can be added to the StackPointer along
  // with the frame prefix to get a valid JitFrameLayout.
  uint32_t frameSize_;

  // Table mapping bailout IDs to snapshot offsets.
  uint32_t bailoutTable_;
  uint32_t bailoutEntries_;

  // Map OSI-point displacement to snapshot.
  uint32_t osiIndexOffset_;
  uint32_t osiIndexEntries_;

  // Offset from the start of the code buffer to its snapshot buffer.
  uint32_t snapshots_;
  uint32_t snapshotsListSize_;
  uint32_t snapshotsRVATableSize_;

  // List of instructions needed to recover stack frames.
  uint32_t recovers_;
  uint32_t recoversSize_;

  // Constant table for constants stored in snapshots.
  uint32_t constantTable_;
  uint32_t constantEntries_;

  // Number of references from invalidation records.
  uint32_t invalidationCount_;

  // Identifier of the compilation which produced this code.
  IonCompilationId compilationId_;

  // The optimization level this script was compiled in.
  OptimizationLevel optimizationLevel_;

  // Number of times we tried to enter this script via OSR but failed due to
  // a LOOPENTRY pc other than osrPc_.
  uint32_t osrPcMismatchCounter_;

  // TraceLogger events that are baked into the IonScript.
  TraceLoggerEventVector traceLoggerEvents_;

 private:
  inline uint8_t* bottomBuffer() { return reinterpret_cast<uint8_t*>(this); }
  inline const uint8_t* bottomBuffer() const {
    return reinterpret_cast<const uint8_t*>(this);
  }

 public:
  SnapshotOffset* bailoutTable() {
    return (SnapshotOffset*)&bottomBuffer()[bailoutTable_];
  }
  PreBarrieredValue* constants() {
    // Nursery constants are manually barriered in CodeGenerator::link() so a
    // post barrier is not required..
    return (PreBarrieredValue*)&bottomBuffer()[constantTable_];
  }
  const SafepointIndex* safepointIndices() const {
    return const_cast<IonScript*>(this)->safepointIndices();
  }
  SafepointIndex* safepointIndices() {
    return (SafepointIndex*)&bottomBuffer()[safepointIndexOffset_];
  }
  const OsiIndex* osiIndices() const {
    return const_cast<IonScript*>(this)->osiIndices();
  }
  OsiIndex* osiIndices() { return (OsiIndex*)&bottomBuffer()[osiIndexOffset_]; }
  uint32_t* icIndex() { return (uint32_t*)&bottomBuffer()[icIndex_]; }
  uint8_t* runtimeData() { return &bottomBuffer()[runtimeData_]; }

 private:
  void trace(JSTracer* trc);

 public:
  // Do not call directly, use IonScript::New. This is public for cx->new_.
  explicit IonScript(IonCompilationId compilationId);

  static IonScript* New(JSContext* cx, IonCompilationId compilationId,
                        uint32_t frameSlots, uint32_t argumentSlots,
                        uint32_t frameSize, size_t snapshotsListSize,
                        size_t snapshotsRVATableSize, size_t recoversSize,
                        size_t bailoutEntries, size_t constants,
                        size_t safepointIndexEntries, size_t osiIndexEntries,
                        size_t icEntries, size_t runtimeSize,
                        size_t safepointsSize,
                        OptimizationLevel optimizationLevel);
  static void Trace(JSTracer* trc, IonScript* script);
  static void Destroy(FreeOp* fop, IonScript* script);

  static inline size_t offsetOfMethod() { return offsetof(IonScript, method_); }
  static inline size_t offsetOfOsrEntryOffset() {
    return offsetof(IonScript, osrEntryOffset_);
  }
  static inline size_t offsetOfSkipArgCheckEntryOffset() {
    return offsetof(IonScript, skipArgCheckEntryOffset_);
  }
  static inline size_t offsetOfInvalidationCount() {
    return offsetof(IonScript, invalidationCount_);
  }
  static inline size_t offsetOfRecompiling() {
    return offsetof(IonScript, recompiling_);
  }

 public:
  JitCode* method() const { return method_; }
  void setMethod(JitCode* code) {
    MOZ_ASSERT(!invalidated());
    method_ = code;
  }
  void setOsrPc(jsbytecode* osrPc) { osrPc_ = osrPc; }
  jsbytecode* osrPc() const { return osrPc_; }
  void setOsrEntryOffset(uint32_t offset) {
    MOZ_ASSERT(!osrEntryOffset_);
    osrEntryOffset_ = offset;
  }
  uint32_t osrEntryOffset() const { return osrEntryOffset_; }
  void setSkipArgCheckEntryOffset(uint32_t offset) {
    MOZ_ASSERT(!skipArgCheckEntryOffset_);
    skipArgCheckEntryOffset_ = offset;
  }
  uint32_t getSkipArgCheckEntryOffset() const {
    return skipArgCheckEntryOffset_;
  }
  bool containsCodeAddress(uint8_t* addr) const {
    return method()->raw() <= addr &&
           addr <= method()->raw() + method()->instructionsSize();
  }
  bool containsReturnAddress(uint8_t* addr) const {
    // This accounts for an off by one error caused by the return address of a
    // bailout sitting outside the range of the containing function.
    return method()->raw() <= addr &&
           addr <= method()->raw() + method()->instructionsSize();
  }
  void setInvalidationEpilogueOffset(uint32_t offset) {
    MOZ_ASSERT(!invalidateEpilogueOffset_);
    invalidateEpilogueOffset_ = offset;
  }
  uint32_t invalidateEpilogueOffset() const {
    MOZ_ASSERT(invalidateEpilogueOffset_);
    return invalidateEpilogueOffset_;
  }
  void setInvalidationEpilogueDataOffset(uint32_t offset) {
    MOZ_ASSERT(!invalidateEpilogueDataOffset_);
    invalidateEpilogueDataOffset_ = offset;
  }
  uint32_t invalidateEpilogueDataOffset() const {
    MOZ_ASSERT(invalidateEpilogueDataOffset_);
    return invalidateEpilogueDataOffset_;
  }
  void incNumBailouts() { numBailouts_++; }
  bool bailoutExpected() const {
    return numBailouts_ >= JitOptions.frequentBailoutThreshold;
  }
  void setHasProfilingInstrumentation() { hasProfilingInstrumentation_ = true; }
  void clearHasProfilingInstrumentation() {
    hasProfilingInstrumentation_ = false;
  }
  bool hasProfilingInstrumentation() const {
    return hasProfilingInstrumentation_;
  }
  MOZ_MUST_USE bool addTraceLoggerEvent(TraceLoggerEvent& event) {
    MOZ_ASSERT(event.hasTextId());
    return traceLoggerEvents_.append(std::move(event));
  }
  const uint8_t* snapshots() const {
    return reinterpret_cast<const uint8_t*>(this) + snapshots_;
  }
  size_t snapshotsListSize() const { return snapshotsListSize_; }
  size_t snapshotsRVATableSize() const { return snapshotsRVATableSize_; }
  const uint8_t* recovers() const {
    return reinterpret_cast<const uint8_t*>(this) + recovers_;
  }
  size_t recoversSize() const { return recoversSize_; }
  const uint8_t* safepoints() const {
    return reinterpret_cast<const uint8_t*>(this) + safepointsStart_;
  }
  size_t safepointsSize() const { return safepointsSize_; }
  size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    return mallocSizeOf(this);
  }
  PreBarrieredValue& getConstant(size_t index) {
    MOZ_ASSERT(index < numConstants());
    return constants()[index];
  }
  size_t numConstants() const { return constantEntries_; }
  uint32_t frameSlots() const { return frameSlots_; }
  uint32_t argumentSlots() const { return argumentSlots_; }
  uint32_t frameSize() const { return frameSize_; }
  SnapshotOffset bailoutToSnapshot(uint32_t bailoutId) {
    MOZ_ASSERT(bailoutId < bailoutEntries_);
    return bailoutTable()[bailoutId];
  }
  const SafepointIndex* getSafepointIndex(uint32_t disp) const;
  const SafepointIndex* getSafepointIndex(uint8_t* retAddr) const {
    MOZ_ASSERT(containsCodeAddress(retAddr));
    return getSafepointIndex(retAddr - method()->raw());
  }
  const OsiIndex* getOsiIndex(uint32_t disp) const;
  const OsiIndex* getOsiIndex(uint8_t* retAddr) const;

  IonIC& getICFromIndex(uint32_t index) {
    MOZ_ASSERT(index < icEntries_);
    uint32_t offset = icIndex()[index];
    return getIC(offset);
  }
  inline IonIC& getIC(uint32_t offset) {
    MOZ_ASSERT(offset < runtimeSize_);
    return *(IonIC*)&runtimeData()[offset];
  }
  size_t numICs() const { return icEntries_; }
  size_t runtimeSize() const { return runtimeSize_; }
  void purgeICs(Zone* zone);
  void copySnapshots(const SnapshotWriter* writer);
  void copyRecovers(const RecoverWriter* writer);
  void copyBailoutTable(const SnapshotOffset* table);
  void copyConstants(const Value* vp);
  void copySafepointIndices(const SafepointIndex* firstSafepointIndex);
  void copyOsiIndices(const OsiIndex* firstOsiIndex);
  void copyRuntimeData(const uint8_t* data);
  void copyICEntries(const uint32_t* caches);
  void copySafepoints(const SafepointWriter* writer);

  bool invalidated() const { return invalidationCount_ != 0; }

  // Invalidate the current compilation.
  void invalidate(JSContext* cx, JSScript* script, bool resetUses,
                  const char* reason);

  size_t invalidationCount() const { return invalidationCount_; }
  void incrementInvalidationCount() { invalidationCount_++; }
  void decrementInvalidationCount(FreeOp* fop) {
    MOZ_ASSERT(invalidationCount_);
    invalidationCount_--;
    if (!invalidationCount_) {
      Destroy(fop, this);
    }
  }
  IonCompilationId compilationId() const { return compilationId_; }
  OptimizationLevel optimizationLevel() const { return optimizationLevel_; }
  uint32_t incrOsrPcMismatchCounter() { return ++osrPcMismatchCounter_; }
  void resetOsrPcMismatchCounter() { osrPcMismatchCounter_ = 0; }

  void setRecompiling() { recompiling_ = true; }

  bool isRecompiling() const { return recompiling_; }

  void clearRecompiling() { recompiling_ = false; }

  enum ShouldIncreaseAge { IncreaseAge = true, KeepAge = false };

  static void writeBarrierPre(Zone* zone, IonScript* ionScript);
};

// Execution information for a basic block which may persist after the
// accompanying IonScript is destroyed, for use during profiling.
struct IonBlockCounts {
 private:
  uint32_t id_;

  // Approximate bytecode in the outer (not inlined) script this block
  // was generated from.
  uint32_t offset_;

  // File and line of the inner script this block was generated from.
  char* description_;

  // ids for successors of this block.
  uint32_t numSuccessors_;
  uint32_t* successors_;

  // Hit count for this block.
  uint64_t hitCount_;

  // Text information about the code generated for this block.
  char* code_;

 public:
  MOZ_MUST_USE bool init(uint32_t id, uint32_t offset, char* description,
                         uint32_t numSuccessors) {
    id_ = id;
    offset_ = offset;
    description_ = description;
    numSuccessors_ = numSuccessors;
    if (numSuccessors) {
      successors_ = js_pod_calloc<uint32_t>(numSuccessors);
      if (!successors_) {
        return false;
      }
    }
    return true;
  }

  void destroy() {
    js_free(description_);
    js_free(successors_);
    js_free(code_);
  }

  uint32_t id() const { return id_; }

  uint32_t offset() const { return offset_; }

  const char* description() const { return description_; }

  size_t numSuccessors() const { return numSuccessors_; }

  void setSuccessor(size_t i, uint32_t id) {
    MOZ_ASSERT(i < numSuccessors_);
    successors_[i] = id;
  }

  uint32_t successor(size_t i) const {
    MOZ_ASSERT(i < numSuccessors_);
    return successors_[i];
  }

  uint64_t* addressOfHitCount() { return &hitCount_; }

  uint64_t hitCount() const { return hitCount_; }

  void setCode(const char* code) {
    char* ncode = js_pod_malloc<char>(strlen(code) + 1);
    if (ncode) {
      strcpy(ncode, code);
      code_ = ncode;
    }
  }

  const char* code() const { return code_; }

  size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    return mallocSizeOf(description_) + mallocSizeOf(successors_) +
           mallocSizeOf(code_);
  }
};

// Execution information for a compiled script which may persist after the
// IonScript is destroyed, for use during profiling.
struct IonScriptCounts {
 private:
  // Any previous invalidated compilation(s) for the script.
  IonScriptCounts* previous_ = nullptr;

  // Information about basic blocks in this script.
  size_t numBlocks_ = 0;
  IonBlockCounts* blocks_ = nullptr;

 public:
  IonScriptCounts() = default;

  ~IonScriptCounts() {
    for (size_t i = 0; i < numBlocks_; i++) {
      blocks_[i].destroy();
    }
    js_free(blocks_);
    // The list can be long in some corner cases (bug 1140084), so
    // unroll the recursion.
    IonScriptCounts* victims = previous_;
    while (victims) {
      IonScriptCounts* victim = victims;
      victims = victim->previous_;
      victim->previous_ = nullptr;
      js_delete(victim);
    }
  }

  MOZ_MUST_USE bool init(size_t numBlocks) {
    blocks_ = js_pod_calloc<IonBlockCounts>(numBlocks);
    if (!blocks_) {
      return false;
    }

    numBlocks_ = numBlocks;
    return true;
  }

  size_t numBlocks() const { return numBlocks_; }

  IonBlockCounts& block(size_t i) {
    MOZ_ASSERT(i < numBlocks_);
    return blocks_[i];
  }

  void setPrevious(IonScriptCounts* previous) { previous_ = previous; }

  IonScriptCounts* previous() const { return previous_; }

  size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    size_t size = 0;
    auto currCounts = this;
    while (currCounts) {
      const IonScriptCounts* currCount = currCounts;
      currCounts = currCount->previous_;
      size += currCount->sizeOfOneIncludingThis(mallocSizeOf);
    }
    return size;
  }

  size_t sizeOfOneIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    size_t size = mallocSizeOf(this) + mallocSizeOf(blocks_);
    for (size_t i = 0; i < numBlocks_; i++) {
      blocks_[i].sizeOfExcludingThis(mallocSizeOf);
    }
    return size;
  }
};

struct VMFunction;

struct AutoFlushICache {
 private:
#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
    defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
  uintptr_t start_;
  uintptr_t stop_;
#  ifdef JS_JITSPEW
  const char* name_;
#  endif
  bool inhibit_;
  AutoFlushICache* prev_;
#endif

 public:
  static void setRange(uintptr_t p, size_t len);
  static void flush(uintptr_t p, size_t len);
  static void setInhibit();
  ~AutoFlushICache();
  explicit AutoFlushICache(const char* nonce, bool inhibit = false);
};

}  // namespace jit

namespace gc {

inline bool IsMarked(JSRuntime* rt, const jit::VMFunction*) {
  // VMFunction are only static objects which are used by WeakMaps as keys.
  // It is considered as a root object which is always marked.
  return true;
}

}  // namespace gc

}  // namespace js

// JS::ubi::Nodes can point to js::jit::JitCode instances; they're js::gc::Cell
// instances with no associated compartment.
namespace JS {
namespace ubi {
template <>
class Concrete<js::jit::JitCode> : TracerConcrete<js::jit::JitCode> {
 protected:
  explicit Concrete(js::jit::JitCode* ptr)
      : TracerConcrete<js::jit::JitCode>(ptr) {}

 public:
  static void construct(void* storage, js::jit::JitCode* ptr) {
    new (storage) Concrete(ptr);
  }

  CoarseType coarseType() const final { return CoarseType::Script; }

  Size size(mozilla::MallocSizeOf mallocSizeOf) const override {
    Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind());
    size += get().bufferSize();
    size += get().headerSize();
    return size;
  }

  const char16_t* typeName() const override { return concreteTypeName; }
  static const char16_t concreteTypeName[];
};

}  // namespace ubi

template <>
struct DeletePolicy<js::jit::IonScript> {
  explicit DeletePolicy(JSRuntime* rt) : rt_(rt) {}
  void operator()(const js::jit::IonScript* script);

 private:
  JSRuntime* rt_;
};

}  // namespace JS

#endif /* jit_IonCode_h */
back to top