Bug 1654149 - Add dumpStencil shell-builtin. r=tcampbell
authorTooru Fujisawa <arai_a@mac.com>
Thu, 06 Aug 2020 08:32:23 +0000
changeset 543568 af63ceb254223ee0868fb3ee05f17c50fd7938d4
parent 543567 30af0e8c7956f3ade46a65966a9c7cc3dd9c098d
child 543569 664faa6f72adbcfaecc7d51d6906a755c9775361
push id123539
push userarai_a@mac.com
push dateThu, 06 Aug 2020 08:48:12 +0000
treeherderautoland@af63ceb25422 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1654149
milestone81.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1654149 - Add dumpStencil shell-builtin. r=tcampbell Add dump method to stencil-related structs, that dumps the struct as JSON, and `dumpStencil()` shell-builtin as a consumer. Some structs have dumpFields method, given that JSONPrinter only provide beginObjectProperty/endObject to print a property with an object value. There's some design decision about when to use string-representation and when to use object. Currently simple single-field struct like ScopeIndex uses string-representation like "ScopeIndex(0)", and ScriptAtom also uses string-representation. There's an exception in ScriptAtom case in DumpScriptThing, that we need to differentiate string and string-representation of other structs, and for simplicity, ScriptAtom case uses object with "type" property, and other single-field structs use string-representation. The current dumpStencil does not dump CompilationInfo.asmJS (FIXME) and ScriptStencil.immutableScriptData. Most of ScriptStencil.immutableScriptData field can be dumped by `dis()` function. Differential Revision: https://phabricator.services.mozilla.com/D86042
js/src/frontend/CompilationInfo.h
js/src/frontend/Frontend2.cpp
js/src/frontend/Frontend2.h
js/src/frontend/ObjLiteral.cpp
js/src/frontend/ObjLiteral.h
js/src/frontend/Stencil.cpp
js/src/frontend/Stencil.h
js/src/shell/js.cpp
js/src/vm/JSONPrinter.cpp
js/src/vm/JSONPrinter.h
js/src/vm/Scope.h
js/src/vm/StringType.cpp
js/src/vm/StringType.h
--- a/js/src/frontend/CompilationInfo.h
+++ b/js/src/frontend/CompilationInfo.h
@@ -25,16 +25,19 @@
 #include "js/Vector.h"
 #include "js/WasmModule.h"
 #include "vm/JSContext.h"
 #include "vm/JSFunction.h"  // JSFunction
 #include "vm/JSScript.h"    // SourceExtent
 #include "vm/Realm.h"
 
 namespace js {
+
+class JSONPrinter;
+
 namespace frontend {
 
 // ScopeContext hold information derivied from the scope and environment chains
 // to try to avoid the parser needing to traverse VM structures directly.
 struct ScopeContext {
   // Whether the enclosing scope allows certain syntax. Eval and arrow scripts
   // inherit this from their enclosing scipt so we track it here.
   bool allowNewTarget = false;
@@ -289,16 +292,21 @@ struct MOZ_RAII CompilationInfo : public
   CompilationInfo(const CompilationInfo&) = delete;
   CompilationInfo(CompilationInfo&&) = delete;
   CompilationInfo& operator=(const CompilationInfo&) = delete;
   CompilationInfo& operator=(CompilationInfo&&) = delete;
 
   ScriptStencilIterable functionScriptStencils() {
     return ScriptStencilIterable(this);
   }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+  void dumpStencil();
+  void dumpStencil(js::JSONPrinter& json);
+#endif
 };
 
 inline void ScriptStencilIterable::Iterator::next() {
   if (state_ == State::TopLevel) {
     state_ = State::Functions;
   } else {
     MOZ_ASSERT(index_ < compilationInfo_->funcData.length());
     index_++;
--- a/js/src/frontend/Frontend2.cpp
+++ b/js/src/frontend/Frontend2.cpp
@@ -497,19 +497,19 @@ void ReportSmooshCompileError(JSContext*
   va_list args;
   va_start(args, errorNumber);
   ReportCompileErrorUTF8(cx, std::move(metadata), /* notes = */ nullptr,
                          errorNumber, &args);
   va_end(args);
 }
 
 /* static */
-JSScript* Smoosh::compileGlobalScript(CompilationInfo& compilationInfo,
-                                      JS::SourceText<Utf8Unit>& srcBuf,
-                                      bool* unimplemented) {
+bool Smoosh::compileGlobalScriptToStencil(CompilationInfo& compilationInfo,
+                                          JS::SourceText<Utf8Unit>& srcBuf,
+                                          bool* unimplemented) {
   // FIXME: check info members and return with *unimplemented = true
   //        if any field doesn't match to smoosh_run.
 
   auto bytes = reinterpret_cast<const uint8_t*>(srcBuf.get());
   size_t length = srcBuf.length();
 
   JSContext* cx = compilationInfo.cx;
 
@@ -525,62 +525,74 @@ JSScript* Smoosh::compileGlobalScript(Co
     ErrorMetadata metadata;
     metadata.filename = "<unknown>";
     metadata.lineNumber = 1;
     metadata.columnNumber = 0;
     metadata.isMuted = false;
     ReportSmooshCompileError(cx, std::move(metadata),
                              JSMSG_SMOOSH_COMPILE_ERROR,
                              reinterpret_cast<const char*>(result.error.data));
-    return nullptr;
+    return false;
   }
 
   if (result.unimplemented) {
     *unimplemented = true;
-    return nullptr;
+    return false;
   }
 
   *unimplemented = false;
 
   JS::RootedVector<JSAtom*> allAtoms(cx);
   if (!ConvertAtoms(cx, result, compilationInfo, &allAtoms)) {
-    return nullptr;
+    return false;
   }
 
   if (!ConvertScopeStencil(cx, result, allAtoms, compilationInfo)) {
-    return nullptr;
+    return false;
   }
 
   if (!ConvertRegExpData(cx, result, compilationInfo)) {
-    return nullptr;
+    return false;
   }
 
   if (!ConvertScriptStencil(cx, result, result.top_level_script, allAtoms,
                             compilationInfo, &compilationInfo.topLevel)) {
-    return nullptr;
+    return false;
   }
 
   if (!compilationInfo.funcData.reserve(result.functions.len)) {
-    return nullptr;
+    return false;
   }
 
   for (size_t i = 0; i < result.functions.len; i++) {
     compilationInfo.funcData.infallibleEmplaceBack(cx);
 
     if (!ConvertScriptStencil(cx, result, result.functions.data[i], allAtoms,
                               compilationInfo, compilationInfo.funcData[i])) {
-      return nullptr;
+      return false;
     }
   }
 
+  return true;
+}
+
+/* static */
+JSScript* Smoosh::compileGlobalScript(CompilationInfo& compilationInfo,
+                                      JS::SourceText<Utf8Unit>& srcBuf,
+                                      bool* unimplemented) {
+  if (!compileGlobalScriptToStencil(compilationInfo, srcBuf, unimplemented)) {
+    return nullptr;
+  }
+
   if (!compilationInfo.instantiateStencils()) {
     return nullptr;
   }
 
 #if defined(DEBUG) || defined(JS_JITSPEW)
+  JSContext* cx = compilationInfo.cx;
   Sprinter sprinter(cx);
   if (!sprinter.init()) {
     return nullptr;
   }
   if (!Disassemble(cx, compilationInfo.script, true, &sprinter,
                    DisassembleSkeptically::Yes)) {
     return nullptr;
   }
--- a/js/src/frontend/Frontend2.h
+++ b/js/src/frontend/Frontend2.h
@@ -30,16 +30,20 @@ class GlobalScriptInfo;
 
 // This is declarated as a class mostly to solve dependency around `friend`
 // declarations in the simple way.
 class Smoosh {
  public:
   static JSScript* compileGlobalScript(
       CompilationInfo& compilationInfo,
       JS::SourceText<mozilla::Utf8Unit>& srcBuf, bool* unimplemented);
+
+  static bool compileGlobalScriptToStencil(
+      CompilationInfo& compilationInfo,
+      JS::SourceText<mozilla::Utf8Unit>& srcBuf, bool* unimplemented);
 };
 
 // Initialize SmooshMonkey globals, such as the logging system.
 void InitSmoosh();
 
 // Use the SmooshMonkey frontend to parse and free the generated AST. Returns
 // true if no error were detected while parsing.
 MOZ_MUST_USE bool SmooshParseScript(JSContext* cx, const uint8_t* bytes,
--- a/js/src/frontend/ObjLiteral.cpp
+++ b/js/src/frontend/ObjLiteral.cpp
@@ -5,17 +5,19 @@
  * 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/. */
 
 #include "frontend/ObjLiteral.h"
 #include "mozilla/DebugOnly.h"
 #include "js/RootingAPI.h"
 #include "vm/JSAtom.h"
 #include "vm/JSObject.h"
+#include "vm/JSONPrinter.h"  // js::JSONPrinter
 #include "vm/ObjectGroup.h"
+#include "vm/Printer.h"  // js::Fprinter
 
 #include "gc/ObjectKind-inl.h"
 #include "vm/JSAtom-inl.h"
 #include "vm/JSObject-inl.h"
 
 namespace js {
 
 static JS::Value InterpretObjLiteralValue(const ObjLiteralAtomVector& atoms,
@@ -117,9 +119,141 @@ static JSObject* InterpretObjLiteralArra
 JSObject* InterpretObjLiteral(JSContext* cx, const ObjLiteralAtomVector& atoms,
                               const mozilla::Span<const uint8_t> literalInsns,
                               ObjLiteralFlags flags) {
   return flags.contains(ObjLiteralFlag::Array)
              ? InterpretObjLiteralArray(cx, atoms, literalInsns, flags)
              : InterpretObjLiteralObj(cx, atoms, literalInsns, flags);
 }
 
+#if defined(DEBUG) || defined(JS_JITSPEW)
+
+static void DumpObjLiteralFlagsItems(js::JSONPrinter& json,
+                                     ObjLiteralFlags flags) {
+  if (flags.contains(ObjLiteralFlag::Array)) {
+    json.value("Array");
+    flags -= ObjLiteralFlag::Array;
+  }
+  if (flags.contains(ObjLiteralFlag::SpecificGroup)) {
+    json.value("SpecificGroup");
+    flags -= ObjLiteralFlag::SpecificGroup;
+  }
+  if (flags.contains(ObjLiteralFlag::Singleton)) {
+    json.value("Singleton");
+    flags -= ObjLiteralFlag::Singleton;
+  }
+  if (flags.contains(ObjLiteralFlag::ArrayCOW)) {
+    json.value("ArrayCOW");
+    flags -= ObjLiteralFlag::ArrayCOW;
+  }
+  if (flags.contains(ObjLiteralFlag::NoValues)) {
+    json.value("NoValues");
+    flags -= ObjLiteralFlag::NoValues;
+  }
+  if (flags.contains(ObjLiteralFlag::IsInnerSingleton)) {
+    json.value("IsInnerSingleton");
+    flags -= ObjLiteralFlag::IsInnerSingleton;
+  }
+
+  if (!flags.isEmpty()) {
+    json.value("Unknown(%x)", flags.serialize());
+  }
+}
+
+void ObjLiteralWriter::dump() {
+  js::Fprinter out(stderr);
+  js::JSONPrinter json(out);
+  dump(json);
+}
+
+void ObjLiteralWriter::dump(js::JSONPrinter& json) {
+  json.beginObject();
+  dumpFields(json);
+  json.endObject();
+}
+
+void ObjLiteralWriter::dumpFields(js::JSONPrinter& json) {
+  json.beginListProperty("flags");
+  DumpObjLiteralFlagsItems(json, flags_);
+  json.endList();
+
+  json.beginListProperty("code");
+  ObjLiteralReader reader(getCode());
+  ObjLiteralInsn insn;
+  while (reader.readInsn(&insn)) {
+    json.beginObject();
+
+    if (insn.getKey().isNone()) {
+      json.nullProperty("key");
+    } else if (insn.getKey().isAtomIndex()) {
+      uint32_t index = insn.getKey().getAtomIndex();
+      json.formatProperty("key", "ConstAtom(%u)", index);
+    } else if (insn.getKey().isArrayIndex()) {
+      uint32_t index = insn.getKey().getArrayIndex();
+      json.formatProperty("key", "ArrayIndex(%u)", index);
+    }
+
+    switch (insn.getOp()) {
+      case ObjLiteralOpcode::ConstValue: {
+        const Value& v = insn.getConstValue();
+        json.formatProperty("op", "ConstValue(%f)", v.toNumber());
+        break;
+      }
+      case ObjLiteralOpcode::ConstAtom: {
+        uint32_t index = insn.getAtomIndex();
+        json.formatProperty("op", "ConstAtom(%u)", index);
+        break;
+      }
+      case ObjLiteralOpcode::Null:
+        json.property("op", "Null");
+        break;
+      case ObjLiteralOpcode::Undefined:
+        json.property("op", "Undefined");
+        break;
+      case ObjLiteralOpcode::True:
+        json.property("op", "True");
+        break;
+      case ObjLiteralOpcode::False:
+        json.property("op", "False");
+        break;
+      default:
+        json.formatProperty("op", "Invalid(%x)", uint8_t(insn.getOp()));
+        break;
+    }
+
+    json.endObject();
+  }
+  json.endList();
+}
+
+void ObjLiteralCreationData::dump() {
+  js::Fprinter out(stderr);
+  js::JSONPrinter json(out);
+  dump(json);
+}
+
+void ObjLiteralCreationData::dump(js::JSONPrinter& json) {
+  json.beginObject();
+  dumpFields(json);
+  json.endObject();
+}
+
+void ObjLiteralCreationData::dumpFields(js::JSONPrinter& json) {
+  json.beginObjectProperty("writer");
+  writer_.dumpFields(json);
+  json.endObject();
+
+  json.beginListProperty("atoms");
+  for (auto& atom : atoms_) {
+    if (atom) {
+      GenericPrinter& out = json.beginString();
+      atom->dumpCharsNoQuote(out);
+      json.endString();
+    } else {
+      json.nullValue();
+    }
+  }
+  json.endList();
+}
+
+#endif  // defined(DEBUG) || defined(JS_JITSPEW)
+
 }  // namespace js
--- a/js/src/frontend/ObjLiteral.h
+++ b/js/src/frontend/ObjLiteral.h
@@ -134,16 +134,18 @@
  * we pass down an `isInner` boolean while recursively traversing the
  * parse-node tree and generating bytecode. If we encounter an object literal
  * that is in singleton (run-once) context but also `isInner`, then we set
  * special flags to ensure its shape is looked up based on properties instead.
  */
 
 namespace js {
 
+class JSONPrinter;
+
 // Object-literal instruction opcodes. An object literal is constructed by a
 // straight-line sequence of these ops, each adding one property to the
 // object.
 enum class ObjLiteralOpcode : uint8_t {
   INVALID = 0,
 
   ConstValue = 1,  // numeric types only.
   ConstAtom = 2,
@@ -356,16 +358,22 @@ struct ObjLiteralWriter : private ObjLit
   MOZ_MUST_USE bool propWithFalseValue() {
     return pushOpAndName(ObjLiteralOpcode::False, nextKey_);
   }
 
   static bool arrayIndexInRange(int32_t i) {
     return i >= 0 && static_cast<uint32_t>(i) <= ATOM_INDEX_MASK;
   }
 
+#if defined(DEBUG) || defined(JS_JITSPEW)
+  void dump();
+  void dump(JSONPrinter& json);
+  void dumpFields(JSONPrinter& json);
+#endif
+
  private:
   ObjLiteralFlags flags_;
   ObjLiteralKey nextKey_;
 };
 
 struct ObjLiteralReaderBase {
  private:
   mozilla::Span<const uint8_t> data_;
@@ -559,12 +567,18 @@ class ObjLiteralCreationData {
   ObjLiteralWriter& writer() { return writer_; }
 
   bool addAtom(JSAtom* atom, uint32_t* index) {
     *index = atoms_.length();
     return atoms_.append(atom);
   }
 
   JSObject* create(JSContext* cx) const;
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+  void dump();
+  void dump(JSONPrinter& json);
+  void dumpFields(JSONPrinter& json);
+#endif
 };
 
 }  // namespace js
 #endif  // frontend_ObjLiteral_h
--- a/js/src/frontend/Stencil.cpp
+++ b/js/src/frontend/Stencil.cpp
@@ -18,22 +18,25 @@
 #include "js/TracingAPI.h"  // TraceEdge
 #include "js/Value.h"       // ObjectValue
 #include "js/WasmModule.h"  // JS::WasmModule
 #include "vm/EnvironmentObject.h"
 #include "vm/GeneratorAndAsyncKind.h"  // GeneratorKind, FunctionAsyncKind
 #include "vm/JSContext.h"              // JSContext
 #include "vm/JSFunction.h"  // JSFunction, GetFunctionPrototype, NewFunctionWithProto
 #include "vm/JSObject.h"     // JSObject
+#include "vm/JSONPrinter.h"  // js::JSONPrinter
 #include "vm/JSScript.h"     // BaseScript, JSScript
 #include "vm/ObjectGroup.h"  // TenuredObject
-#include "vm/Scope.h"  // Scope, CreateEnvironmentShape, EmptyEnvironmentShape
+#include "vm/Printer.h"      // js::Fprinter
+#include "vm/Scope.h"  // Scope, CreateEnvironmentShape, EmptyEnvironmentShape, ScopeKindString
 #include "vm/StencilEnums.h"  // ImmutableScriptFlagsEnum
 #include "vm/StringType.h"    // JSAtom, js::CopyChars
 #include "wasm/AsmJS.h"       // InstantiateAsmJS
+#include "wasm/WasmModule.h"  // wasm::Module
 
 using namespace js;
 using namespace js::frontend;
 
 bool frontend::RegExpCreationData::init(JSContext* cx, JSAtom* pattern,
                                         JS::RegExpFlags flags) {
   length_ = pattern->length();
   buf_ = cx->make_pod_array<char16_t>(length_);
@@ -648,8 +651,671 @@ bool CompilationInfo::instantiateStencil
   UpdateEmittedInnerFunctions(*this);
 
   if (lazy == nullptr) {
     LinkEnclosingLazyScript(*this);
   }
 
   return true;
 }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+
+void RegExpCreationData::dump() {
+  js::Fprinter out(stderr);
+  js::JSONPrinter json(out);
+  dump(json);
+}
+
+void RegExpCreationData::dump(js::JSONPrinter& json) {
+  GenericPrinter& out = json.beginString();
+
+  out.put("/");
+  for (size_t i = 0; i < length_; i++) {
+    char16_t c = buf_[i];
+    if (c == '\n') {
+      out.put("\\n");
+    } else if (c == '\t') {
+      out.put("\\t");
+    } else if (c >= 32 && c < 127) {
+      out.putChar(char(buf_[i]));
+    } else if (c <= 255) {
+      out.printf("\\x%02x", unsigned(c));
+    } else {
+      out.printf("\\u%04x", unsigned(c));
+    }
+  }
+  out.put("/");
+
+  if (flags_.global()) {
+    out.put("g");
+  }
+  if (flags_.ignoreCase()) {
+    out.put("i");
+  }
+  if (flags_.multiline()) {
+    out.put("m");
+  }
+  if (flags_.dotAll()) {
+    out.put("s");
+  }
+  if (flags_.unicode()) {
+    out.put("u");
+  }
+  if (flags_.sticky()) {
+    out.put("y");
+  }
+
+  json.endString();
+}
+
+void BigIntCreationData::dump() {
+  js::Fprinter out(stderr);
+  js::JSONPrinter json(out);
+  dump(json);
+}
+
+void BigIntCreationData::dump(js::JSONPrinter& json) {
+  GenericPrinter& out = json.beginString();
+
+  for (size_t i = 0; i < length_; i++) {
+    out.putChar(char(buf_[i]));
+  }
+
+  json.endString();
+}
+
+void ScopeStencil::dump() {
+  js::Fprinter out(stderr);
+  js::JSONPrinter json(out);
+  dump(json);
+}
+
+void ScopeStencil::dump(js::JSONPrinter& json) {
+  json.beginObject();
+  dumpFields(json);
+  json.endObject();
+}
+
+void ScopeStencil::dumpFields(js::JSONPrinter& json) {
+  if (enclosing_) {
+    json.formatProperty("enclosing", "Some(ScopeIndex(%zu))",
+                        size_t(*enclosing_));
+  } else {
+    json.property("enclosing", "Nothing");
+  }
+
+  json.property("kind", ScopeKindString(kind_));
+
+  json.property("firstFrameSlot", firstFrameSlot_);
+
+  if (numEnvironmentSlots_) {
+    json.formatProperty("numEnvironmentSlots", "Some(%zu)",
+                        size_t(*numEnvironmentSlots_));
+  } else {
+    json.property("numEnvironmentSlots", "Nothing");
+  }
+
+  if (kind_ == ScopeKind::Function) {
+    if (functionIndex_.isSome()) {
+      json.formatProperty("functionIndex", "FunctionIndex(%zu)",
+                          size_t(*functionIndex_));
+    } else {
+      json.property("functionIndex", "Nothing");
+    }
+
+    json.boolProperty("isArrow", isArrow_);
+  }
+
+  json.beginObjectProperty("data");
+
+  AbstractTrailingNamesArray<JSAtom>* trailingNames = nullptr;
+  uint32_t length = 0;
+
+  switch (kind_) {
+    case ScopeKind::Function: {
+      auto* data = static_cast<FunctionScope::Data*>(data_.get());
+      json.property("nextFrameSlot", data->nextFrameSlot);
+      json.property("hasParameterExprs", data->hasParameterExprs);
+      json.property("nonPositionalFormalStart", data->nonPositionalFormalStart);
+      json.property("varStart", data->varStart);
+
+      trailingNames = &data->trailingNames;
+      length = data->length;
+      break;
+    }
+
+    case ScopeKind::FunctionBodyVar: {
+      auto* data = static_cast<VarScope::Data*>(data_.get());
+      json.property("nextFrameSlot", data->nextFrameSlot);
+
+      trailingNames = &data->trailingNames;
+      length = data->length;
+      break;
+    }
+
+    case ScopeKind::Lexical:
+    case ScopeKind::SimpleCatch:
+    case ScopeKind::Catch:
+    case ScopeKind::NamedLambda:
+    case ScopeKind::StrictNamedLambda:
+    case ScopeKind::FunctionLexical:
+    case ScopeKind::ClassBody: {
+      auto* data = static_cast<LexicalScope::Data*>(data_.get());
+      json.property("nextFrameSlot", data->nextFrameSlot);
+      json.property("constStart", data->constStart);
+
+      trailingNames = &data->trailingNames;
+      length = data->length;
+      break;
+    }
+
+    case ScopeKind::With: {
+      break;
+    }
+
+    case ScopeKind::Eval:
+    case ScopeKind::StrictEval: {
+      auto* data = static_cast<EvalScope::Data*>(data_.get());
+      json.property("nextFrameSlot", data->nextFrameSlot);
+
+      trailingNames = &data->trailingNames;
+      length = data->length;
+      break;
+    }
+
+    case ScopeKind::Global:
+    case ScopeKind::NonSyntactic: {
+      auto* data = static_cast<GlobalScope::Data*>(data_.get());
+      json.property("letStart", data->letStart);
+      json.property("constStart", data->constStart);
+
+      trailingNames = &data->trailingNames;
+      length = data->length;
+      break;
+    }
+
+    case ScopeKind::Module: {
+      auto* data = static_cast<ModuleScope::Data*>(data_.get());
+      json.property("nextFrameSlot", data->nextFrameSlot);
+      json.property("varStart", data->varStart);
+      json.property("letStart", data->letStart);
+      json.property("constStart", data->constStart);
+
+      trailingNames = &data->trailingNames;
+      length = data->length;
+      break;
+    }
+
+    case ScopeKind::WasmInstance: {
+      auto* data = static_cast<WasmInstanceScope::Data*>(data_.get());
+      json.property("nextFrameSlot", data->nextFrameSlot);
+      json.property("globalsStart", data->globalsStart);
+
+      trailingNames = &data->trailingNames;
+      length = data->length;
+      break;
+    }
+
+    case ScopeKind::WasmFunction: {
+      auto* data = static_cast<WasmFunctionScope::Data*>(data_.get());
+      json.property("nextFrameSlot", data->nextFrameSlot);
+
+      trailingNames = &data->trailingNames;
+      length = data->length;
+      break;
+    }
+
+    default: {
+      MOZ_CRASH("Unexpected ScopeKind");
+      break;
+    }
+  }
+
+  if (trailingNames) {
+    json.beginListProperty("trailingNames");
+    for (size_t i = 0; i < length; i++) {
+      auto& name = (*trailingNames)[i];
+      json.beginObject();
+
+      json.boolProperty("closedOver", name.closedOver());
+
+      json.boolProperty("isTopLevelFunction", name.isTopLevelFunction());
+
+      GenericPrinter& out = json.beginStringProperty("name");
+      name.name()->dumpCharsNoQuote(out);
+      json.endStringProperty();
+
+      json.endObject();
+    }
+    json.endList();
+  }
+
+  json.endObject();
+}
+
+static void DumpModuleEntryVectorItems(
+    js::JSONPrinter& json, const StencilModuleMetadata::EntryVector& entries) {
+  for (const auto& entry : entries) {
+    json.beginObject();
+    if (entry.specifier) {
+      GenericPrinter& out = json.beginStringProperty("specifier");
+      entry.specifier->dumpCharsNoQuote(out);
+      json.endStringProperty();
+    }
+    if (entry.localName) {
+      GenericPrinter& out = json.beginStringProperty("localName");
+      entry.localName->dumpCharsNoQuote(out);
+      json.endStringProperty();
+    }
+    if (entry.importName) {
+      GenericPrinter& out = json.beginStringProperty("importName");
+      entry.importName->dumpCharsNoQuote(out);
+      json.endStringProperty();
+    }
+    if (entry.exportName) {
+      GenericPrinter& out = json.beginStringProperty("exportName");
+      entry.exportName->dumpCharsNoQuote(out);
+      json.endStringProperty();
+    }
+    json.endObject();
+  }
+}
+
+void StencilModuleMetadata::dump() {
+  js::Fprinter out(stderr);
+  js::JSONPrinter json(out);
+  dump(json);
+}
+
+void StencilModuleMetadata::dump(js::JSONPrinter& json) {
+  json.beginObject();
+  dumpFields(json);
+  json.endObject();
+}
+
+void StencilModuleMetadata::dumpFields(js::JSONPrinter& json) {
+  json.beginListProperty("requestedModules");
+  DumpModuleEntryVectorItems(json, requestedModules);
+  json.endList();
+
+  json.beginListProperty("importEntries");
+  DumpModuleEntryVectorItems(json, importEntries);
+  json.endList();
+
+  json.beginListProperty("localExportEntries");
+  DumpModuleEntryVectorItems(json, localExportEntries);
+  json.endList();
+
+  json.beginListProperty("indirectExportEntries");
+  DumpModuleEntryVectorItems(json, indirectExportEntries);
+  json.endList();
+
+  json.beginListProperty("starExportEntries");
+  DumpModuleEntryVectorItems(json, starExportEntries);
+  json.endList();
+
+  json.beginListProperty("functionDecls");
+  for (auto& index : functionDecls) {
+    json.value("FunctionIndex(%zu)", size_t(index));
+  }
+  json.endList();
+}
+
+static void DumpImmutableScriptFlags(js::JSONPrinter& json,
+                                     ImmutableScriptFlags immutableFlags) {
+  for (uint32_t i = 1; i; i = i << 1) {
+    if (uint32_t(immutableFlags) & i) {
+      switch (ImmutableScriptFlagsEnum(i)) {
+        case ImmutableScriptFlagsEnum::IsForEval:
+          json.value("IsForEval");
+          break;
+        case ImmutableScriptFlagsEnum::IsModule:
+          json.value("IsModule");
+          break;
+        case ImmutableScriptFlagsEnum::IsFunction:
+          json.value("IsFunction");
+          break;
+        case ImmutableScriptFlagsEnum::SelfHosted:
+          json.value("SelfHosted");
+          break;
+        case ImmutableScriptFlagsEnum::ForceStrict:
+          json.value("ForceStrict");
+          break;
+        case ImmutableScriptFlagsEnum::HasNonSyntacticScope:
+          json.value("HasNonSyntacticScope");
+          break;
+        case ImmutableScriptFlagsEnum::NoScriptRval:
+          json.value("NoScriptRval");
+          break;
+        case ImmutableScriptFlagsEnum::TreatAsRunOnce:
+          json.value("TreatAsRunOnce");
+          break;
+        case ImmutableScriptFlagsEnum::Strict:
+          json.value("Strict");
+          break;
+        case ImmutableScriptFlagsEnum::HasModuleGoal:
+          json.value("HasModuleGoal");
+          break;
+        case ImmutableScriptFlagsEnum::HasInnerFunctions:
+          json.value("HasInnerFunctions");
+          break;
+        case ImmutableScriptFlagsEnum::HasDirectEval:
+          json.value("HasDirectEval");
+          break;
+        case ImmutableScriptFlagsEnum::BindingsAccessedDynamically:
+          json.value("BindingsAccessedDynamically");
+          break;
+        case ImmutableScriptFlagsEnum::HasCallSiteObj:
+          json.value("HasCallSiteObj");
+          break;
+        case ImmutableScriptFlagsEnum::IsAsync:
+          json.value("IsAsync");
+          break;
+        case ImmutableScriptFlagsEnum::IsGenerator:
+          json.value("IsGenerator");
+          break;
+        case ImmutableScriptFlagsEnum::FunHasExtensibleScope:
+          json.value("FunHasExtensibleScope");
+          break;
+        case ImmutableScriptFlagsEnum::FunctionHasThisBinding:
+          json.value("FunctionHasThisBinding");
+          break;
+        case ImmutableScriptFlagsEnum::NeedsHomeObject:
+          json.value("NeedsHomeObject");
+          break;
+        case ImmutableScriptFlagsEnum::IsDerivedClassConstructor:
+          json.value("IsDerivedClassConstructor");
+          break;
+        case ImmutableScriptFlagsEnum::IsFieldInitializer:
+          json.value("IsFieldInitializer");
+          break;
+        case ImmutableScriptFlagsEnum::HasRest:
+          json.value("HasRest");
+          break;
+        case ImmutableScriptFlagsEnum::NeedsFunctionEnvironmentObjects:
+          json.value("NeedsFunctionEnvironmentObjects");
+          break;
+        case ImmutableScriptFlagsEnum::FunctionHasExtraBodyVarScope:
+          json.value("FunctionHasExtraBodyVarScope");
+          break;
+        case ImmutableScriptFlagsEnum::ShouldDeclareArguments:
+          json.value("ShouldDeclareArguments");
+          break;
+        case ImmutableScriptFlagsEnum::ArgumentsHasVarBinding:
+          json.value("ArgumentsHasVarBinding");
+          break;
+        case ImmutableScriptFlagsEnum::AlwaysNeedsArgsObj:
+          json.value("AlwaysNeedsArgsObj");
+          break;
+        case ImmutableScriptFlagsEnum::HasMappedArgsObj:
+          json.value("HasMappedArgsObj");
+          break;
+        case ImmutableScriptFlagsEnum::IsLikelyConstructorWrapper:
+          json.value("IsLikelyConstructorWrapper");
+          break;
+        default:
+          json.value("Unknown(%x)", i);
+          break;
+      }
+    }
+  }
+}
+
+static void DumpFunctionFlagsItems(js::JSONPrinter& json,
+                                   FunctionFlags functionFlags) {
+  switch (functionFlags.kind()) {
+    case FunctionFlags::FunctionKind::NormalFunction:
+      json.value("NORMAL_KIND");
+      break;
+    case FunctionFlags::FunctionKind::AsmJS:
+      json.value("ASMJS_KIND");
+      break;
+    case FunctionFlags::FunctionKind::Wasm:
+      json.value("WASM_KIND");
+      break;
+    case FunctionFlags::FunctionKind::Arrow:
+      json.value("ARROW_KIND");
+      break;
+    case FunctionFlags::FunctionKind::Method:
+      json.value("METHOD_KIND");
+      break;
+    case FunctionFlags::FunctionKind::ClassConstructor:
+      json.value("CLASSCONSTRUCTOR_KIND");
+      break;
+    case FunctionFlags::FunctionKind::Getter:
+      json.value("GETTER_KIND");
+      break;
+    case FunctionFlags::FunctionKind::Setter:
+      json.value("SETTER_KIND");
+      break;
+    default:
+      json.value("Unknown(%x)", uint8_t(functionFlags.kind()));
+      break;
+  }
+
+  static_assert(FunctionFlags::FUNCTION_KIND_MASK == 0x0007,
+                "FunctionKind should use the lowest 3 bits");
+  for (uint16_t i = 1 << 3; i; i = i << 1) {
+    if (functionFlags.toRaw() & i) {
+      switch (FunctionFlags::Flags(i)) {
+        case FunctionFlags::Flags::EXTENDED:
+          json.value("EXTENDED");
+          break;
+        case FunctionFlags::Flags::SELF_HOSTED:
+          json.value("SELF_HOSTED");
+          break;
+        case FunctionFlags::Flags::BASESCRIPT:
+          json.value("BASESCRIPT");
+          break;
+        case FunctionFlags::Flags::SELFHOSTLAZY:
+          json.value("SELFHOSTLAZY");
+          break;
+        case FunctionFlags::Flags::CONSTRUCTOR:
+          json.value("CONSTRUCTOR");
+          break;
+        case FunctionFlags::Flags::BOUND_FUN:
+          json.value("BOUND_FUN");
+          break;
+        case FunctionFlags::Flags::LAMBDA:
+          json.value("LAMBDA");
+          break;
+        case FunctionFlags::Flags::WASM_JIT_ENTRY:
+          json.value("WASM_JIT_ENTRY");
+          break;
+        case FunctionFlags::Flags::HAS_INFERRED_NAME:
+          json.value("HAS_INFERRED_NAME");
+          break;
+        case FunctionFlags::Flags::ATOM_EXTRA_FLAG:
+          json.value("ATOM_EXTRA_FLAG");
+          break;
+        case FunctionFlags::Flags::RESOLVED_NAME:
+          json.value("RESOLVED_NAME");
+          break;
+        case FunctionFlags::Flags::RESOLVED_LENGTH:
+          json.value("RESOLVED_LENGTH");
+          break;
+        case FunctionFlags::Flags::NEW_SCRIPT_CLEARED:
+          json.value("NEW_SCRIPT_CLEARED");
+          break;
+        default:
+          json.value("Unknown(%x)", i);
+          break;
+      }
+    }
+  }
+}
+
+static void DumpScriptThing(js::JSONPrinter& json, ScriptThingVariant& thing) {
+  struct Matcher {
+    js::JSONPrinter& json;
+
+    void operator()(ScriptAtom& data) {
+      json.beginObject();
+      json.property("type", "ScriptAtom");
+      JSAtom* atom = data;
+      GenericPrinter& out = json.beginStringProperty("value");
+      atom->dumpCharsNoQuote(out);
+      json.endStringProperty();
+      json.endObject();
+    }
+
+    void operator()(NullScriptThing& data) { json.nullValue(); }
+
+    void operator()(BigIntIndex& index) {
+      json.value("BigIntIndex(%zu)", size_t(index));
+    }
+
+    void operator()(RegExpIndex& index) {
+      json.value("RegExpIndex(%zu)", size_t(index));
+    }
+
+    void operator()(ObjLiteralCreationData& data) {
+      json.beginObject();
+      json.property("type", "ObjLiteralCreationData");
+      json.beginObjectProperty("value");
+      data.dumpFields(json);
+      json.endObject();
+      json.endObject();
+    }
+
+    void operator()(ScopeIndex& index) {
+      json.value("ScopeIndex(%zu)", size_t(index));
+    }
+
+    void operator()(FunctionIndex& index) {
+      json.value("FunctionIndex(%zu)", size_t(index));
+    }
+
+    void operator()(EmptyGlobalScopeType& emptyGlobalScope) {
+      json.value("EmptyGlobalScope");
+    }
+  };
+
+  Matcher m{json};
+  thing.match(m);
+}
+
+void ScriptStencil::dump() {
+  js::Fprinter out(stderr);
+  js::JSONPrinter json(out);
+  dump(json);
+}
+
+void ScriptStencil::dump(js::JSONPrinter& json) {
+  json.beginObject();
+  dumpFields(json);
+  json.endObject();
+}
+
+void ScriptStencil::dumpFields(js::JSONPrinter& json) {
+  json.beginListProperty("immutableFlags");
+  DumpImmutableScriptFlags(json, immutableFlags);
+  json.endList();
+
+  if (fieldInitializers) {
+    json.formatProperty("fieldInitializers", "Some(%u)",
+                        (*fieldInitializers).numFieldInitializers);
+  } else {
+    json.property("fieldInitializers", "Nothing");
+  }
+
+  json.beginListProperty("gcThings");
+  for (auto& thing : gcThings) {
+    DumpScriptThing(json, thing);
+  }
+  json.endList();
+
+  if (immutableScriptData) {
+    json.formatProperty("immutableScriptData", "u8[%zu]",
+                        immutableScriptData->immutableData().Length());
+  } else {
+    json.nullProperty("immutableScriptData");
+  }
+
+  json.beginObjectProperty("extent");
+  json.property("sourceStart", extent.sourceStart);
+  json.property("sourceEnd", extent.sourceEnd);
+  json.property("toStringStart", extent.toStringStart);
+  json.property("toStringEnd", extent.toStringEnd);
+  json.property("lineno", extent.lineno);
+  json.property("column", extent.column);
+  json.endObject();
+
+  if (isFunction()) {
+    if (functionAtom) {
+      GenericPrinter& out = json.beginStringProperty("functionAtom");
+      functionAtom->dumpCharsNoQuote(out);
+      json.endStringProperty();
+    } else {
+      json.nullProperty("functionAtom");
+    }
+
+    json.beginListProperty("functionFlags");
+    DumpFunctionFlagsItems(json, functionFlags);
+    json.endList();
+
+    json.property("nargs", nargs);
+
+    if (lazyFunctionEnclosingScopeIndex_) {
+      json.formatProperty("lazyFunctionEnclosingScopeIndex",
+                          "Some(ScopeIndex(%zu))",
+                          size_t(*lazyFunctionEnclosingScopeIndex_));
+    } else {
+      json.property("lazyFunctionEnclosingScopeIndex", "Nothing");
+    }
+
+    json.boolProperty("isStandaloneFunction", isStandaloneFunction);
+    json.boolProperty("wasFunctionEmitted", wasFunctionEmitted);
+    json.boolProperty("isSingletonFunction", isSingletonFunction);
+  }
+}
+
+void CompilationInfo::dumpStencil() {
+  js::Fprinter out(stderr);
+  js::JSONPrinter json(out);
+  dumpStencil(json);
+  out.put("\n");
+}
+
+void CompilationInfo::dumpStencil(js::JSONPrinter& json) {
+  json.beginObject();
+
+  json.beginObjectProperty("topLevel");
+  topLevel.get().dumpFields(json);
+  json.endObject();
+
+  // FIXME: dump asmJS
+
+  json.beginListProperty("funcData");
+  for (size_t i = 0; i < funcData.length(); i++) {
+    funcData[i].get().dump(json);
+  }
+  json.endList();
+
+  json.beginListProperty("regExpData");
+  for (auto& data : regExpData) {
+    data.dump(json);
+  }
+  json.endList();
+
+  json.beginListProperty("bigIntData");
+  for (auto& data : bigIntData) {
+    data.dump(json);
+  }
+  json.endList();
+
+  json.beginListProperty("scopeData");
+  for (auto& data : scopeData) {
+    data.dump(json);
+  }
+  json.endList();
+
+  if (topLevel.get().isModule()) {
+    json.beginObjectProperty("moduleMetadata");
+    moduleMetadata.get().dumpFields(json);
+    json.endObject();
+  }
+
+  json.endObject();
+}
+
+#endif  // defined(DEBUG) || defined(JS_JITSPEW)
--- a/js/src/frontend/Stencil.h
+++ b/js/src/frontend/Stencil.h
@@ -38,17 +38,21 @@
 #include "vm/Runtime.h"   // ReportOutOfMemory
 #include "vm/Scope.h"  // BaseScopeData, FunctionScope, LexicalScope, VarScope, GlobalScope, EvalScope, ModuleScope
 #include "vm/ScopeKind.h"      // ScopeKind
 #include "vm/SharedStencil.h"  // ImmutableScriptFlags, GCThingIndex
 #include "vm/StencilEnums.h"   // ImmutableScriptFlagsEnum
 
 class JS_PUBLIC_API JSTracer;
 
-namespace js::frontend {
+namespace js {
+
+class JSONPrinter;
+
+namespace frontend {
 
 struct CompilationInfo;
 
 // [SMDOC] Script Stencil (Frontend Representation)
 //
 // Stencils are GC object free representations of artifacts created during
 // parsing and bytecode emission that are being used as part of Project
 // Stencil (https://bugzilla.mozilla.org/show_bug.cgi?id=stencil) to revamp
@@ -92,16 +96,21 @@ class RegExpCreationData {
     }
     flags_ = flags;
     return true;
   }
 
   MOZ_MUST_USE bool init(JSContext* cx, JSAtom* pattern, JS::RegExpFlags flags);
 
   RegExpObject* createRegExp(JSContext* cx) const;
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+  void dump();
+  void dump(JSONPrinter& json);
+#endif
 };
 
 using RegExpIndex = TypedIndex<RegExpCreationData>;
 
 // This owns a set of characters guaranteed to parse into a BigInt via
 // ParseBigIntLiteral. Used to avoid allocating the BigInt on the
 // GC heap during parsing.
 class BigIntCreationData {
@@ -129,16 +138,21 @@ class BigIntCreationData {
 
     return js::ParseBigIntLiteral(cx, source);
   }
 
   bool isZero() {
     mozilla::Range<const char16_t> source(buf_.get(), length_);
     return js::BigIntLiteralIsZero(source);
   }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+  void dump();
+  void dump(JSONPrinter& json);
+#endif
 };
 
 using BigIntIndex = TypedIndex<BigIntCreationData>;
 
 class ScopeStencil {
   friend class js::AbstractScopePtr;
   friend class js::GCMarker;
 
@@ -239,16 +253,22 @@ class ScopeStencil {
   bool isArrow() const { return isArrow_; }
 
   Scope* createScope(JSContext* cx, CompilationInfo& compilationInfo);
 
   void trace(JSTracer* trc);
 
   uint32_t nextFrameSlot() const;
 
+#if defined(DEBUG) || defined(JS_JITSPEW)
+  void dump();
+  void dump(JSONPrinter& json);
+  void dumpFields(JSONPrinter& json);
+#endif
+
  private:
   // Non owning reference to data
   template <typename SpecificScopeType>
   typename SpecificScopeType::Data& data() const {
     MOZ_ASSERT(data_.get());
     return *static_cast<typename SpecificScopeType::Data*>(data_.get());
   }
 
@@ -380,16 +400,22 @@ class StencilModuleMetadata {
         localExportEntries(cx),
         indirectExportEntries(cx),
         starExportEntries(cx),
         functionDecls(cx) {}
 
   bool initModule(JSContext* cx, JS::Handle<ModuleObject*> module);
 
   void trace(JSTracer* trc);
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+  void dump();
+  void dump(JSONPrinter& json);
+  void dumpFields(JSONPrinter& json);
+#endif
 };
 
 // The lazy closed-over-binding info is represented by these types that will
 // convert to a GCCellPtr(nullptr), GCCellPtr(JSAtom*).
 class NullScriptThing {};
 using ScriptAtom = JSAtom*;
 
 // These types all end up being baked into GC things as part of stencil
@@ -482,19 +508,26 @@ class ScriptStencil {
     return result;
   }
 
   bool isModule() const {
     bool result = immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsModule);
     MOZ_ASSERT_IF(result, !isFunction());
     return result;
   }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+  void dump();
+  void dump(JSONPrinter& json);
+  void dumpFields(JSONPrinter& json);
+#endif
 };
 
-} /* namespace js::frontend */
+} /* namespace frontend */
+} /* namespace js */
 
 namespace JS {
 template <>
 struct GCPolicy<js::frontend::ScopeStencil*> {
   static void trace(JSTracer* trc, js::frontend::ScopeStencil** data,
                     const char* name) {
     (*data)->trace(trc);
   }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -65,16 +65,17 @@
 #endif
 
 #include "builtin/Array.h"
 #include "builtin/MapObject.h"
 #include "builtin/ModuleObject.h"
 #include "builtin/RegExp.h"
 #include "builtin/TestingFunctions.h"
 #include "debugger/DebugAPI.h"
+#include "frontend/BytecodeEmitter.h"
 #include "frontend/CompilationInfo.h"
 #ifdef JS_ENABLE_SMOOSH
 #  include "frontend/Frontend2.h"
 #endif
 #include "frontend/ModuleSharedContext.h"
 #include "frontend/Parser.h"
 #include "frontend/SourceNotes.h"  // SrcNote, SrcNoteType, SrcNoteIterator
 #include "gc/PublicIterators.h"
@@ -5118,60 +5119,144 @@ static bool SetModuleResolveHook(JSConte
   global->setReservedSlot(GlobalAppSlotModuleResolveHook, args[0]);
 
   JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook);
 
   args.rval().setUndefined();
   return true;
 }
 
+enum class DumpType {
+  ParseNode,
+  Stencil,
+};
+
 template <typename Unit>
-static bool FullParseTest(JSContext* cx,
-                          const JS::ReadOnlyCompileOptions& options,
-                          const Unit* units, size_t length,
-                          js::frontend::CompilationInfo& compilationInfo,
-                          js::frontend::ParseGoal goal) {
+static bool FrontendTest(JSContext* cx,
+                         const JS::ReadOnlyCompileOptions& options,
+                         const Unit* units, size_t length,
+                         js::frontend::CompilationInfo& compilationInfo,
+                         js::frontend::ParseGoal goal, DumpType dumpType) {
   using namespace js::frontend;
-  Parser<FullParseHandler, Unit> parser(cx, options, units, length,
-                                        /* foldConstants = */ false,
-                                        compilationInfo, nullptr, nullptr);
+
+  bool foldConstants = dumpType == DumpType::Stencil;
+
+  Parser<SyntaxParseHandler, Unit> syntaxParser(cx, options, units, length,
+                                                foldConstants, compilationInfo,
+                                                nullptr, nullptr);
+  if (!syntaxParser.checkOptions()) {
+    return false;
+  }
+
+  Parser<FullParseHandler, Unit> parser(
+      cx, options, units, length, foldConstants, compilationInfo,
+      dumpType == DumpType::Stencil ? &syntaxParser : nullptr, nullptr);
   if (!parser.checkOptions()) {
     return false;
   }
 
-  js::frontend::ParseNode* pn;  // Deallocated once `parser` goes out of scope.
   if (goal == frontend::ParseGoal::Script) {
-    pn = parser.parse();
+    js::frontend::ParseNode* pn;
+    switch (dumpType) {
+      case DumpType::ParseNode: {
+        pn = parser.parse();
+        if (!pn) {
+          return false;
+        }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+        js::Fprinter out(stderr);
+        DumpParseTree(pn, out);
+#endif
+        break;
+      }
+      case DumpType::Stencil: {
+        SourceExtent extent = SourceExtent::makeGlobalExtent(length, options);
+        Directives directives(options.forceStrictMode());
+        GlobalSharedContext globalsc(cx, ScopeKind::Global, compilationInfo,
+                                     directives, extent);
+        pn = parser.globalBody(&globalsc);
+        if (!pn) {
+          return false;
+        }
+
+        BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc,
+                            compilationInfo);
+        if (!bce.init()) {
+          return false;
+        }
+        if (!bce.emitScript(pn)) {
+          return false;
+        }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+        compilationInfo.dumpStencil();
+#endif
+        break;
+      }
+    }
   } else {
     if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) {
       return false;
     }
 
     ModuleBuilder builder(cx, &parser);
 
     SourceExtent extent = SourceExtent::makeGlobalExtent(length);
     ModuleSharedContext modulesc(cx, compilationInfo, builder, extent);
-    pn = parser.moduleBody(&modulesc);
-  }
-  if (!pn) {
-    return false;
-  }
-#ifdef DEBUG
-  js::Fprinter out(stderr);
-  DumpParseTree(pn, out);
-#endif
-  return true;
-}
-
-static bool Parse(JSContext* cx, unsigned argc, Value* vp) {
+    js::frontend::ParseNode* pn = parser.moduleBody(&modulesc);
+    if (!pn) {
+      return false;
+    }
+
+    switch (dumpType) {
+      case DumpType::ParseNode: {
+        pn = parser.parse();
+        if (!pn) {
+          return false;
+        }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+        js::Fprinter out(stderr);
+        DumpParseTree(pn, out);
+#endif
+        break;
+      }
+      case DumpType::Stencil: {
+        BytecodeEmitter bce(/* parent = */ nullptr, &parser, &modulesc,
+                            compilationInfo);
+        if (!bce.init()) {
+          return false;
+        }
+        if (!bce.emitScript(pn->as<ModuleNode>().body())) {
+          return false;
+        }
+
+        StencilModuleMetadata& moduleMetadata =
+            compilationInfo.moduleMetadata.get();
+        builder.finishFunctionDecls(moduleMetadata);
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+        compilationInfo.dumpStencil();
+#endif
+        break;
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool FrontendTest(JSContext* cx, unsigned argc, Value* vp,
+                         const char* funcName, DumpType dumpType) {
   using namespace js::frontend;
 
   CallArgs args = CallArgsFromVp(argc, vp);
 
-  if (!args.requireAtLeast(cx, "parse", 1)) {
+  if (!args.requireAtLeast(cx, funcName, 1)) {
     return false;
   }
   if (!args[0].isString()) {
     const char* typeName = InformalValueTypeName(args[0]);
     JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
     return false;
   }
 
@@ -5254,70 +5339,120 @@ static bool Parse(JSContext* cx, unsigne
   } else {
     if (!stableChars.initTwoByte(cx, scriptContents)) {
       return false;
     }
   }
 
   size_t length = scriptContents->length();
 #ifdef JS_ENABLE_SMOOSH
-  if (smoosh) {
-    if (isAscii) {
-      const Latin1Char* chars = stableChars.latin1Range().begin().get();
-      if (goal == frontend::ParseGoal::Script) {
-        if (!SmooshParseScript(cx, chars, length)) {
-          return false;
+  if (dumpType == DumpType::ParseNode) {
+    if (smoosh) {
+      if (isAscii) {
+        const Latin1Char* chars = stableChars.latin1Range().begin().get();
+
+        if (goal == frontend::ParseGoal::Script) {
+          if (!SmooshParseScript(cx, chars, length)) {
+            return false;
+          }
+        } else {
+          if (!SmooshParseModule(cx, chars, length)) {
+            return false;
+          }
         }
-      } else {
-        if (!SmooshParseModule(cx, chars, length)) {
-          return false;
-        }
-      }
-      args.rval().setUndefined();
-      return true;
-    }
-    JS_ReportErrorASCII(cx,
-                        "SmooshMonkey does not support non-ASCII chars yet");
-    return false;
+        args.rval().setUndefined();
+        return true;
+      }
+      JS_ReportErrorASCII(cx,
+                          "SmooshMonkey does not support non-ASCII chars yet");
+      return false;
+    }
   }
 #endif  // JS_ENABLE_SMOOSH
 
   CompileOptions options(cx);
-  options.setIntroductionType("js shell parse").setFileAndLine("<string>", 1);
+  options.setIntroductionType("js shell parse")
+      .setFileAndLine("<string>", 1)
+      .setIsRunOnce(true)
+      .setNoScriptRval(true);
+
   if (goal == frontend::ParseGoal::Module) {
     // See frontend::CompileModule.
     options.setForceStrictMode();
     options.allowHTMLComments = false;
   }
 
   LifoAllocScope allocScope(&cx->tempLifoAlloc());
   js::frontend::CompilationInfo compilationInfo(cx, allocScope, options);
   if (!compilationInfo.init(cx)) {
     return false;
   }
 
+#ifdef JS_ENABLE_SMOOSH
+  if (dumpType == DumpType::Stencil) {
+    if (smoosh) {
+      if (isAscii) {
+        if (goal == frontend::ParseGoal::Script) {
+          const Latin1Char* latin1 = stableChars.latin1Range().begin().get();
+          auto utf8 = reinterpret_cast<const mozilla::Utf8Unit*>(latin1);
+          JS::SourceText<Utf8Unit> srcBuf;
+          if (!srcBuf.init(cx, utf8, length, JS::SourceOwnership::Borrowed)) {
+            return false;
+          }
+
+          bool unimplemented;
+          if (!Smoosh::compileGlobalScriptToStencil(compilationInfo, srcBuf,
+                                                    &unimplemented)) {
+            return false;
+          }
+
+#  ifdef DEBUG
+          compilationInfo.dumpStencil();
+#  endif
+        } else {
+          JS_ReportErrorASCII(cx,
+                              "SmooshMonkey does not support module stencil");
+        }
+        args.rval().setUndefined();
+        return true;
+      }
+      JS_ReportErrorASCII(cx,
+                          "SmooshMonkey does not support non-ASCII chars yet");
+      return false;
+    }
+  }
+#endif  // JS_ENABLE_SMOOSH
+
   if (isAscii) {
     const Latin1Char* latin1 = stableChars.latin1Range().begin().get();
     auto utf8 = reinterpret_cast<const mozilla::Utf8Unit*>(latin1);
-    if (!FullParseTest<mozilla::Utf8Unit>(cx, options, utf8, length,
-                                          compilationInfo, goal)) {
+    if (!FrontendTest<mozilla::Utf8Unit>(cx, options, utf8, length,
+                                         compilationInfo, goal, dumpType)) {
       return false;
     }
   } else {
     MOZ_ASSERT(stableChars.isTwoByte());
     const char16_t* chars = stableChars.twoByteRange().begin().get();
-    if (!FullParseTest<char16_t>(cx, options, chars, length, compilationInfo,
-                                 goal)) {
+    if (!FrontendTest<char16_t>(cx, options, chars, length, compilationInfo,
+                                goal, dumpType)) {
       return false;
     }
   }
   args.rval().setUndefined();
   return true;
 }
 
+static bool DumpStencil(JSContext* cx, unsigned argc, Value* vp) {
+  return FrontendTest(cx, argc, vp, "dumpStencil", DumpType::Stencil);
+}
+
+static bool Parse(JSContext* cx, unsigned argc, Value* vp) {
+  return FrontendTest(cx, argc, vp, "parse", DumpType::ParseNode);
+}
+
 static bool SyntaxParse(JSContext* cx, unsigned argc, Value* vp) {
   using namespace js::frontend;
 
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!args.requireAtLeast(cx, "syntaxParse", 1)) {
     return false;
   }
@@ -8571,16 +8706,20 @@ JS_FN_HELP("rateMyCacheIR", RateMyCacheI
 "   Takes a XDR bytecode representation of an uninstantiated ModuleObject and returns a ModuleObject."),
 
     JS_FN_HELP("setModuleResolveHook", SetModuleResolveHook, 1, 0,
 "setModuleResolveHook(function(referrer, specifier))",
 "  Set the HostResolveImportedModule hook to |function|, overriding the shell\n"
 "  module loader for testing purposes. This hook is used to look up a\n"
 "  previously loaded module object."),
 
+    JS_FN_HELP("dumpStencil", DumpStencil, 1, 0,
+"dumpStencil(code)",
+"  Parses a string and returns string that represents stencil."),
+
     JS_FN_HELP("parse", Parse, 1, 0,
 "parse(code)",
 "  Parses a string, potentially throwing."),
 
     JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0,
 "syntaxParse(code)",
 "  Check the syntax of a string, returning success value"),
 
--- a/js/src/vm/JSONPrinter.cpp
+++ b/js/src/vm/JSONPrinter.cpp
@@ -33,56 +33,82 @@ void JSONPrinter::indent() {
 }
 
 void JSONPrinter::propertyName(const char* name) {
   if (!first_) {
     out_.putChar(',');
   }
   indent();
   out_.printf("\"%s\":", name);
+  if (indent_) {
+    out_.put(" ");
+  }
   first_ = false;
 }
 
 void JSONPrinter::beginObject() {
   if (!first_) {
     out_.putChar(',');
-    indent();
   }
+  indent();
   out_.putChar('{');
   indentLevel_++;
   first_ = true;
 }
 
 void JSONPrinter::beginList() {
   if (!first_) {
     out_.putChar(',');
   }
+  indent();
   out_.putChar('[');
+  indentLevel_++;
   first_ = true;
 }
 
 void JSONPrinter::beginObjectProperty(const char* name) {
   propertyName(name);
   out_.putChar('{');
   indentLevel_++;
   first_ = true;
 }
 
 void JSONPrinter::beginListProperty(const char* name) {
   propertyName(name);
   out_.putChar('[');
+  indentLevel_++;
   first_ = true;
 }
 
-void JSONPrinter::beginStringProperty(const char* name) {
+GenericPrinter& JSONPrinter::beginStringProperty(const char* name) {
   propertyName(name);
   out_.putChar('"');
+  return out_;
+}
+
+void JSONPrinter::endStringProperty() {
+  endString();
+  first_ = false;
 }
 
-void JSONPrinter::endStringProperty() { out_.putChar('"'); }
+GenericPrinter& JSONPrinter::beginString() {
+  if (!first_) {
+    out_.putChar(',');
+  }
+  indent();
+  out_.putChar('"');
+  return out_;
+}
+
+void JSONPrinter::endString() { out_.putChar('"'); }
+
+void JSONPrinter::boolProperty(const char* name, bool value) {
+  propertyName(name);
+  out_.put(value ? "true" : "false");
+}
 
 void JSONPrinter::property(const char* name, const char* value) {
   beginStringProperty(name);
   out_.put(value);
   endStringProperty();
 }
 
 void JSONPrinter::formatProperty(const char* name, const char* format, ...) {
@@ -105,16 +131,17 @@ void JSONPrinter::formatProperty(const c
 
 void JSONPrinter::value(const char* format, ...) {
   va_list ap;
   va_start(ap, format);
 
   if (!first_) {
     out_.putChar(',');
   }
+  indent();
   out_.putChar('"');
   out_.vprintf(format, ap);
   out_.putChar('"');
 
   va_end(ap);
   first_ = false;
 }
 
@@ -122,16 +149,17 @@ void JSONPrinter::property(const char* n
   propertyName(name);
   out_.printf("%" PRId32, value);
 }
 
 void JSONPrinter::value(int val) {
   if (!first_) {
     out_.putChar(',');
   }
+  indent();
   out_.printf("%d", val);
   first_ = false;
 }
 
 void JSONPrinter::property(const char* name, uint32_t value) {
   propertyName(name);
   out_.printf("%" PRIu32, value);
 }
@@ -197,19 +225,35 @@ void JSONPrinter::property(const char* n
       split = lldiv(static_cast<int64_t>(dur.ToMicroseconds()), 1000);
       break;
     case MICROSECONDS:
       MOZ_ASSERT_UNREACHABLE("");
   };
   out_.printf("%lld.%03lld", split.quot, split.rem);
 }
 
+void JSONPrinter::nullProperty(const char* name) {
+  propertyName(name);
+  out_.put("null");
+}
+
+void JSONPrinter::nullValue() {
+  if (!first_) {
+    out_.putChar(',');
+  }
+  indent();
+  out_.put("null");
+  first_ = false;
+}
+
 void JSONPrinter::endObject() {
   indentLevel_--;
   indent();
   out_.putChar('}');
   first_ = false;
 }
 
 void JSONPrinter::endList() {
+  indentLevel_--;
+  indent();
   out_.putChar(']');
   first_ = false;
 }
--- a/js/src/vm/JSONPrinter.h
+++ b/js/src/vm/JSONPrinter.h
@@ -41,16 +41,18 @@ class JSONPrinter {
   void beginObject();
   void beginList();
   void beginObjectProperty(const char* name);
   void beginListProperty(const char* name);
 
   void value(const char* format, ...) MOZ_FORMAT_PRINTF(2, 3);
   void value(int value);
 
+  void boolProperty(const char* name, bool value);
+
   void property(const char* name, const char* value);
   void property(const char* name, int32_t value);
   void property(const char* name, uint32_t value);
   void property(const char* name, int64_t value);
   void property(const char* name, uint64_t value);
 #if defined(XP_DARWIN) || defined(__OpenBSD__)
   // On OSX and OpenBSD, size_t is long unsigned, uint32_t is unsigned, and
   // uint64_t is long long unsigned. Everywhere else, size_t matches either
@@ -65,19 +67,25 @@ class JSONPrinter {
   // JSON requires decimals to be separated by periods, but the LC_NUMERIC
   // setting may cause printf to use commas in some locales.
   enum TimePrecision { SECONDS, MILLISECONDS, MICROSECONDS };
   void property(const char* name, const mozilla::TimeDuration& dur,
                 TimePrecision precision);
 
   void floatProperty(const char* name, double value, size_t precision);
 
-  void beginStringProperty(const char* name);
+  GenericPrinter& beginStringProperty(const char* name);
   void endStringProperty();
 
+  GenericPrinter& beginString();
+  void endString();
+
+  void nullProperty(const char* name);
+  void nullValue();
+
   void endObject();
   void endList();
 
   // Notify the output that the caller has detected OOM and should transition
   // to its saw-OOM state.
   void outOfMemory() { out_.reportOutOfMemory(); }
 
  protected:
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -109,16 +109,17 @@ class AbstractBindingName {
   uint8_t flagsForXDR() const { return static_cast<uint8_t>(bits_ & FlagMask); }
 
   NameT* name() const { return reinterpret_cast<NameT*>(bits_ & ~FlagMask); }
 
   bool closedOver() const { return bits_ & ClosedOverFlag; }
 
  private:
   friend class AbstractBindingIter<NameT>;
+  friend class frontend::ScopeStencil;
 
   // This method should be called only for binding names in `vars` range in
   // BindingIter.
   bool isTopLevelFunction() const { return bits_ & TopLevelFunctionFlag; }
 
  public:
   void trace(JSTracer* trc);
 };
--- a/js/src/vm/StringType.cpp
+++ b/js/src/vm/StringType.cpp
@@ -257,54 +257,80 @@ void JSString::dumpChars(const CharT* s,
   if (n == SIZE_MAX) {
     n = 0;
     while (s[n]) {
       n++;
     }
   }
 
   out.put("\"");
+  dumpCharsNoQuote(s, n, out);
+  out.putChar('"');
+}
+
+template void JSString::dumpChars(const Latin1Char* s, size_t n,
+                                  js::GenericPrinter& out);
+
+template void JSString::dumpChars(const char16_t* s, size_t n,
+                                  js::GenericPrinter& out);
+
+template <typename CharT>
+/*static */
+void JSString::dumpCharsNoQuote(const CharT* s, size_t n,
+                                js::GenericPrinter& out) {
   for (size_t i = 0; i < n; i++) {
     char16_t c = s[i];
     if (c == '\n') {
       out.put("\\n");
     } else if (c == '\t') {
       out.put("\\t");
     } else if (c >= 32 && c < 127) {
       out.putChar((char)s[i]);
     } else if (c <= 255) {
       out.printf("\\x%02x", unsigned(c));
     } else {
       out.printf("\\u%04x", unsigned(c));
     }
   }
-  out.putChar('"');
 }
 
-template void JSString::dumpChars(const Latin1Char* s, size_t n,
-                                  js::GenericPrinter& out);
+template void JSString::dumpCharsNoQuote(const Latin1Char* s, size_t n,
+                                         js::GenericPrinter& out);
 
-template void JSString::dumpChars(const char16_t* s, size_t n,
-                                  js::GenericPrinter& out);
+template void JSString::dumpCharsNoQuote(const char16_t* s, size_t n,
+                                         js::GenericPrinter& out);
 
 void JSString::dumpCharsNoNewline(js::GenericPrinter& out) {
   if (JSLinearString* linear = ensureLinear(nullptr)) {
     AutoCheckCannotGC nogc;
     if (hasLatin1Chars()) {
       out.put("[Latin 1]");
       dumpChars(linear->latin1Chars(nogc), length(), out);
     } else {
       out.put("[2 byte]");
       dumpChars(linear->twoByteChars(nogc), length(), out);
     }
   } else {
     out.put("(oom in JSString::dumpCharsNoNewline)");
   }
 }
 
+void JSString::dumpCharsNoQuote(js::GenericPrinter& out) {
+  if (JSLinearString* linear = ensureLinear(nullptr)) {
+    AutoCheckCannotGC nogc;
+    if (hasLatin1Chars()) {
+      dumpCharsNoQuote(linear->latin1Chars(nogc), length(), out);
+    } else {
+      dumpCharsNoQuote(linear->twoByteChars(nogc), length(), out);
+    }
+  } else {
+    out.put("(oom in JSString::dumpCharsNoNewline)");
+  }
+}
+
 void JSString::dump() {
   js::Fprinter out(stderr);
   dump(out);
 }
 
 void JSString::dump(js::GenericPrinter& out) {
   dumpNoNewline(out);
   out.putChar('\n');
--- a/js/src/vm/StringType.h
+++ b/js/src/vm/StringType.h
@@ -625,20 +625,25 @@ class JSString : public js::gc::CellWith
 #if defined(DEBUG) || defined(JS_JITSPEW)
   void dump();  // Debugger-friendly stderr dump.
   void dump(js::GenericPrinter& out);
   void dumpNoNewline(js::GenericPrinter& out);
   void dumpCharsNoNewline(js::GenericPrinter& out);
   void dumpRepresentation(js::GenericPrinter& out, int indent) const;
   void dumpRepresentationHeader(js::GenericPrinter& out,
                                 const char* subclass) const;
+  void dumpCharsNoQuote(js::GenericPrinter& out);
 
   template <typename CharT>
   static void dumpChars(const CharT* s, size_t len, js::GenericPrinter& out);
 
+  template <typename CharT>
+  static void dumpCharsNoQuote(const CharT* s, size_t len,
+                               js::GenericPrinter& out);
+
   bool equals(const char* s);
 #endif
 
   void traceChildren(JSTracer* trc);
 
   static MOZ_ALWAYS_INLINE void readBarrier(JSString* thing) {
     if (thing->isPermanentAtom() || js::gc::IsInsideNursery(thing)) {
       return;