Bug 1499544 - [Part 1] Foundational implementation of BytecodeIterator r=djvj
authorMatthew Gaudet <mgaudet@mozilla.com>
Tue, 16 Oct 2018 15:43:37 -0400
changeset 491279 908a0972c1b529a90e9bee5055f83c86b06a0bb2
parent 491278 2e9cdd9ff7b6bdc6e7add0ae187166f59d9ad177
child 491280 1417a92298ff87a3b0f328566ed20b06cbcb1d65
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersdjvj
bugs1499544
milestone65.0a1
Bug 1499544 - [Part 1] Foundational implementation of BytecodeIterator r=djvj This is an initial skeleton of an inteface for bytecode. There are 6 major portions included in this patch. - RawBytecode: A typedef to expose jsbytecode* in a controlled manner, to allow easy future audits. - BytecodeLocation: This is an immutable view of a single program operation (a bytecode). There is is some basic functionality in here included for experimentation sake. - BytecodeLocationOffset: An immutable view of a source location offset, (and RawBytecodeLocationOffset, another typedef to ease future auditing) - BytecodeIterator: A forward iterator over BytecodeLocations, using program text order. - AllBytecodeIterable: A wrapper class that allows the use of a range-based for-loop over BytecodeLocations in a Script. - Modifications to JSScript to expose BytecodeLocations and answer some queries about containment. The interface is experimentally tested using JSScript::initScriptCounts as a simple playground for demonstrating the use of the interface. Differential Revision: https://phabricator.services.mozilla.com/D8918
js/src/vm/BytecodeIterator-inl.h
js/src/vm/BytecodeIterator.h
js/src/vm/BytecodeLocation-inl.h
js/src/vm/BytecodeLocation.h
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
new file mode 100644
--- /dev/null
+++ b/js/src/vm/BytecodeIterator-inl.h
@@ -0,0 +1,32 @@
+/* -*- 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 vm_BytecodeIterator_inl_h
+#define vm_BytecodeIterator_inl_h
+
+#include "vm/BytecodeIterator.h"
+
+#include "vm/JSScript.h"
+namespace js {
+
+BytecodeIterator::BytecodeIterator(const JSScript* script)
+  : current_(script, script->code())
+{}
+
+// AllBytecodesIterable
+
+inline BytecodeIterator
+AllBytecodesIterable::begin() {
+    return BytecodeIterator(script_);
+}
+
+inline BytecodeIterator
+AllBytecodesIterable::end() {
+    return BytecodeIterator(BytecodeLocation(script_, script_->codeEnd()));
+}
+
+}
+#endif
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/vm/BytecodeIterator.h
@@ -0,0 +1,74 @@
+/* -*- 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 vm_BytecodeIterator_h
+#define vm_BytecodeIterator_h
+
+#include "vm/BytecodeLocation.h"
+
+namespace js {
+
+class BytecodeIterator
+{
+    BytecodeLocation current_;
+
+  public:
+
+    explicit BytecodeIterator(const JSScript* script);
+
+    explicit BytecodeIterator(BytecodeLocation loc)
+      : current_(loc)
+    {}
+
+    bool operator==(const BytecodeIterator& other) const {
+        return other.current_ == current_;
+    }
+
+    bool operator!=(const BytecodeIterator& other) const {
+        return !(other.current_ == current_);
+    }
+
+    const BytecodeLocation& operator*() const {
+        return current_;
+    }
+
+    const BytecodeLocation* operator->() const {
+        return &current_;
+    }
+
+    // Pre-increment
+    BytecodeIterator& operator++() {
+        current_ = current_.next();
+        return *this;
+    }
+
+    // Post-increment
+    BytecodeIterator operator++(int) {
+        current_ = current_.next();
+        return *this;
+    }
+
+};
+
+// Given a JSScript, allow the construction of a range based for-loop
+// that will visit all script locations in that script.
+class AllBytecodesIterable
+{
+
+    const JSScript* script_;
+  public:
+
+    explicit AllBytecodesIterable(const JSScript* script)
+      : script_(script)
+    {}
+
+    BytecodeIterator begin();
+    BytecodeIterator end();
+};
+
+}
+
+#endif
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/vm/BytecodeLocation-inl.h
@@ -0,0 +1,30 @@
+/* -*- 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 vm_BytecodeLocation_inl_h
+#define vm_BytecodeLocation_inl_h
+
+#include "vm/BytecodeLocation.h"
+
+#include "vm/JSScript.h"
+
+namespace js {
+
+inline bool
+BytecodeLocation::isValid(const JSScript* script) const {
+    // Note: Don't create a new BytecodeLocation during the implementation of this, as it
+    // is used in the constructor, and will recurse forever.
+    return script->contains(*this) ||toRawBytecode() == script->codeEnd();
+}
+
+inline bool
+BytecodeLocation::isInBounds(const JSScript* script) const {
+    return script->contains(*this);
+}
+
+}
+
+#endif
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/vm/BytecodeLocation.h
@@ -0,0 +1,148 @@
+/* -*- 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 vm_BytecodeLocation_h
+#define vm_BytecodeLocation_h
+
+#include "js/TypeDecls.h"
+#include "vm/BytecodeUtil.h"
+
+namespace js {
+
+typedef uint32_t RawBytecodeLocationOffset;
+
+
+class BytecodeLocationOffset
+{
+    RawBytecodeLocationOffset rawOffset_;
+
+  public:
+    explicit BytecodeLocationOffset(RawBytecodeLocationOffset offset)
+      : rawOffset_(offset)
+    {}
+
+    RawBytecodeLocationOffset rawOffset() const {
+        return rawOffset_;
+    }
+
+};
+
+
+typedef jsbytecode* RawBytecode;
+
+// A immutable representation of a program location
+//
+class BytecodeLocation
+{
+    RawBytecode rawBytecode_;
+#ifdef DEBUG
+    const JSScript* debugOnlyScript_;
+#endif
+
+    // Construct a new BytecodeLocation, while borrowing scriptIdentity
+    // from some other BytecodeLocation.
+    BytecodeLocation(const BytecodeLocation& loc, RawBytecode pc)
+      : rawBytecode_(pc)
+#ifdef DEBUG
+      , debugOnlyScript_(loc.debugOnlyScript_)
+#endif
+    {
+        MOZ_ASSERT(isValid());
+    }
+
+  public:
+    BytecodeLocation(const JSScript* script, RawBytecode pc)
+      : rawBytecode_(pc)
+#ifdef DEBUG
+      , debugOnlyScript_(script)
+#endif
+    {
+        MOZ_ASSERT(isValid());
+    }
+
+    RawBytecode toRawBytecode() const {
+        return rawBytecode_;
+    }
+
+    // Return true if this bytecode location is valid for the given script.
+    // This includes the location 1-past the end of the bytecode.
+    bool isValid(const JSScript* script) const;
+
+    // Return true if this bytecode location is within the bounds of the
+    // bytecode for a given script.
+    bool isInBounds(const JSScript* script) const;
+
+    bool operator==(const BytecodeLocation& other) const {
+        MOZ_ASSERT(this->debugOnlyScript_ == other.debugOnlyScript_);
+        return rawBytecode_ == other.rawBytecode_;
+    }
+
+    bool operator!=(const BytecodeLocation& other) const {
+        return !(other == *this);
+    }
+
+    bool operator<(const BytecodeLocation& other) const {
+        MOZ_ASSERT(this->debugOnlyScript_ == other.debugOnlyScript_);
+        return rawBytecode_ < other.rawBytecode_;
+    }
+
+    // It is traditional to represent the rest of the relational operators
+    // using operator<, so we don't need to assert for these.
+    bool operator>(const BytecodeLocation& other) const {
+        return other < *this;
+    }
+
+    bool operator<=(const BytecodeLocation& other) const {
+        return !(other < *this);
+    }
+
+    bool operator>=(const BytecodeLocation& other) const {
+        return !(*this < other);
+    }
+
+    // Return the next bytecode
+    BytecodeLocation next() const {
+        return BytecodeLocation(*this, rawBytecode_ + GetBytecodeLength(rawBytecode_));
+    }
+
+    // Add an offset.
+    BytecodeLocation operator+(const BytecodeLocationOffset& offset) {
+        return BytecodeLocation(*this, rawBytecode_ + offset.rawOffset());
+    }
+
+    // Identity Checks
+    bool is(JSOp op) const {
+        MOZ_ASSERT(isInBounds());
+        return getOp() == op;
+    }
+
+    bool isJumpTarget() const {
+        return BytecodeIsJumpTarget(getOp());
+    }
+
+    // Accessors:
+    JSOp getOp() const {
+        return JSOp(*rawBytecode_);
+    }
+
+#ifdef DEBUG
+    // To ease writing assertions
+    bool isValid() const {
+        return isValid(debugOnlyScript_);
+    }
+
+    bool isInBounds() const {
+        return isInBounds(debugOnlyScript_);
+    }
+#endif
+
+};
+
+
+}
+
+
+#endif
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -42,31 +42,35 @@
 #include "js/Printf.h"
 #include "js/SourceBufferHolder.h"
 #include "js/UniquePtr.h"
 #include "js/Utility.h"
 #include "js/Wrapper.h"
 #include "util/StringBuffer.h"
 #include "util/Text.h"
 #include "vm/ArgumentsObject.h"
+#include "vm/BytecodeIterator.h"
+#include "vm/BytecodeLocation.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Compression.h"
 #include "vm/Debugger.h"
 #include "vm/JSAtom.h"
 #include "vm/JSContext.h"
 #include "vm/JSFunction.h"
 #include "vm/JSObject.h"
 #include "vm/Opcodes.h"
 #include "vm/SelfHosting.h"
 #include "vm/Shape.h"
 #include "vm/SharedImmutableStringsCache.h"
 #include "vm/Xdr.h"
 #include "vtune/VTuneWrapper.h"
 
 #include "gc/Marking-inl.h"
+#include "vm/BytecodeIterator-inl.h"
+#include "vm/BytecodeLocation-inl.h"
 #include "vm/Compartment-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/JSFunction-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/SharedImmutableStringsCache-inl.h"
 #include "vm/Stack-inl.h"
 
@@ -1124,21 +1128,22 @@ JSScript::maybeForwardedScriptSource() c
 
 bool
 JSScript::initScriptCounts(JSContext* cx)
 {
     MOZ_ASSERT(!hasScriptCounts());
 
     // Record all pc which are the first instruction of a basic block.
     mozilla::Vector<jsbytecode*, 16, SystemAllocPolicy> jumpTargets;
-    jsbytecode* mainPc = main();
-    jsbytecode* end = codeEnd();
-    for (jsbytecode* pc = code(); pc != end; pc = GetNextPc(pc)) {
-        if (BytecodeIsJumpTarget(JSOp(*pc)) || pc == mainPc) {
-            if (!jumpTargets.append(pc)) {
+
+    js::BytecodeLocation main = mainLocation();
+    AllBytecodesIterable iterable(this);
+    for (auto& loc : iterable) {
+        if (loc.isJumpTarget() || loc == main) {
+            if (!jumpTargets.append(loc.toRawBytecode())) {
                 ReportOutOfMemory(cx);
                 return false;
             }
         }
     }
 
     // Initialize all PCCounts counters to 0.
     ScriptCounts::PCCountsVector base;
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -28,16 +28,18 @@
 #include "frontend/NameAnalysisTypes.h"
 #include "gc/Barrier.h"
 #include "gc/Rooting.h"
 #include "jit/IonCode.h"
 #include "js/CompileOptions.h"
 #include "js/UbiNode.h"
 #include "js/UniquePtr.h"
 #include "js/Utility.h"
+#include "vm/BytecodeIterator.h"
+#include "vm/BytecodeLocation.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/JSAtom.h"
 #include "vm/NativeObject.h"
 #include "vm/Scope.h"
 #include "vm/Shape.h"
 #include "vm/SharedImmutableStringsCache.h"
 #include "vm/Time.h"
 
@@ -1743,16 +1745,25 @@ class JSScript : public js::gc::TenuredC
 
     // Script bytecode is immutable after creation.
     jsbytecode* code() const {
         if (!scriptData_) {
             return nullptr;
         }
         return scriptData_->code();
     }
+
+    js::AllBytecodesIterable allLocations() {
+        return js::AllBytecodesIterable(this);
+    }
+
+    js::BytecodeLocation location() {
+        return js::BytecodeLocation(this, code());
+    }
+
     bool isUncompleted() const {
         // code() becomes non-null only if this script is complete.
         // See the comment in JSScript::fullyInitFromEmitter.
         return !code();
     }
 
     size_t length() const {
         MOZ_ASSERT(scriptData_);
@@ -1766,16 +1777,20 @@ class JSScript : public js::gc::TenuredC
         MOZ_ASSERT(*pc == JSOP_RETRVAL);
         return pc;
     }
 
     bool containsPC(const jsbytecode* pc) const {
         return pc >= code() && pc < codeEnd();
     }
 
+    bool contains(const js::BytecodeLocation& loc) const {
+        return containsPC(loc.toRawBytecode());
+    }
+
     size_t pcToOffset(const jsbytecode* pc) const {
         MOZ_ASSERT(containsPC(pc));
         return size_t(pc - code());
     }
 
     jsbytecode* offsetToPC(size_t offset) const {
         MOZ_ASSERT(offset < length());
         return code() + offset;
@@ -2354,16 +2369,24 @@ class JSScript : public js::gc::TenuredC
     void destroyScriptCounts();
     void destroyScriptName();
     void clearHasScriptCounts();
 
     jsbytecode* main() const {
         return code() + mainOffset();
     }
 
+    js::BytecodeLocation mainLocation() const {
+        return js::BytecodeLocation(this, main());
+    }
+
+    js::BytecodeLocation endLocation() const {
+        return js::BytecodeLocation(this, codeEnd());
+    }
+
     /*
      * computedSizeOfData() is the in-use size of all the data sections.
      * sizeOfData() is the size of the block allocated to hold all the data
      * sections (which can be larger than the in-use size).
      */
     size_t computedSizeOfData() const;
     size_t sizeOfData(mozilla::MallocSizeOf mallocSizeOf) const;
     size_t sizeOfTypeScript(mozilla::MallocSizeOf mallocSizeOf) const;