| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * 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/PodOperations.h" |
| |
| #include "IonTypes.h" |
| #include "AsmJS.h" |
| #include "gc/Heap.h" |
| |
| // For RecompileInfo |
| #include "jsinfer.h" |
| |
| namespace JSC { |
| class ExecutablePool; |
| } |
| |
| class JSScript; |
| |
| namespace js { |
| namespace jit { |
| |
| // The maximum size of any buffer associated with an assembler or code object. |
| // This is chosen to not overflow a signed integer, leaving room for an extra |
| // bit on offsets. |
| static const uint32_t MAX_BUFFER_SIZE = (1 << 30) - 1; |
| |
| // Maximum number of scripted arg slots. |
| static const uint32_t SNAPSHOT_MAX_NARGS = 127; |
| |
| class MacroAssembler; |
| class CodeOffsetLabel; |
| |
| class IonCode : public gc::Cell |
| { |
| protected: |
| uint8_t *code_; |
| JSC::ExecutablePool *pool_; |
| uint32_t bufferSize_; // Total buffer size. |
| 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. |
| uint32_t preBarrierTableBytes_; // Size of the prebarrier table. |
| JSBool invalidated_; // Whether the code object has been invalidated. |
| // This is necessary to prevent GC tracing. |
| |
| #if JS_BITS_PER_WORD == 32 |
| // Ensure IonCode is gc::Cell aligned. |
| uint32_t padding_; |
| #endif |
| |
| IonCode() |
| : code_(NULL), |
| pool_(NULL) |
| { } |
| IonCode(uint8_t *code, uint32_t bufferSize, JSC::ExecutablePool *pool) |
| : code_(code), |
| pool_(pool), |
| bufferSize_(bufferSize), |
| insnSize_(0), |
| dataSize_(0), |
| jumpRelocTableBytes_(0), |
| dataRelocTableBytes_(0), |
| preBarrierTableBytes_(0), |
| invalidated_(false) |
| { } |
| |
| uint32_t dataOffset() const { |
| return insnSize_; |
| } |
| uint32_t jumpRelocTableOffset() const { |
| return dataOffset() + dataSize_; |
| } |
| uint32_t dataRelocTableOffset() const { |
| return jumpRelocTableOffset() + jumpRelocTableBytes_; |
| } |
| uint32_t preBarrierTableOffset() const { |
| return dataRelocTableOffset() + dataRelocTableBytes_; |
| } |
| |
| public: |
| uint8_t *raw() const { |
| return code_; |
| } |
| size_t instructionsSize() const { |
| return insnSize_; |
| } |
| void trace(JSTracer *trc); |
| void finalize(FreeOp *fop); |
| void setInvalidated() { |
| invalidated_ = true; |
| } |
| |
| void togglePreBarriers(bool enabled); |
| |
| // If this IonCode 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 IonCode *FromExecutable(uint8_t *buffer) { |
| IonCode *code = *(IonCode **)(buffer - sizeof(IonCode *)); |
| JS_ASSERT(code->raw() == buffer); |
| return code; |
| } |
| |
| static size_t offsetOfCode() { |
| return offsetof(IonCode, code_); |
| } |
| |
| uint8_t *jumpRelocTable() { |
| return code_ + jumpRelocTableOffset(); |
| } |
| |
| // Allocates a new IonCode object which will be managed by the GC. If no |
| // object can be allocated, NULL is returned. On failure, |pool| is |
| // automatically released, so the code may be freed. |
| static IonCode *New(JSContext *cx, uint8_t *code, uint32_t bufferSize, JSC::ExecutablePool *pool); |
| |
| public: |
| JS::Zone *zone() const { return tenuredZone(); } |
| static void readBarrier(IonCode *code); |
| static void writeBarrierPre(IonCode *code); |
| static void writeBarrierPost(IonCode *code, void *addr); |
| static inline ThingRootKind rootKind() { return THING_ROOT_ION_CODE; } |
| }; |
| |
| class SnapshotWriter; |
| class SafepointWriter; |
| class SafepointIndex; |
| class OsiIndex; |
| class IonCache; |
| |
| // An IonScript attaches Ion-generated information to a JSScript. |
| struct IonScript |
| { |
| private: |
| // Code pointer containing the actual method. |
| EncapsulatedPtr<IonCode> method_; |
| |
| // Deoptimization table used by this method. |
| EncapsulatedPtr<IonCode> deoptTable_; |
| |
| // Entrypoint for OSR, or NULL. |
| 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 when it is likely that one of our (transitive) call |
| // targets is not compiled. Used in ForkJoin.cpp to decide when |
| // we should add call targets to the worklist. |
| bool hasUncompiledCallTarget_; |
| |
| // Flag set if IonScript was compiled with SPS profiling enabled. |
| bool hasSPSInstrumentation_; |
| |
| // 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 cacheIndex which give a |
| // relative offset in the runtimeData array. |
| uint32_t cacheIndex_; |
| uint32_t cacheEntries_; |
| |
| // 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 STACK_SLOT_SIZE-length slots this function reserves on the |
| // stack. |
| uint32_t frameSlots_; |
| |
| // Frame size is the value that can be added to the StackPointer along |
| // with the frame prefix to get a valid IonJSFrameLayout. |
| 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 snapshotsSize_; |
| |
| // Constant table for constants stored in snapshots. |
| uint32_t constantTable_; |
| uint32_t constantEntries_; |
| |
| // List of compiled/inlined JSScript's. |
| uint32_t scriptList_; |
| uint32_t scriptEntries_; |
| |
| // List of scripts that we call. |
| // |
| // Currently this is only non-NULL for parallel IonScripts. |
| uint32_t callTargetList_; |
| uint32_t callTargetEntries_; |
| |
| // Number of references from invalidation records. |
| size_t refcount_; |
| |
| // Identifier of the compilation which produced this code. |
| types::RecompileInfo recompileInfo_; |
| |
| // Number of times we tried to enter this script via OSR but failed due to |
| // a LOOPENTRY pc other than osrPc_. |
| uint32_t osrPcMismatchCounter_; |
| |
| // If non-null, the list of AsmJSModules |
| // that contain an optimized call directly into this IonScript. |
| Vector<DependentAsmJSModuleExit> *dependentAsmJSModules; |
| |
| 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_]; |
| } |
| EncapsulatedValue *constants() { |
| return (EncapsulatedValue *) &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 *cacheIndex() { |
| return (uint32_t *) &bottomBuffer()[cacheIndex_]; |
| } |
| uint8_t *runtimeData() { |
| return &bottomBuffer()[runtimeData_]; |
| } |
| JSScript **scriptList() const { |
| return (JSScript **) &bottomBuffer()[scriptList_]; |
| } |
| JSScript **callTargetList() { |
| return (JSScript **) &bottomBuffer()[callTargetList_]; |
| } |
| bool addDependentAsmJSModule(JSContext *cx, DependentAsmJSModuleExit exit); |
| void removeDependentAsmJSModule(DependentAsmJSModuleExit exit) { |
| JS_ASSERT(dependentAsmJSModules); |
| for (size_t i = 0; i < dependentAsmJSModules->length(); i++) { |
| if (dependentAsmJSModules->begin()[i].module == exit.module && |
| dependentAsmJSModules->begin()[i].exitIndex == exit.exitIndex) |
| { |
| dependentAsmJSModules->erase(dependentAsmJSModules->begin() + i); |
| break; |
| } |
| } |
| } |
| void detachDependentAsmJSModules(FreeOp *fop); |
| |
| private: |
| void trace(JSTracer *trc); |
| |
| public: |
| // Do not call directly, use IonScript::New. This is public for cx->new_. |
| IonScript(); |
| |
| static IonScript *New(JSContext *cx, uint32_t frameLocals, uint32_t frameSize, |
| size_t snapshotsSize, size_t snapshotEntries, |
| size_t constants, size_t safepointIndexEntries, size_t osiIndexEntries, |
| size_t cacheEntries, size_t runtimeSize, |
| size_t safepointsSize, size_t scriptEntries, |
| size_t callTargetEntries); |
| 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_); |
| } |
| |
| public: |
| IonCode *method() const { |
| return method_; |
| } |
| void setMethod(IonCode *code) { |
| JS_ASSERT(!invalidated()); |
| method_ = code; |
| } |
| void setDeoptTable(IonCode *code) { |
| deoptTable_ = code; |
| } |
| void setOsrPc(jsbytecode *osrPc) { |
| osrPc_ = osrPc; |
| } |
| jsbytecode *osrPc() const { |
| return osrPc_; |
| } |
| void setOsrEntryOffset(uint32_t offset) { |
| JS_ASSERT(!osrEntryOffset_); |
| osrEntryOffset_ = offset; |
| } |
| uint32_t osrEntryOffset() const { |
| return osrEntryOffset_; |
| } |
| void setSkipArgCheckEntryOffset(uint32_t offset) { |
| JS_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) { |
| JS_ASSERT(!invalidateEpilogueOffset_); |
| invalidateEpilogueOffset_ = offset; |
| } |
| uint32_t invalidateEpilogueOffset() const { |
| JS_ASSERT(invalidateEpilogueOffset_); |
| return invalidateEpilogueOffset_; |
| } |
| void setInvalidationEpilogueDataOffset(uint32_t offset) { |
| JS_ASSERT(!invalidateEpilogueDataOffset_); |
| invalidateEpilogueDataOffset_ = offset; |
| } |
| uint32_t invalidateEpilogueDataOffset() const { |
| JS_ASSERT(invalidateEpilogueDataOffset_); |
| return invalidateEpilogueDataOffset_; |
| } |
| void incNumBailouts() { |
| numBailouts_++; |
| } |
| uint32_t numBailouts() const { |
| return numBailouts_; |
| } |
| bool bailoutExpected() const { |
| return numBailouts_ > 0; |
| } |
| void setHasUncompiledCallTarget() { |
| hasUncompiledCallTarget_ = true; |
| } |
| void clearHasUncompiledCallTarget() { |
| hasUncompiledCallTarget_ = false; |
| } |
| bool hasUncompiledCallTarget() const { |
| return hasUncompiledCallTarget_; |
| } |
| void setHasSPSInstrumentation() { |
| hasSPSInstrumentation_ = true; |
| } |
| void clearHasSPSInstrumentation() { |
| hasSPSInstrumentation_ = false; |
| } |
| bool hasSPSInstrumentation() const { |
| return hasSPSInstrumentation_; |
| } |
| const uint8_t *snapshots() const { |
| return reinterpret_cast<const uint8_t *>(this) + snapshots_; |
| } |
| size_t snapshotsSize() const { |
| return snapshotsSize_; |
| } |
| const uint8_t *safepoints() const { |
| return reinterpret_cast<const uint8_t *>(this) + safepointsStart_; |
| } |
| size_t safepointsSize() const { |
| return safepointsSize_; |
| } |
| JSScript *getScript(size_t i) const { |
| JS_ASSERT(i < scriptEntries_); |
| return scriptList()[i]; |
| } |
| size_t scriptEntries() const { |
| return scriptEntries_; |
| } |
| size_t callTargetEntries() const { |
| return callTargetEntries_; |
| } |
| size_t sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) const { |
| return mallocSizeOf(this); |
| } |
| EncapsulatedValue &getConstant(size_t index) { |
| JS_ASSERT(index < numConstants()); |
| return constants()[index]; |
| } |
| size_t numConstants() const { |
| return constantEntries_; |
| } |
| uint32_t frameSlots() const { |
| return frameSlots_; |
| } |
| uint32_t frameSize() const { |
| return frameSize_; |
| } |
| SnapshotOffset bailoutToSnapshot(uint32_t bailoutId) { |
| JS_ASSERT(bailoutId < bailoutEntries_); |
| return bailoutTable()[bailoutId]; |
| } |
| const SafepointIndex *getSafepointIndex(uint32_t disp) const; |
| const SafepointIndex *getSafepointIndex(uint8_t *retAddr) const { |
| JS_ASSERT(containsCodeAddress(retAddr)); |
| return getSafepointIndex(retAddr - method()->raw()); |
| } |
| const OsiIndex *getOsiIndex(uint32_t disp) const; |
| const OsiIndex *getOsiIndex(uint8_t *retAddr) const; |
| inline IonCache &getCache(uint32_t index) { |
| JS_ASSERT(index < cacheEntries_); |
| uint32_t offset = cacheIndex()[index]; |
| JS_ASSERT(offset < runtimeSize_); |
| return *(IonCache *) &runtimeData()[offset]; |
| } |
| size_t numCaches() const { |
| return cacheEntries_; |
| } |
| size_t runtimeSize() const { |
| return runtimeSize_; |
| } |
| void toggleBarriers(bool enabled); |
| void purgeCaches(JS::Zone *zone); |
| void destroyCaches(); |
| void copySnapshots(const SnapshotWriter *writer); |
| void copyBailoutTable(const SnapshotOffset *table); |
| void copyConstants(const Value *vp); |
| void copySafepointIndices(const SafepointIndex *firstSafepointIndex, MacroAssembler &masm); |
| void copyOsiIndices(const OsiIndex *firstOsiIndex, MacroAssembler &masm); |
| void copyRuntimeData(const uint8_t *data); |
| void copyCacheEntries(const uint32_t *caches, MacroAssembler &masm); |
| void copySafepoints(const SafepointWriter *writer); |
| void copyScriptEntries(JSScript **scripts); |
| void copyCallTargetEntries(JSScript **callTargets); |
| |
| bool invalidated() const { |
| return refcount_ != 0; |
| } |
| size_t refcount() const { |
| return refcount_; |
| } |
| void incref() { |
| refcount_++; |
| } |
| void decref(FreeOp *fop) { |
| JS_ASSERT(refcount_); |
| refcount_--; |
| if (!refcount_) |
| Destroy(fop, this); |
| } |
| const types::RecompileInfo& recompileInfo() const { |
| return recompileInfo_; |
| } |
| uint32_t incrOsrPcMismatchCounter() { |
| return ++osrPcMismatchCounter_; |
| } |
| void resetOsrPcMismatchCounter() { |
| osrPcMismatchCounter_ = 0; |
| } |
| |
| 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_; |
| |
| // 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_; |
| |
| // Number of bytes of code generated in this block. Spill code is counted |
| // separately from other, instruction implementing code. |
| uint32_t instructionBytes_; |
| uint32_t spillBytes_; |
| |
| public: |
| |
| bool init(uint32_t id, uint32_t offset, uint32_t numSuccessors) { |
| id_ = id; |
| offset_ = offset; |
| numSuccessors_ = numSuccessors; |
| if (numSuccessors) { |
| successors_ = js_pod_calloc<uint32_t>(numSuccessors); |
| if (!successors_) |
| return false; |
| } |
| return true; |
| } |
| |
| void destroy() { |
| if (successors_) |
| js_free(successors_); |
| if (code_) |
| js_free(code_); |
| } |
| |
| uint32_t id() const { |
| return id_; |
| } |
| |
| uint32_t offset() const { |
| return offset_; |
| } |
| |
| size_t numSuccessors() const { |
| return numSuccessors_; |
| } |
| |
| void setSuccessor(size_t i, uint32_t id) { |
| JS_ASSERT(i < numSuccessors_); |
| successors_[i] = id; |
| } |
| |
| uint32_t successor(size_t i) const { |
| JS_ASSERT(i < numSuccessors_); |
| return successors_[i]; |
| } |
| |
| uint64_t *addressOfHitCount() { |
| return &hitCount_; |
| } |
| |
| uint64_t hitCount() const { |
| return hitCount_; |
| } |
| |
| void setCode(const char *code) { |
| char *ncode = (char *) js_malloc(strlen(code) + 1); |
| if (ncode) { |
| strcpy(ncode, code); |
| code_ = ncode; |
| } |
| } |
| |
| const char *code() const { |
| return code_; |
| } |
| |
| void setInstructionBytes(uint32_t bytes) { |
| instructionBytes_ = bytes; |
| } |
| |
| uint32_t instructionBytes() const { |
| return instructionBytes_; |
| } |
| |
| void setSpillBytes(uint32_t bytes) { |
| spillBytes_ = bytes; |
| } |
| |
| uint32_t spillBytes() const { |
| return spillBytes_; |
| } |
| }; |
| |
| // 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_; |
| |
| // Information about basic blocks in this script. |
| size_t numBlocks_; |
| IonBlockCounts *blocks_; |
| |
| public: |
| |
| IonScriptCounts() { |
| mozilla::PodZero(this); |
| } |
| |
| ~IonScriptCounts() { |
| for (size_t i = 0; i < numBlocks_; i++) |
| blocks_[i].destroy(); |
| js_free(blocks_); |
| if (previous_) |
| js_delete(previous_); |
| } |
| |
| bool init(size_t numBlocks) { |
| numBlocks_ = numBlocks; |
| blocks_ = js_pod_calloc<IonBlockCounts>(numBlocks); |
| return blocks_ != NULL; |
| } |
| |
| size_t numBlocks() const { |
| return numBlocks_; |
| } |
| |
| IonBlockCounts &block(size_t i) { |
| JS_ASSERT(i < numBlocks_); |
| return blocks_[i]; |
| } |
| |
| void setPrevious(IonScriptCounts *previous) { |
| previous_ = previous; |
| } |
| |
| IonScriptCounts *previous() const { |
| return previous_; |
| } |
| }; |
| |
| struct VMFunction; |
| |
| class IonCompartment; |
| class IonRuntime; |
| |
| struct AutoFlushCache |
| { |
| private: |
| uintptr_t start_; |
| uintptr_t stop_; |
| const char *name_; |
| IonRuntime *runtime_; |
| bool used_; |
| |
| public: |
| void update(uintptr_t p, size_t len); |
| static void updateTop(uintptr_t p, size_t len); |
| ~AutoFlushCache(); |
| AutoFlushCache(const char *nonce, IonRuntime *rt = NULL); |
| void flushAnyway(); |
| }; |
| |
| // If you are currently in the middle of modifing Ion-compiled code, which |
| // is going to be flushed at *some* point, but determine that you *must* |
| // call a function *right* *now*, two things can go wrong: |
| // 1) The flusher that you were using is still active, but you are about to |
| // enter jitted code, so it needs to be flushed |
| // 2) the called function can re-enter a compilation/modification path which |
| // will use your AFC, and thus not flush when his compilation is done |
| |
| struct AutoFlushInhibitor { |
| private: |
| IonCompartment *ic_; |
| AutoFlushCache *afc; |
| public: |
| AutoFlushInhibitor(IonCompartment *ic); |
| ~AutoFlushInhibitor(); |
| }; |
| } // namespace jit |
| |
| namespace gc { |
| |
| inline bool |
| IsMarked(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 |
| |
| #endif /* jit_IonCode_h */ |