Bug 1622561 - Modernize and cleanup source notes. r=jorendorff
authorTooru Fujisawa <arai_a@mac.com>
Sat, 28 Mar 2020 11:15:43 +0000
changeset 520963 1f3269207d8c3dc01f7d3200a9989e323e975a70
parent 520962 8a6011666e5ff1ffe800424d0b600203bee63523
child 520964 d54856a882de3195adde4cbc9d0580e2fa48de01
push id37261
push useraiakab@mozilla.com
push dateSun, 29 Mar 2020 09:54:17 +0000
treeherdermozilla-central@4f16fe418e32 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1622561
milestone76.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 1622561 - Modernize and cleanup source notes. r=jorendorff Differential Revision: https://phabricator.services.mozilla.com/D66901
js/src/frontend/BytecodeControlStructures.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/BytecodeSection.h
js/src/frontend/CForEmitter.cpp
js/src/frontend/Frontend2.cpp
js/src/frontend/ObjectEmitter.cpp
js/src/frontend/SourceNotes.cpp
js/src/frontend/SourceNotes.h
js/src/frontend/TryEmitter.cpp
js/src/frontend/moz.build
js/src/shell/js.cpp
js/src/vm/BytecodeUtil-inl.h
js/src/vm/BytecodeUtil.cpp
js/src/vm/BytecodeUtil.h
js/src/vm/Caches.h
js/src/vm/CodeCoverage.cpp
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
js/src/vm/SharedStencil.h
--- a/js/src/frontend/BytecodeControlStructures.cpp
+++ b/js/src/frontend/BytecodeControlStructures.cpp
@@ -3,17 +3,16 @@
  * 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/. */
 
 #include "frontend/BytecodeControlStructures.h"
 
 #include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
 #include "frontend/EmitterScope.h"     // EmitterScope
-#include "frontend/SourceNotes.h"      // SRC_*
 #include "vm/Opcodes.h"                // JSOp
 
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::Maybe;
 
 NestableControl::NestableControl(BytecodeEmitter* bce, StatementKind kind)
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -46,16 +46,17 @@
 #include "frontend/ModuleSharedContext.h"  // ModuleSharedContext
 #include "frontend/NameFunctions.h"        // NameFunctions
 #include "frontend/NameOpEmitter.h"        // NameOpEmitter
 #include "frontend/ObjectEmitter.h"  // PropertyEmitter, ObjectEmitter, ClassEmitter
 #include "frontend/OptionalEmitter.h"  // OptionalEmitter
 #include "frontend/ParseNode.h"      // ParseNodeKind, ParseNode and subclasses
 #include "frontend/Parser.h"         // Parser
 #include "frontend/PropOpEmitter.h"  // PropOpEmitter
+#include "frontend/SourceNotes.h"    // SrcNote, SrcNoteType, SrcNoteWriter
 #include "frontend/SwitchEmitter.h"  // SwitchEmitter
 #include "frontend/TDZCheckCache.h"  // TDZCheckCache
 #include "frontend/TryEmitter.h"     // TryEmitter
 #include "frontend/WhileEmitter.h"   // WhileEmitter
 #include "js/CompileOptions.h"       // TransitiveCompileOptions, CompileOptions
 #include "vm/AsyncFunction.h"        // AsyncFunctionResolveKind
 #include "vm/BytecodeUtil.h"  // IsArgOp, IsLocalOp, SET_UINT24, SET_ICINDEX, BytecodeFallsThrough, BytecodeIsJumpTarget
 #include "vm/GeneratorObject.h"  // AbstractGeneratorObject
@@ -187,21 +188,21 @@ bool BytecodeEmitter::markStepBreakpoint
   if (inPrologue()) {
     return true;
   }
 
   if (!emitInstrumentation(InstrumentationKind::Breakpoint)) {
     return false;
   }
 
-  if (!newSrcNote(SRC_STEP_SEP)) {
-    return false;
-  }
-
-  if (!newSrcNote(SRC_BREAKPOINT)) {
+  if (!newSrcNote(SrcNoteType::StepSep)) {
+    return false;
+  }
+
+  if (!newSrcNote(SrcNoteType::Breakpoint)) {
     return false;
   }
 
   // We track the location of the most recent separator for use in
   // markSimpleBreakpoint. Note that this means that the position must already
   // be set before markStepBreakpoint is called.
   bytecodeSection().updateSeparatorPosition();
 
@@ -217,17 +218,17 @@ bool BytecodeEmitter::markSimpleBreakpoi
   // expression start, we need to skip marking it breakable in order to avoid
   // having two breakpoints with the same line/column position.
   // Note: This assumes that the position for the call has already been set.
   if (!bytecodeSection().isDuplicateLocation()) {
     if (!emitInstrumentation(InstrumentationKind::Breakpoint)) {
       return false;
     }
 
-    if (!newSrcNote(SRC_BREAKPOINT)) {
+    if (!newSrcNote(SrcNoteType::Breakpoint)) {
       return false;
     }
   }
 
   return true;
 }
 
 bool BytecodeEmitter::emitCheck(JSOp op, ptrdiff_t delta,
@@ -498,20 +499,16 @@ bool BytecodeEmitter::emitPopN(unsigned 
 bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) {
   return emit2(JSOp::CheckIsObj, uint8_t(kind));
 }
 
 bool BytecodeEmitter::emitCheckIsCallable(CheckIsCallableKind kind) {
   return emit2(JSOp::CheckIsCallable, uint8_t(kind));
 }
 
-static inline unsigned LengthOfSetLine(unsigned line) {
-  return 1 /* SRC_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1);
-}
-
 /* Updates line number notes, not column notes. */
 bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) {
   // Don't emit line/column number notes in the prologue.
   if (inPrologue()) {
     return true;
   }
 
   ErrorReporter* er = &parser->errorReporter();
@@ -522,33 +519,34 @@ bool BytecodeEmitter::updateLineNumberNo
   }
 
   if (!onThisLine) {
     unsigned line = er->lineAt(offset);
     unsigned delta = line - bytecodeSection().currentLine();
 
     /*
      * Encode any change in the current source line number by using
-     * either several SRC_NEWLINE notes or just one SRC_SETLINE note,
-     * whichever consumes less space.
+     * either several SrcNoteType::NewLine notes or just one
+     * SrcNoteType::SetLine note, whichever consumes less space.
      *
      * NB: We handle backward line number deltas (possible with for
      * loops where the update part is emitted after the body, but its
      * line number is <= any line number in the body) here by letting
      * unsigned delta_ wrap to a very large number, which triggers a
-     * SRC_SETLINE.
+     * SrcNoteType::SetLine.
      */
     bytecodeSection().setCurrentLine(line, offset);
-    if (delta >= LengthOfSetLine(line)) {
-      if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(line))) {
+    if (delta >= SrcNote::SetLine::lengthFor(line)) {
+      if (!newSrcNote2(SrcNoteType::SetLine,
+                       SrcNote::SetLine::toOperand(line))) {
         return false;
       }
     } else {
       do {
-        if (!newSrcNote(SRC_NEWLINE)) {
+        if (!newSrcNote(SrcNoteType::NewLine)) {
           return false;
         }
       } while (--delta != 0);
     }
 
     bytecodeSection().updateSeparatorPositionIfPresent();
   }
   return true;
@@ -569,20 +567,21 @@ bool BytecodeEmitter::updateSourceCoordN
   ptrdiff_t colspan =
       ptrdiff_t(columnIndex) - ptrdiff_t(bytecodeSection().lastColumn());
   if (colspan != 0) {
     // If the column span is so large that we can't store it, then just
     // discard this information. This can happen with minimized or otherwise
     // machine-generated code. Even gigantic column numbers are still
     // valuable if you have a source map to relate them to something real;
     // but it's better to fail soft here.
-    if (!SN_REPRESENTABLE_COLSPAN(colspan)) {
+    if (!SrcNote::ColSpan::isRepresentable(colspan)) {
       return true;
     }
-    if (!newSrcNote2(SRC_COLSPAN, SN_COLSPAN_TO_OFFSET(colspan))) {
+    if (!newSrcNote2(SrcNoteType::ColSpan,
+                     SrcNote::ColSpan::toOperand(colspan))) {
       return false;
     }
     bytecodeSection().setLastColumn(columnIndex, offset);
     bytecodeSection().updateSeparatorPositionIfPresent();
   }
   return true;
 }
 
@@ -4299,17 +4298,17 @@ bool BytecodeEmitter::emitAssignmentOrIn
     if (!emitAssignmentRhs(offset)) {
       //            [stack] ... VAL? RHS
       return false;
     }
   }
 
   /* If += etc., emit the binary operator with a source note. */
   if (isCompound) {
-    if (!newSrcNote(SRC_ASSIGNOP)) {
+    if (!newSrcNote(SrcNoteType::AssignOp)) {
       return false;
     }
     if (!emit1(compoundOp)) {
       //            [stack] ... VAL
       return false;
     }
   }
 
@@ -10447,26 +10446,26 @@ bool BytecodeEmitter::emitTree(
 
     default:
       MOZ_ASSERT(0);
   }
 
   return true;
 }
 
-static bool AllocSrcNote(JSContext* cx, SrcNotesVector& notes,
+static bool AllocSrcNote(JSContext* cx, SrcNotesVector& notes, unsigned size,
                          unsigned* index) {
   size_t oldLength = notes.length();
 
-  if (MOZ_UNLIKELY(oldLength + 1 > MaxSrcNotesLength)) {
+  if (MOZ_UNLIKELY(oldLength + size > MaxSrcNotesLength)) {
     ReportAllocationOverflow(cx);
     return false;
   }
 
-  if (!notes.growByUninitialized(1)) {
+  if (!notes.growByUninitialized(size)) {
     return false;
   }
 
   *index = oldLength;
   return true;
 }
 
 bool BytecodeEmitter::addTryNote(TryNoteKind kind, uint32_t stackDepth,
@@ -10475,169 +10474,67 @@ bool BytecodeEmitter::addTryNote(TryNote
   return bytecodeSection().tryNoteList().append(kind, stackDepth, start, end);
 }
 
 bool BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp) {
   // Prologue shouldn't have source notes.
   MOZ_ASSERT(!inPrologue());
   SrcNotesVector& notes = bytecodeSection().notes();
   unsigned index;
-  if (!AllocSrcNote(cx, notes, &index)) {
-    return false;
-  }
 
   /*
    * Compute delta from the last annotated bytecode's offset.  If it's too
    * big to fit in sn, allocate one or more xdelta notes and reset sn.
    */
   BytecodeOffset offset = bytecodeSection().offset();
   ptrdiff_t delta = (offset - bytecodeSection().lastNoteOffset()).value();
   bytecodeSection().setLastNoteOffset(offset);
-  if (delta >= SN_DELTA_LIMIT) {
-    do {
-      ptrdiff_t xdelta = std::min(delta, SN_XDELTA_MASK);
-      SN_MAKE_XDELTA(&notes[index], xdelta);
-      delta -= xdelta;
-      if (!AllocSrcNote(cx, notes, &index)) {
-        return false;
-      }
-    } while (delta >= SN_DELTA_LIMIT);
-  }
-
-  /*
-   * Initialize type and delta, then allocate the minimum number of notes
-   * needed for type's arity.  Usually, we won't need more, but if an offset
-   * does take two bytes, setSrcNoteOffset will grow notes.
-   */
-  SN_MAKE_NOTE(&notes[index], type, delta);
-  for (int n = (int)js_SrcNoteSpec[type].arity; n > 0; n--) {
-    if (!newSrcNote(SRC_NULL)) {
-      return false;
-    }
+
+  auto allocator = [&](unsigned size) -> SrcNote* {
+    if (!AllocSrcNote(cx, notes, size, &index)) {
+      return nullptr;
+    }
+    return &notes[index];
+  };
+
+  if (!SrcNoteWriter::writeNote(type, delta, allocator)) {
+    return false;
   }
 
   if (indexp) {
     *indexp = index;
   }
   return true;
 }
 
 bool BytecodeEmitter::newSrcNote2(SrcNoteType type, ptrdiff_t offset,
                                   unsigned* indexp) {
   unsigned index;
   if (!newSrcNote(type, &index)) {
     return false;
   }
-  if (!setSrcNoteOffset(index, 0, BytecodeOffsetDiff(offset))) {
+  if (!newSrcNoteOperand(offset)) {
     return false;
   }
   if (indexp) {
     *indexp = index;
   }
   return true;
 }
 
-bool BytecodeEmitter::newSrcNote3(SrcNoteType type, ptrdiff_t offset1,
-                                  ptrdiff_t offset2, unsigned* indexp) {
-  unsigned index;
-  if (!newSrcNote(type, &index)) {
-    return false;
-  }
-  if (!setSrcNoteOffset(index, 0, BytecodeOffsetDiff(offset1))) {
-    return false;
-  }
-  if (!setSrcNoteOffset(index, 1, BytecodeOffsetDiff(offset2))) {
-    return false;
-  }
-  if (indexp) {
-    *indexp = index;
-  }
-  return true;
-}
-
-bool BytecodeEmitter::setSrcNoteOffset(unsigned index, unsigned which,
-                                       BytecodeOffsetDiff offset) {
-  ptrdiff_t offsetValue = offset.value();
-
-  if (!SN_REPRESENTABLE_OFFSET(offsetValue)) {
+bool BytecodeEmitter::newSrcNoteOperand(ptrdiff_t operand) {
+  if (!SrcNote::isRepresentableOperand(operand)) {
     reportError(nullptr, JSMSG_NEED_DIET, js_script_str);
     return false;
   }
 
   SrcNotesVector& notes = bytecodeSection().notes();
 
-  /* Find the offset numbered which (i.e., skip exactly which offsets). */
-  jssrcnote* sn = &notes[index];
-  MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
-  MOZ_ASSERT((int)which < js_SrcNoteSpec[SN_TYPE(sn)].arity);
-  for (sn++; which; sn++, which--) {
-    if (*sn & SN_4BYTE_OFFSET_FLAG) {
-      sn += 3;
-    }
-  }
-
-  /*
-   * See if the new offset requires four bytes either by being too big or if
-   * the offset has already been inflated (in which case, we need to stay big
-   * to not break the srcnote encoding if this isn't the last srcnote).
-   */
-  if (offsetValue > (ptrdiff_t)SN_4BYTE_OFFSET_MASK ||
-      (*sn & SN_4BYTE_OFFSET_FLAG)) {
-    /* Maybe this offset was already set to a four-byte value. */
-    if (!(*sn & SN_4BYTE_OFFSET_FLAG)) {
-      /* Insert three dummy bytes that will be overwritten shortly. */
-      if (MOZ_UNLIKELY(notes.length() + 3 > MaxSrcNotesLength)) {
-        ReportAllocationOverflow(cx);
-        return false;
-      }
-      jssrcnote dummy = 0;
-      if (!(sn = notes.insert(sn, dummy)) || !(sn = notes.insert(sn, dummy)) ||
-          !(sn = notes.insert(sn, dummy))) {
-        return false;
-      }
-    }
-    *sn++ = (jssrcnote)(SN_4BYTE_OFFSET_FLAG | (offsetValue >> 24));
-    *sn++ = (jssrcnote)(offsetValue >> 16);
-    *sn++ = (jssrcnote)(offsetValue >> 8);
-  }
-  *sn = (jssrcnote)offsetValue;
-  return true;
-}
-
-const JSSrcNoteSpec js_SrcNoteSpec[] = {
-#define DEFINE_SRC_NOTE_SPEC(sym, name, arity) {name, arity},
-    FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_SPEC)
-#undef DEFINE_SRC_NOTE_SPEC
-};
-
-static int SrcNoteArity(jssrcnote* sn) {
-  MOZ_ASSERT(SN_TYPE(sn) < SRC_LAST);
-  return js_SrcNoteSpec[SN_TYPE(sn)].arity;
-}
-
-JS_FRIEND_API unsigned js::SrcNoteLength(jssrcnote* sn) {
-  unsigned arity;
-  jssrcnote* base;
-
-  arity = SrcNoteArity(sn);
-  for (base = sn++; arity; sn++, arity--) {
-    if (*sn & SN_4BYTE_OFFSET_FLAG) {
-      sn += 3;
-    }
-  }
-  return sn - base;
-}
-
-JS_FRIEND_API ptrdiff_t js::GetSrcNoteOffset(jssrcnote* sn, unsigned which) {
-  /* Find the offset numbered which (i.e., skip exactly which offsets). */
-  MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
-  MOZ_ASSERT((int)which < SrcNoteArity(sn));
-  for (sn++; which; sn++, which--) {
-    if (*sn & SN_4BYTE_OFFSET_FLAG) {
-      sn += 3;
-    }
-  }
-  if (*sn & SN_4BYTE_OFFSET_FLAG) {
-    return (ptrdiff_t)(((uint32_t)(sn[0] & SN_4BYTE_OFFSET_MASK) << 24) |
-                       (sn[1] << 16) | (sn[2] << 8) | sn[3]);
-  }
-  return (ptrdiff_t)*sn;
-}
+  auto allocator = [&](unsigned size) -> SrcNote* {
+    unsigned index;
+    if (!AllocSrcNote(cx, notes, size, &index)) {
+      return nullptr;
+    }
+    return &notes[index];
+  };
+
+  return SrcNoteWriter::writeOperand(operand, allocator);
+}
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -347,23 +347,20 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   // relative to current section.
   MOZ_MUST_USE bool addTryNote(TryNoteKind kind, uint32_t stackDepth,
                                BytecodeOffset start, BytecodeOffset end);
 
   // Append a new source note of the given type (and therefore size) to the
   // notes dynamic array, updating noteCount. Return the new note's index
   // within the array pointed at by current->notes as outparam.
   MOZ_MUST_USE bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr);
-  MOZ_MUST_USE bool newSrcNote2(SrcNoteType type, ptrdiff_t offset,
+  MOZ_MUST_USE bool newSrcNote2(SrcNoteType type, ptrdiff_t operand,
                                 unsigned* indexp = nullptr);
-  MOZ_MUST_USE bool newSrcNote3(SrcNoteType type, ptrdiff_t offset1,
-                                ptrdiff_t offset2, unsigned* indexp = nullptr);
 
-  MOZ_MUST_USE bool setSrcNoteOffset(unsigned index, unsigned which,
-                                     BytecodeOffsetDiff offset);
+  MOZ_MUST_USE bool newSrcNoteOperand(ptrdiff_t operand);
 
   // Control whether emitTree emits a line number note.
   enum EmitLineNumberNote { EMIT_LINENOTE, SUPPRESS_LINENOTE };
 
   // Emit code for the tree rooted at pn.
   MOZ_MUST_USE bool emitTree(ParseNode* pn,
                              ValueUsage valueUsage = ValueUsage::WantValue,
                              EmitLineNumberNote emitLineNote = EMIT_LINENOTE,
--- a/js/src/frontend/BytecodeSection.h
+++ b/js/src/frontend/BytecodeSection.h
@@ -19,17 +19,17 @@
 
 #include "frontend/AbstractScopePtr.h"  // AbstractScopePtr
 #include "frontend/BytecodeOffset.h"    // BytecodeOffset
 #include "frontend/CompilationInfo.h"   // CompilationInfo
 #include "frontend/JumpList.h"          // JumpTarget
 #include "frontend/NameCollections.h"   // AtomIndexMap, PooledMapPtr
 #include "frontend/ObjLiteral.h"        // ObjLiteralCreationData
 #include "frontend/ParseNode.h"         // BigIntLiteral
-#include "frontend/SourceNotes.h"       // jssrcnote
+#include "frontend/SourceNotes.h"       // SrcNote
 #include "frontend/Stencil.h"           // Stencils
 #include "gc/Barrier.h"                 // GCPtrObject, GCPtrScope, GCPtrValue
 #include "gc/Rooting.h"                 // JS::Rooted
 #include "js/GCVariant.h"               // GCPolicy<mozilla::Variant>
 #include "js/GCVector.h"                // GCVector
 #include "js/TypeDecls.h"               // jsbytecode, JSContext
 #include "js/Value.h"                   // JS::Vector
 #include "js/Vector.h"                  // Vector
@@ -155,17 +155,17 @@ struct CGResumeOffsetList {
 };
 
 static constexpr size_t MaxBytecodeLength = INT32_MAX;
 static constexpr size_t MaxSrcNotesLength = INT32_MAX;
 
 // Have a few inline elements, so as to avoid heap allocation for tiny
 // sequences.  See bug 1390526.
 typedef Vector<jsbytecode, 64> BytecodeVector;
-typedef Vector<jssrcnote, 64> SrcNotesVector;
+typedef Vector<js::SrcNote, 64> SrcNotesVector;
 
 // Bytecode and all data directly associated with specific opcode/index inside
 // bytecode is stored in this class.
 class BytecodeSection {
  public:
   BytecodeSection(JSContext* cx, uint32_t lineNum);
 
   // ---- Bytecode ----
@@ -345,18 +345,18 @@ class BytecodeSection {
   // ---- Line and column ----
 
   // Line number for srcnotes.
   //
   // WARNING: If this becomes out of sync with already-emitted srcnotes,
   // we can get undefined behavior.
   uint32_t currentLine_;
 
-  // Zero-based column index on currentLine_ of last SRC_COLSPAN-annotated
-  // opcode.
+  // Zero-based column index on currentLine_ of last
+  // SrcNoteType::ColSpan-annotated opcode.
   //
   // WARNING: If this becomes out of sync with already-emitted srcnotes,
   // we can get undefined behavior.
   uint32_t lastColumn_ = 0;
 
   // The last code unit used for srcnotes.
   uint32_t lastSourceOffset_ = 0;
 
--- a/js/src/frontend/CForEmitter.cpp
+++ b/js/src/frontend/CForEmitter.cpp
@@ -3,17 +3,16 @@
  * 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/. */
 
 #include "frontend/CForEmitter.h"
 
 #include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
 #include "frontend/EmitterScope.h"     // EmitterScope
-#include "frontend/SourceNotes.h"      // SRC_*, SrcNote
 #include "vm/JSScript.h"               // JSTRY_LOOP
 #include "vm/Opcodes.h"                // JSOp
 #include "vm/Scope.h"                  // ScopeKind
 
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::Maybe;
--- a/js/src/frontend/Frontend2.cpp
+++ b/js/src/frontend/Frontend2.cpp
@@ -13,17 +13,17 @@
 #include <stddef.h>  // size_t
 #include <stdint.h>  // uint8_t, uint32_t
 
 #include "jsapi.h"
 
 #include "frontend/AbstractScopePtr.h"  // ScopeIndex
 #include "frontend/CompilationInfo.h"   // CompilationInfo
 #include "frontend/smoosh_generated.h"  // CVec, SmooshResult, SmooshCompileOptions, free_smoosh, run_smoosh
-#include "frontend/SourceNotes.h"  // jssrcnote
+#include "frontend/SourceNotes.h"  // SrcNote
 #include "frontend/Stencil.h"      // ScopeCreationData
 #include "gc/Rooting.h"            // RootedScriptSourceObject
 #include "js/HeapAPI.h"            // JS::GCCellPtr
 #include "js/RootingAPI.h"         // JS::Handle, JS::Rooted
 #include "js/TypeDecls.h"          // Rooted{Script,Value,String,Object}
 #include "vm/JSAtom.h"             // AtomizeUTF8Chars
 #include "vm/JSScript.h"           // JSScript
 #include "vm/Scope.h"              // BindingName
@@ -80,17 +80,17 @@ class SmooshScriptStencil : public Scrip
                                 js_script_str);
       return false;
     }
     immutableScriptData = ImmutableScriptData::new_(
         cx, result_.main_offset, nfixed, uint32_t(nslots64),
         result_.body_scope_index, result_.num_ic_entries, result_.num_type_sets,
         result_.is_function, /* funLength = */ 0,
         mozilla::MakeSpan(result_.bytecode.data, result_.bytecode.len),
-        mozilla::Span<const jssrcnote>(), mozilla::Span<const uint32_t>(),
+        mozilla::Span<const SrcNote>(), mozilla::Span<const uint32_t>(),
         scopeNotes, mozilla::Span<const TryNote>());
     if (!immutableScriptData) {
       return false;
     }
 
     strict = result_.strict;
     bindingsAccessedDynamically = result_.bindings_accessed_dynamically;
     hasCallSiteObj = result_.has_call_site_obj;
--- a/js/src/frontend/ObjectEmitter.cpp
+++ b/js/src/frontend/ObjectEmitter.cpp
@@ -6,17 +6,16 @@
 
 #include "frontend/ObjectEmitter.h"
 
 #include "mozilla/Assertions.h"  // MOZ_ASSERT
 
 #include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
 #include "frontend/IfEmitter.h"        // IfEmitter
 #include "frontend/SharedContext.h"    // SharedContext
-#include "frontend/SourceNotes.h"      // SRC_*
 #include "gc/AllocKind.h"              // AllocKind
 #include "js/Id.h"                     // jsid
 #include "js/Value.h"                  // UndefinedHandleValue
 #include "vm/BytecodeUtil.h"           // IsHiddenInitOp
 #include "vm/JSContext.h"              // JSContext
 #include "vm/NativeObject.h"           // NativeDefineDataProperty
 #include "vm/ObjectGroup.h"            // TenuredObject
 #include "vm/Opcodes.h"                // JSOp
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/SourceNotes.cpp
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/SourceNotes.h"
+
+const js::SrcNote::Spec js::SrcNote::specs_[] = {
+#define DEFINE_SRC_NOTE_SPEC(sym, name, arity) {name, arity},
+    FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_SPEC)
+#undef DEFINE_SRC_NOTE_SPEC
+};
--- a/js/src/frontend/SourceNotes.h
+++ b/js/src/frontend/SourceNotes.h
@@ -2,194 +2,419 @@
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef frontend_SourceNotes_h
 #define frontend_SourceNotes_h
 
-#include <stddef.h>  // ptrdiff_t
-#include <stdint.h>
+#include "mozilla/Assertions.h"  // MOZ_ASSERT
 
-#include "jstypes.h"
+#include <algorithm>  // std::min
+#include <stddef.h>   // ptrdiff_t, size_t
+#include <stdint.h>   // int8_t, uint8_t, uint32_t
 
-using jssrcnote = uint8_t;
+#include "jstypes.h"  // js::{Bit, BitMask}
 
 namespace js {
 
 /*
  * Source notes generated along with bytecode for decompiling and debugging.
  * A source note is a uint8_t with 4 bits of type and 4 of offset from the pc
  * of the previous note. If 4 bits of offset aren't enough, extended delta
- * notes (SRC_XDELTA) consisting of 1 set high order bit followed by 7 offset
+ * notes (XDelta) consisting of 1 set high order bit followed by 7 offset
  * bits are emitted before the next note. Some notes have operand offsets
  * encoded immediately after them, in note bytes or byte-triples.
  *
  *                 Source Note               Extended Delta
  *              +7-6-5-4+3-2-1-0+           +7+6-5-4-3-2-1-0+
  *              | type  | delta |           |1| ext-delta   |
  *              +-------+-------+           +-+-------------+
  *
- * At most one "gettable" note (i.e., a note of type other than SRC_NEWLINE,
- * SRC_COLSPAN, SRC_SETLINE, and SRC_XDELTA) applies to a given bytecode.
+ * At most one "gettable" note (i.e., a note of type other than NewLine,
+ * ColSpan, SetLine, and XDelta) applies to a given bytecode.
  *
- * NB: the js_SrcNoteSpec array in BytecodeEmitter.cpp is indexed by this
- * enum, so its initializers need to match the order here.
+ * NB: the js::SrcNote::specs_ array is indexed by this enum, so its
+ * initializers need to match the order here.
  */
 
+#define FOR_EACH_SRC_NOTE_TYPE(M)                                            \
+  /* Terminates a note vector. */                                            \
+  M(Null, "null", 0)                                                         \
+  /* += or another assign-op follows. */                                     \
+  M(AssignOp, "assignop", 0)                                                 \
+  /* All notes above here are "gettable".  See SrcNote::isGettable below. */ \
+  M(ColSpan, "colspan", int8_t(SrcNote::ColSpan::Operands::Count))           \
+  /* Bytecode follows a source newline. */                                   \
+  M(NewLine, "newline", 0)                                                   \
+  M(SetLine, "setline", int8_t(SrcNote::SetLine::Operands::Count))           \
+  /* Bytecode is a recommended breakpoint. */                                \
+  M(Breakpoint, "breakpoint", 0)                                             \
+  /* Bytecode is the first in a new steppable area. */                       \
+  M(StepSep, "step-sep", 0)                                                  \
+  M(Unused7, "unused", 0)                                                    \
+  /* 8-15 (0b1xxx) are for extended delta notes. */                          \
+  M(XDelta, "xdelta", 0)
+
+// Note: need to add a new source note? If there's no Unused* note left,
+// consider bumping SrcNoteType::XDelta to 12-15 and change
+// SrcNote::XDeltaBits from 7 to 6.
+
+enum class SrcNoteType : uint8_t {
+#define DEFINE_SRC_NOTE_TYPE(sym, name, arity) sym,
+  FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_TYPE)
+#undef DEFINE_SRC_NOTE_TYPE
+
+      Last,
+  LastGettable = AssignOp
+};
+
+static_assert(uint8_t(SrcNoteType::XDelta) == 8, "XDelta should be 8");
+
 class SrcNote {
+  struct Spec {
+    const char* name_;
+    int8_t arity_;
+  };
+
+  static const Spec specs_[];
+
+  static constexpr unsigned TypeBits = 4;
+  static constexpr unsigned DeltaBits = 4;
+  static constexpr unsigned XDeltaBits = 7;
+
+  static constexpr uint8_t TypeMask = js::BitMask(TypeBits) << DeltaBits;
+  static constexpr ptrdiff_t DeltaMask = js::BitMask(DeltaBits);
+  static constexpr ptrdiff_t XDeltaMask = js::BitMask(XDeltaBits);
+
+  static constexpr ptrdiff_t DeltaLimit = js::Bit(DeltaBits);
+  static constexpr ptrdiff_t XDeltaLimit = js::Bit(XDeltaBits);
+
+  static constexpr inline uint8_t toShiftedTypeBits(SrcNoteType type) {
+    return (uint8_t(type) << DeltaBits);
+  }
+
+  static inline uint8_t noteValue(SrcNoteType type, ptrdiff_t delta) {
+    MOZ_ASSERT((delta & DeltaMask) == delta);
+    return noteValueUnchecked(type, delta);
+  }
+
+  static constexpr inline uint8_t noteValueUnchecked(SrcNoteType type,
+                                                     ptrdiff_t delta) {
+    return toShiftedTypeBits(type) | (delta & DeltaMask);
+  }
+
+  static inline uint8_t xDeltaValue(ptrdiff_t delta) {
+    return toShiftedTypeBits(SrcNoteType::XDelta) | (delta & XDeltaMask);
+  }
+
+  uint8_t value_;
+
+  constexpr explicit SrcNote(uint8_t value) : value_(value) {}
+
  public:
-  // SRC_COLSPAN: Source note for arbitrary ops.
+  constexpr SrcNote() : value_(noteValueUnchecked(SrcNoteType::Null, 0)){};
+
+  SrcNote(const SrcNote& other) = default;
+  SrcNote& operator=(const SrcNote& other) = default;
+
+  SrcNote(SrcNote&& other) = default;
+  SrcNote& operator=(SrcNote&& other) = default;
+
+  static constexpr SrcNote terminator() { return SrcNote(); }
+
+ private:
+  inline uint8_t typeBits() const { return (value_ >> DeltaBits); }
+
+  inline bool isXDelta() const {
+    return typeBits() >= uint8_t(SrcNoteType::XDelta);
+  }
+
+  inline bool isFourBytesOperand() const {
+    return value_ & FourBytesOperandFlag;
+  }
+
+  // number of operands
+  inline unsigned arity() const {
+    MOZ_ASSERT(uint8_t(type()) < uint8_t(SrcNoteType::Last));
+    return specs_[uint8_t(type())].arity_;
+  }
+
+ public:
+  inline SrcNoteType type() const {
+    if (isXDelta()) {
+      return SrcNoteType::XDelta;
+    }
+    return SrcNoteType(typeBits());
+  }
+
+  // name for disassembly/debugging output
+  const char* name() const {
+    MOZ_ASSERT(uint8_t(type()) < uint8_t(SrcNoteType::Last));
+    return specs_[uint8_t(type())].name_;
+  }
+
+  inline bool isGettable() const {
+    return uint8_t(type()) <= uint8_t(SrcNoteType::LastGettable);
+  }
+
+  inline bool isTerminator() const {
+    return value_ == uint8_t(SrcNoteType::Null);
+  }
+
+  inline ptrdiff_t delta() const {
+    if (isXDelta()) {
+      return value_ & XDeltaMask;
+    }
+    return value_ & DeltaMask;
+  }
+
+ private:
+  /*
+   * Operand fields follow certain notes and are frequency-encoded: an operand
+   * in [0,0x7f] consumes one byte, an operand in [0x80,0x7fffffff] takes four,
+   * and the high bit of the first byte is set.
+   */
+  static constexpr unsigned FourBytesOperandFlag = 0x80;
+  static constexpr unsigned FourBytesOperandMask = 0x7f;
+
+  static constexpr unsigned OperandBits = 31;
+
+ public:
+  static constexpr size_t MaxOperand = (size_t(1) << OperandBits) - 1;
+
+  static inline bool isRepresentableOperand(ptrdiff_t operand) {
+    return 0 <= operand && size_t(operand) <= MaxOperand;
+  }
+
   class ColSpan {
    public:
-    enum Fields {
+    enum class Operands {
       // The column span (the diff between the column corresponds to the
       // current op and last known column).
       Span,
       Count
     };
+
+   private:
+    /*
+     * SrcNoteType::ColSpan values represent changes to the column number.
+     * Colspans are signed: negative changes arise in describing constructs like
+     * for(;;) loops, that generate code in non-source order. (Negative colspans
+     * also have a history of indicating bugs in updating ParseNodes' source
+     * locations.)
+     *
+     * We store colspans in operands. However, unlike normal operands, colspans
+     * are signed, so we truncate colspans (toOperand) for storage as
+     * operands, and sign-extend operands into colspans when we read them
+     * (fromOperand).
+     */
+    static constexpr ptrdiff_t ColSpanSignBit = 1 << (OperandBits - 1);
+    static constexpr ptrdiff_t MinColSpan = -ColSpanSignBit;
+    static constexpr ptrdiff_t MaxColSpan = ColSpanSignBit - 1;
+
+    static inline ptrdiff_t fromOperand(ptrdiff_t operand) {
+      // There should be no bits set outside the field we're going to
+      // sign-extend.
+      MOZ_ASSERT(!(operand & ~((1U << OperandBits) - 1)));
+
+      // Sign-extend the least significant OperandBits bits.
+      return (operand ^ ColSpanSignBit) - ColSpanSignBit;
+    }
+
+   public:
+    static inline bool isRepresentable(ptrdiff_t colspan) {
+      return MinColSpan <= colspan && colspan <= MaxColSpan;
+    }
+
+    static inline ptrdiff_t toOperand(ptrdiff_t colspan) {
+      // Truncate the two's complement colspan, for storage as an operand.
+      ptrdiff_t operand = colspan & ((1U << OperandBits) - 1);
+
+      // When we read this back, we'd better get the value we stored.
+      MOZ_ASSERT(fromOperand(operand) == colspan);
+      return operand;
+    }
+
+    static inline ptrdiff_t getSpan(const SrcNote* sn);
   };
-  // SRC_SETLINE: Source note for arbitrary ops.
+
   class SetLine {
    public:
-    enum Fields {
+    enum class Operands {
       // The file-absolute source line number of the current op.
       Line,
       Count
     };
+
+   private:
+    static inline size_t fromOperand(ptrdiff_t operand) {
+      return size_t(operand);
+    }
+
+   public:
+    static inline unsigned lengthFor(unsigned line) {
+      return 1 /* SetLine */ + (line > SrcNote::FourBytesOperandMask ? 4 : 1);
+    }
+
+    static inline ptrdiff_t toOperand(size_t line) { return ptrdiff_t(line); }
+
+    static inline size_t getLine(const SrcNote* sn);
   };
+
+  friend class SrcNoteWriter;
+  friend class SrcNoteReader;
+  friend class SrcNoteIterator;
 };
 
-// clang-format off
-#define FOR_EACH_SRC_NOTE_TYPE(M)                                                                  \
-    M(SRC_NULL,         "null",        0)  /* Terminates a note vector. */                         \
-    M(SRC_ASSIGNOP,     "assignop",    0)  /* += or another assign-op follows. */                  \
-    /* All notes above here are "gettable".  See SN_IS_GETTABLE below. */                          \
-    M(SRC_COLSPAN,      "colspan",     SrcNote::ColSpan::Count) \
-    M(SRC_NEWLINE,      "newline",     0)  /* Bytecode follows a source newline. */                \
-    M(SRC_SETLINE,      "setline",     SrcNote::SetLine::Count) \
-    M(SRC_BREAKPOINT,   "breakpoint",  0)  /* Bytecode is a recommended breakpoint. */             \
-    M(SRC_STEP_SEP,     "step-sep",    0)  /* Bytecode is the first in a new steppable area. */    \
-    M(SRC_UNUSED7,      "unused",      0) \
-    M(SRC_XDELTA,       "xdelta",      0)  /* 8-15 (0b1xxx) are for extended delta notes. */
-    // Note: need to add a new source note? If there's no SRC_UNUSED* note left,
-    // consider bumping SRC_XDELTA to 12-15 and change SN_XDELTA_BITS from 7 to 6.
-// clang-format on
+class SrcNoteWriter {
+ public:
+  // Write a source note with given `type`, and `delta` from the last source
+  // note. This writes the source note itself, and `XDelta`s if necessary.
+  //
+  // This doesn't write or allocate space for operands.
+  // If the source note is not nullary, the caller is responsible for calling
+  // `writeOperand` immediately after this.
+  //
+  // `allocator` is called with the number of bytes required to store the notes.
+  // `allocator` can be called multiple times for each source note.
+  // The last call corresponds to the source note for `type`.
+  template <typename T>
+  static bool writeNote(SrcNoteType type, ptrdiff_t delta, T allocator) {
+    while (delta >= SrcNote::DeltaLimit) {
+      ptrdiff_t xdelta = std::min(delta, SrcNote::XDeltaMask);
+      SrcNote* sn = allocator(1);
+      if (!sn) {
+        return false;
+      }
+      sn->value_ = SrcNote::xDeltaValue(xdelta);
+      delta -= xdelta;
+    }
 
-enum SrcNoteType {
-#define DEFINE_SRC_NOTE_TYPE(sym, name, arity) sym,
-  FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_TYPE)
-#undef DEFINE_SRC_NOTE_TYPE
+    SrcNote* sn = allocator(1);
+    sn->value_ = SrcNote::noteValue(type, delta);
+    return true;
+  }
 
-      SRC_LAST,
-  SRC_LAST_GETTABLE = SRC_ASSIGNOP
+  // Write source note operand.
+  //
+  // `allocator` is called with the number of bytes required to store the
+  // operand.  `allocator` is called only once.
+  template <typename T>
+  static bool writeOperand(ptrdiff_t operand, T allocator) {
+    if (operand > ptrdiff_t(SrcNote::FourBytesOperandMask)) {
+      SrcNote* sn = allocator(4);
+      if (!sn) {
+        return false;
+      }
+
+      sn[0].value_ = (SrcNote::FourBytesOperandFlag | (operand >> 24));
+      sn[1].value_ = operand >> 16;
+      sn[2].value_ = operand >> 8;
+      sn[3].value_ = operand;
+    } else {
+      SrcNote* sn = allocator(1);
+      if (!sn) {
+        return false;
+      }
+
+      sn[0].value_ = operand;
+    }
+
+    return true;
+  }
 };
 
-static_assert(SRC_XDELTA == 8, "SRC_XDELTA should be 8");
-
-/* A source note array is terminated by an all-zero element. */
-inline void SN_MAKE_TERMINATOR(jssrcnote* sn) { *sn = SRC_NULL; }
-
-inline bool SN_IS_TERMINATOR(jssrcnote* sn) { return *sn == SRC_NULL; }
-
-}  // namespace js
+class SrcNoteReader {
+  template <typename T>
+  static T getOperandHead(T sn, unsigned which) {
+    MOZ_ASSERT(sn->type() != SrcNoteType::XDelta);
+    MOZ_ASSERT(uint8_t(which) < sn->arity());
 
-#define SN_TYPE_BITS 4
-#define SN_DELTA_BITS 4
-#define SN_XDELTA_BITS 7
-
-#define SN_TYPE_MASK (js::BitMask(SN_TYPE_BITS) << SN_DELTA_BITS)
-#define SN_DELTA_MASK ((ptrdiff_t)js::BitMask(SN_DELTA_BITS))
-#define SN_XDELTA_MASK ((ptrdiff_t)js::BitMask(SN_XDELTA_BITS))
-
-#define SN_MAKE_NOTE(sn, t, d) \
-  (*(sn) = (jssrcnote)(((t) << SN_DELTA_BITS) | ((d)&SN_DELTA_MASK)))
-#define SN_MAKE_XDELTA(sn, d) \
-  (*(sn) = (jssrcnote)((SRC_XDELTA << SN_DELTA_BITS) | ((d)&SN_XDELTA_MASK)))
+    T curr = sn + 1;
+    for (; which; which--) {
+      if (curr->isFourBytesOperand()) {
+        curr += 4;
+      } else {
+        curr++;
+      }
+    }
+    return curr;
+  }
 
-#define SN_IS_XDELTA(sn) ((*(sn) >> SN_DELTA_BITS) >= SRC_XDELTA)
-#define SN_TYPE(sn) \
-  ((js::SrcNoteType)(SN_IS_XDELTA(sn) ? SRC_XDELTA : *(sn) >> SN_DELTA_BITS))
-#define SN_SET_TYPE(sn, type) SN_MAKE_NOTE(sn, type, SN_DELTA(sn))
-#define SN_IS_GETTABLE(sn) (SN_TYPE(sn) <= SRC_LAST_GETTABLE)
+ public:
+  // Return the operand of source note `sn`, specified by `which`.
+  static ptrdiff_t getOperand(const SrcNote* sn, unsigned which) {
+    const SrcNote* head = getOperandHead(sn, which);
 
-#define SN_DELTA(sn) \
-  ((ptrdiff_t)(SN_IS_XDELTA(sn) ? *(sn)&SN_XDELTA_MASK : *(sn)&SN_DELTA_MASK))
-#define SN_SET_DELTA(sn, delta)                 \
-  (SN_IS_XDELTA(sn) ? SN_MAKE_XDELTA(sn, delta) \
-                    : SN_MAKE_NOTE(sn, SN_TYPE(sn), delta))
+    if (head->isFourBytesOperand()) {
+      return ptrdiff_t(
+          (uint32_t(head[0].value_ & SrcNote::FourBytesOperandMask) << 24) |
+          (uint32_t(head[1].value_) << 16) | (uint32_t(head[2].value_) << 8) |
+          uint32_t(head[3].value_));
+    }
 
-#define SN_DELTA_LIMIT ((ptrdiff_t)js::Bit(SN_DELTA_BITS))
-#define SN_XDELTA_LIMIT ((ptrdiff_t)js::Bit(SN_XDELTA_BITS))
+    return ptrdiff_t(head[0].value_);
+  }
+};
 
-/*
- * Offset fields follow certain notes and are frequency-encoded: an offset in
- * [0,0x7f] consumes one byte, an offset in [0x80,0x7fffffff] takes four, and
- * the high bit of the first byte is set.
- */
-#define SN_4BYTE_OFFSET_FLAG 0x80
-#define SN_4BYTE_OFFSET_MASK 0x7f
+/* static */
+inline ptrdiff_t SrcNote::ColSpan::getSpan(const SrcNote* sn) {
+  return fromOperand(SrcNoteReader::getOperand(sn, unsigned(Operands::Span)));
+}
 
-#define SN_OFFSET_BITS 31
-#define SN_MAX_OFFSET (((size_t)1 << SN_OFFSET_BITS) - 1)
-
-inline bool SN_REPRESENTABLE_OFFSET(ptrdiff_t offset) {
-  return 0 <= offset && size_t(offset) <= SN_MAX_OFFSET;
+/* static */
+inline size_t SrcNote::SetLine::getLine(const SrcNote* sn) {
+  return fromOperand(SrcNoteReader::getOperand(sn, unsigned(Operands::Line)));
 }
 
-/*
- * SRC_COLSPAN values represent changes to the column number. Colspans are
- * signed: negative changes arise in describing constructs like for(;;) loops,
- * that generate code in non-source order. (Negative colspans also have a
- * history of indicating bugs in updating ParseNodes' source locations.)
- *
- * We store colspans using the same variable-length encoding as offsets,
- * described above. However, unlike offsets, colspans are signed, so we truncate
- * colspans (SN_COLSPAN_TO_OFFSET) for storage as offsets, and sign-extend
- * offsets into colspans when we read them (SN_OFFSET_TO_COLSPAN).
- */
-#define SN_COLSPAN_SIGN_BIT (1 << (SN_OFFSET_BITS - 1))
-#define SN_MIN_COLSPAN (-SN_COLSPAN_SIGN_BIT)
-#define SN_MAX_COLSPAN (SN_COLSPAN_SIGN_BIT - 1)
+// Iterate over SrcNote array, until it hits terminator.
+//
+// Usage:
+//   for (SrcNoteIterator iter(notes); !iter.atEnd(); ++iter) {
+//     auto sn = *iter; // `sn` is `const SrcNote*` typed.
+//     ...
+//   }
+class SrcNoteIterator {
+  const SrcNote* current_;
 
-inline bool SN_REPRESENTABLE_COLSPAN(ptrdiff_t colspan) {
-  return SN_MIN_COLSPAN <= colspan && colspan <= SN_MAX_COLSPAN;
-}
+  void next() {
+    unsigned arity = current_->arity();
+    current_++;
 
-inline ptrdiff_t SN_OFFSET_TO_COLSPAN(ptrdiff_t offset) {
-  // There should be no bits set outside the field we're going to sign-extend.
-  MOZ_ASSERT(!(offset & ~((1U << SN_OFFSET_BITS) - 1)));
-  // Sign-extend the least significant SN_OFFSET_BITS bits.
-  return (offset ^ SN_COLSPAN_SIGN_BIT) - SN_COLSPAN_SIGN_BIT;
-}
+    for (; arity; arity--) {
+      if (current_->isFourBytesOperand()) {
+        current_ += 4;
+      } else {
+        current_++;
+      }
+    }
+  }
 
-inline ptrdiff_t SN_COLSPAN_TO_OFFSET(ptrdiff_t colspan) {
-  // Truncate the two's complement colspan, for storage as an offset.
-  ptrdiff_t offset = colspan & ((1U << SN_OFFSET_BITS) - 1);
-  // When we read this back, we'd better get the value we stored.
-  MOZ_ASSERT(SN_OFFSET_TO_COLSPAN(offset) == colspan);
-  return offset;
-}
+ public:
+  SrcNoteIterator() = delete;
 
-#define SN_LENGTH(sn) \
-  ((js_SrcNoteSpec[SN_TYPE(sn)].arity == 0) ? 1 : js::SrcNoteLength(sn))
-#define SN_NEXT(sn) ((sn) + SN_LENGTH(sn))
+  SrcNoteIterator(const SrcNoteIterator& other) = delete;
+  SrcNoteIterator& operator=(const SrcNoteIterator& other) = delete;
+
+  SrcNoteIterator(SrcNoteIterator&& other) = default;
+  SrcNoteIterator& operator=(SrcNoteIterator&& other) = default;
+
+  explicit SrcNoteIterator(const SrcNote* sn) : current_(sn) {}
 
-struct JSSrcNoteSpec {
-  const char* name; /* name for disassembly/debugging output */
-  int8_t arity;     /* number of offset operands */
-};
+  bool atEnd() const { return current_->isTerminator(); }
 
-extern JS_FRIEND_DATA const JSSrcNoteSpec js_SrcNoteSpec[];
+  const SrcNote* operator*() const { return current_; }
 
-namespace js {
-
-extern JS_FRIEND_API unsigned SrcNoteLength(jssrcnote* sn);
+  // Pre-increment
+  SrcNoteIterator& operator++() {
+    next();
+    return *this;
+  }
 
-/*
- * Get and set the offset operand identified by which (0 for the first, etc.).
- */
-extern JS_FRIEND_API ptrdiff_t GetSrcNoteOffset(jssrcnote* sn, unsigned which);
+  // Post-increment
+  SrcNoteIterator operator++(int) = delete;
+};
 
 }  // namespace js
 
 #endif /* frontend_SourceNotes_h */
--- a/js/src/frontend/TryEmitter.cpp
+++ b/js/src/frontend/TryEmitter.cpp
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "frontend/TryEmitter.h"
 
 #include "mozilla/Assertions.h"  // MOZ_ASSERT
 
 #include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
 #include "frontend/SharedContext.h"    // StatementKind
-#include "frontend/SourceNotes.h"      // SrcNote, SRC_*
 #include "vm/JSScript.h"               // JSTRY_CATCH, JSTRY_FINALLY
 #include "vm/Opcodes.h"                // JSOp
 
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::Maybe;
 
--- a/js/src/frontend/moz.build
+++ b/js/src/frontend/moz.build
@@ -56,16 +56,17 @@ UNIFIED_SOURCES += [
     'ObjectEmitter.cpp',
     'ObjLiteral.cpp',
     'OptionalEmitter.cpp',
     'ParseContext.cpp',
     'ParseNode.cpp',
     'ParseNodeVerify.cpp',
     'PropOpEmitter.cpp',
     'SharedContext.cpp',
+    'SourceNotes.cpp',
     'Stencil.cpp',
     'SwitchEmitter.cpp',
     'TDZCheckCache.cpp',
     'TokenStream.cpp',
     'TryEmitter.cpp',
     'WhileEmitter.cpp',
 ]
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -76,16 +76,17 @@
 #  include "frontend/BinASTParser.h"
 #endif  // defined(JS_BUILD_BINAST)
 #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"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/InlinableNatives.h"
 #include "jit/Ion.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitRealm.h"
 #include "jit/shared/CodeGenerator-shared.h"
 #include "js/Array.h"        // JS::NewArrayObject
@@ -3007,51 +3008,52 @@ static MOZ_MUST_USE bool SrcNotes(JSCont
                     "desc", "args") ||
       !sp->put("---- ---- ----- ------ -------- ------\n")) {
     return false;
   }
 
   unsigned offset = 0;
   unsigned colspan = 0;
   unsigned lineno = script->lineno();
-  jssrcnote* notes = script->notes();
-  for (jssrcnote* sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
-    unsigned delta = SN_DELTA(sn);
+  SrcNote* notes = script->notes();
+  for (SrcNoteIterator iter(notes); !iter.atEnd(); ++iter) {
+    auto sn = *iter;
+
+    unsigned delta = sn->delta();
     offset += delta;
-    SrcNoteType type = SN_TYPE(sn);
-    const char* name = js_SrcNoteSpec[type].name;
+    SrcNoteType type = sn->type();
+    const char* name = sn->name();
     if (!sp->jsprintf("%3u: %4u %5u [%4u] %-8s", unsigned(sn - notes), lineno,
                       offset, delta, name)) {
       return false;
     }
 
     switch (type) {
-      case SRC_NULL:
-      case SRC_ASSIGNOP:
-      case SRC_BREAKPOINT:
-      case SRC_STEP_SEP:
-      case SRC_XDELTA:
+      case SrcNoteType::Null:
+      case SrcNoteType::AssignOp:
+      case SrcNoteType::Breakpoint:
+      case SrcNoteType::StepSep:
+      case SrcNoteType::XDelta:
         break;
 
-      case SRC_COLSPAN:
-        colspan =
-            SN_OFFSET_TO_COLSPAN(GetSrcNoteOffset(sn, SrcNote::ColSpan::Span));
+      case SrcNoteType::ColSpan:
+        colspan = SrcNote::ColSpan::getSpan(sn);
         if (!sp->jsprintf("%d", colspan)) {
           return false;
         }
         break;
 
-      case SRC_SETLINE:
-        lineno = GetSrcNoteOffset(sn, SrcNote::SetLine::Line);
+      case SrcNoteType::SetLine:
+        lineno = SrcNote::SetLine::getLine(sn);
         if (!sp->jsprintf(" lineno %u", lineno)) {
           return false;
         }
         break;
 
-      case SRC_NEWLINE:
+      case SrcNoteType::NewLine:
         ++lineno;
         break;
 
       default:
         MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
     }
     if (!sp->put("\n")) {
       return false;
--- a/js/src/vm/BytecodeUtil-inl.h
+++ b/js/src/vm/BytecodeUtil-inl.h
@@ -4,17 +4,17 @@
  * 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_BytecodeUtil_inl_h
 #define vm_BytecodeUtil_inl_h
 
 #include "vm/BytecodeUtil.h"
 
-#include "frontend/SourceNotes.h"
+#include "frontend/SourceNotes.h"  // SrcNote, SrcNoteType, SrcNoteIterator
 #include "vm/JSScript.h"
 
 namespace js {
 
 static inline unsigned GetDefCount(jsbytecode* pc) {
   /*
    * Add an extra pushed value for Or/And opcodes, so that they are included
    * in the pushed array of stack values for type inference.
@@ -117,18 +117,18 @@ class BytecodeRangeWithPosition : privat
         lineno(script->lineno()),
         column(0),
         sn(script->notes()),
         snpc(script->code()),
         isEntryPoint(false),
         isBreakpoint(false),
         seenStepSeparator(false),
         wasArtifactEntryPoint(false) {
-    if (!SN_IS_TERMINATOR(sn)) {
-      snpc += SN_DELTA(sn);
+    if (!sn->isTerminator()) {
+      snpc += sn->delta();
     }
     updatePosition();
     while (frontPC() != script->main()) {
       popFront();
     }
 
     if (frontOpcode() != JSOp::JumpTarget) {
       isEntryPoint = true;
@@ -170,65 +170,67 @@ class BytecodeRangeWithPosition : privat
   // debugger exactly which offsets represent "interesting" (to the
   // user) places to stop.
   bool frontIsEntryPoint() const { return isEntryPoint; }
 
   // Breakable points are explicitly marked by the emitter as locations where
   // the debugger may want to allow users to pause.
   bool frontIsBreakablePoint() const { return isBreakpoint; }
 
-  // Breakable step points are the first breakable point after a SRC_STEP_SEP
-  // note has been encountered.
+  // Breakable step points are the first breakable point after a
+  // SrcNote::StepSep note has been encountered.
   bool frontIsBreakableStepPoint() const {
     return isBreakpoint && seenStepSeparator;
   }
 
  private:
   void updatePosition() {
     if (isBreakpoint) {
       isBreakpoint = false;
       seenStepSeparator = false;
     }
 
     // Determine the current line number by reading all source notes up to
     // and including the current offset.
     jsbytecode* lastLinePC = nullptr;
-    while (!SN_IS_TERMINATOR(sn) && snpc <= frontPC()) {
-      SrcNoteType type = SN_TYPE(sn);
-      if (type == SRC_COLSPAN) {
-        ptrdiff_t colspan =
-            SN_OFFSET_TO_COLSPAN(GetSrcNoteOffset(sn, SrcNote::ColSpan::Span));
+    SrcNoteIterator iter(sn);
+    for (; !iter.atEnd() && snpc <= frontPC();
+         ++iter, snpc += (*iter)->delta()) {
+      auto sn = *iter;
+
+      SrcNoteType type = sn->type();
+      if (type == SrcNoteType::ColSpan) {
+        ptrdiff_t colspan = SrcNote::ColSpan::getSpan(sn);
         MOZ_ASSERT(ptrdiff_t(column) + colspan >= 0);
         column += colspan;
         lastLinePC = snpc;
-      } else if (type == SRC_SETLINE) {
-        lineno = size_t(GetSrcNoteOffset(sn, SrcNote::SetLine::Line));
+      } else if (type == SrcNoteType::SetLine) {
+        lineno = SrcNote::SetLine::getLine(sn);
         column = 0;
         lastLinePC = snpc;
-      } else if (type == SRC_NEWLINE) {
+      } else if (type == SrcNoteType::NewLine) {
         lineno++;
         column = 0;
         lastLinePC = snpc;
-      } else if (type == SRC_BREAKPOINT) {
+      } else if (type == SrcNoteType::Breakpoint) {
         isBreakpoint = true;
         lastLinePC = snpc;
-      } else if (type == SRC_STEP_SEP) {
+      } else if (type == SrcNoteType::StepSep) {
         seenStepSeparator = true;
         lastLinePC = snpc;
       }
+    }
 
-      sn = SN_NEXT(sn);
-      snpc += SN_DELTA(sn);
-    }
+    sn = *iter;
     isEntryPoint = lastLinePC == frontPC();
   }
 
   size_t lineno;
   size_t column;
-  jssrcnote* sn;
+  const SrcNote* sn;
   jsbytecode* snpc;
   bool isEntryPoint;
   bool isBreakpoint;
   bool seenStepSeparator;
   bool wasArtifactEntryPoint;
 };
 
 }  // namespace js
--- a/js/src/vm/BytecodeUtil.cpp
+++ b/js/src/vm/BytecodeUtil.cpp
@@ -24,17 +24,17 @@
 #include <string.h>
 #include <type_traits>
 
 #include "jsapi.h"
 #include "jsnum.h"
 #include "jstypes.h"
 
 #include "frontend/BytecodeCompiler.h"
-#include "frontend/SourceNotes.h"
+#include "frontend/SourceNotes.h"  // SrcNote, SrcNoteType, SrcNoteIterator
 #include "gc/PublicIterators.h"
 #include "js/CharacterEncoding.h"
 #include "js/Printf.h"
 #include "js/Symbol.h"
 #include "util/Memory.h"
 #include "util/StringBuffer.h"
 #include "util/Text.h"
 #include "vm/BytecodeLocation.h"
@@ -1062,28 +1062,32 @@ static MOZ_MUST_USE bool DisassembleAtPC
       }
     }
     if (pc != nullptr) {
       if (!sp->put(pc == next ? "--> " : "    ")) {
         return false;
       }
     }
     if (showAll) {
-      jssrcnote* sn = GetSrcNote(cx, script, next);
+      const SrcNote* sn = GetSrcNote(cx, script, next);
       if (sn) {
-        MOZ_ASSERT(!SN_IS_TERMINATOR(sn));
-        jssrcnote* next = SN_NEXT(sn);
-        while (!SN_IS_TERMINATOR(next) && SN_DELTA(next) == 0) {
-          if (!sp->jsprintf("%02u\n    ", SN_TYPE(sn))) {
+        MOZ_ASSERT(!sn->isTerminator());
+        SrcNoteIterator iter(sn);
+        while (true) {
+          ++iter;
+          auto next = *iter;
+          if (!(!next->isTerminator() && next->delta() == 0)) {
+            break;
+          }
+          if (!sp->jsprintf("%s\n    ", sn->name())) {
             return false;
           }
-          sn = next;
-          next = SN_NEXT(sn);
+          sn = *iter;
         }
-        if (!sp->jsprintf("%02u ", SN_TYPE(sn))) {
+        if (!sp->jsprintf("%s ", sn->name())) {
           return false;
         }
       } else {
         if (!sp->put("   ")) {
           return false;
         }
       }
       if (parser && parser->isReachable(next)) {
@@ -1737,18 +1741,18 @@ bool ExpressionDecompiler::decompilePC(j
 
   if (const char* token = CodeToken[uint8_t(op)]) {
     MOZ_ASSERT(defIndex == 0);
     MOZ_ASSERT(CodeSpec(op).ndefs == 1);
 
     // Handle simple cases of binary and unary operators.
     switch (CodeSpec(op).nuses) {
       case 2: {
-        jssrcnote* sn = GetSrcNote(cx, script, pc);
-        if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP) {
+        const SrcNote* sn = GetSrcNote(cx, script, pc);
+        if (!sn || sn->type() != SrcNoteType::AssignOp) {
           return write("(") && decompilePCForStackOperand(pc, -2) &&
                  write(" ") && write(token) && write(" ") &&
                  decompilePCForStackOperand(pc, -1) && write(")");
         }
         break;
       }
       case 1:
         return write("(") && write(token) &&
--- a/js/src/vm/BytecodeUtil.h
+++ b/js/src/vm/BytecodeUtil.h
@@ -204,17 +204,17 @@ static const unsigned UINT32_INDEX_LEN =
 static MOZ_ALWAYS_INLINE uint32_t GET_UINT32_INDEX(const jsbytecode* pc) {
   return GET_UINT32(pc);
 }
 
 static MOZ_ALWAYS_INLINE void SET_UINT32_INDEX(jsbytecode* pc, uint32_t index) {
   SET_UINT32(pc, index);
 }
 
-// Index limit is determined by SN_4BYTE_OFFSET_FLAG, see
+// Index limit is determined by SrcNote::FourByteOffsetFlag, see
 // frontend/BytecodeEmitter.h.
 static const unsigned INDEX_LIMIT_LOG2 = 31;
 static const uint32_t INDEX_LIMIT = uint32_t(1) << INDEX_LIMIT_LOG2;
 
 static inline jsbytecode ARGC_HI(uint16_t argc) { return UINT16_HI(argc); }
 
 static inline jsbytecode ARGC_LO(uint16_t argc) { return UINT16_LO(argc); }
 
--- a/js/src/vm/Caches.h
+++ b/js/src/vm/Caches.h
@@ -6,17 +6,17 @@
 
 #ifndef vm_Caches_h
 #define vm_Caches_h
 
 #include <new>
 
 #include "jsmath.h"
 
-#include "frontend/SourceNotes.h"
+#include "frontend/SourceNotes.h"  // SrcNote
 #include "gc/Tracer.h"
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 #include "js/UniquePtr.h"
 #include "util/Memory.h"
 #include "vm/ArrayObject.h"
 #include "vm/JSAtom.h"
 #include "vm/JSObject.h"
@@ -27,17 +27,17 @@ namespace js {
 
 /*
  * GetSrcNote cache to avoid O(n^2) growth in finding a source note for a
  * given pc in a script. We use the script->code pointer to tag the cache,
  * instead of the script address itself, so that source notes are always found
  * by offset from the bytecode with which they were generated.
  */
 struct GSNCache {
-  typedef HashMap<jsbytecode*, jssrcnote*, PointerHasher<jsbytecode*>,
+  typedef HashMap<jsbytecode*, const SrcNote*, PointerHasher<jsbytecode*>,
                   SystemAllocPolicy>
       Map;
 
   jsbytecode* code;
   Map map;
 
   GSNCache() : code(nullptr) {}
 
--- a/js/src/vm/CodeCoverage.cpp
+++ b/js/src/vm/CodeCoverage.cpp
@@ -13,16 +13,17 @@
 #include <utility>
 #ifdef XP_WIN
 #  include <process.h>
 #  define getpid _getpid
 #else
 #  include <unistd.h>
 #endif
 
+#include "frontend/SourceNotes.h"  // SrcNote, SrcNoteType, SrcNoteIterator
 #include "gc/Zone.h"
 #include "util/Text.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/JSScript.h"
 #include "vm/Realm.h"
 #include "vm/Runtime.h"
 #include "vm/Time.h"
 
@@ -139,19 +140,19 @@ void LCovSource::writeScript(JSScript* s
     outFNDA_.printf("FNDA:%" PRIu64 ",%s\n", counts->numExec(), scriptName);
 
     // Set the hit count of the pre-main code to 1, if the function ever got
     // visited.
     hits = 1;
   }
 
   jsbytecode* snpc = script->code();
-  jssrcnote* sn = script->notes();
-  if (!SN_IS_TERMINATOR(sn)) {
-    snpc += SN_DELTA(sn);
+  const SrcNote* sn = script->notes();
+  if (!sn->isTerminator()) {
+    snpc += sn->delta();
   }
 
   size_t lineno = script->lineno();
   jsbytecode* end = script->codeEnd();
   size_t branchId = 0;
   bool firstLineHasBeenWritten = false;
   for (jsbytecode* pc = script->code(); pc != end; pc = GetNextPc(pc)) {
     MOZ_ASSERT(script->code() <= pc && pc < end);
@@ -167,27 +168,29 @@ void LCovSource::writeScript(JSScript* s
         hits = counts->numExec();
       }
     }
 
     // If we have additional source notes, walk all the source notes of the
     // current pc.
     if (snpc <= pc || !firstLineHasBeenWritten) {
       size_t oldLine = lineno;
-      while (!SN_IS_TERMINATOR(sn) && snpc <= pc) {
-        SrcNoteType type = SN_TYPE(sn);
-        if (type == SRC_SETLINE) {
-          lineno = size_t(GetSrcNoteOffset(sn, SrcNote::SetLine::Line));
-        } else if (type == SRC_NEWLINE) {
+      SrcNoteIterator iter(sn);
+      while (!iter.atEnd() && snpc <= pc) {
+        sn = *iter;
+        SrcNoteType type = sn->type();
+        if (type == SrcNoteType::SetLine) {
+          lineno = SrcNote::SetLine::getLine(sn);
+        } else if (type == SrcNoteType::NewLine) {
           lineno++;
         }
-
-        sn = SN_NEXT(sn);
-        snpc += SN_DELTA(sn);
+        ++iter;
+        snpc += (*iter)->delta();
       }
+      sn = *iter;
 
       if ((oldLine != lineno || !firstLineHasBeenWritten) &&
           pc >= script->main() && fallsthrough) {
         auto p = linesHit_.lookupForAdd(lineno);
         if (!p) {
           if (!linesHit_.add(p, lineno, hits)) {
             hadOOM_ = true;
             return;
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -30,16 +30,17 @@
 #include "jsapi.h"
 #include "jstypes.h"
 
 #include "frontend/BinASTRuntimeSupport.h"  // BinASTSourceMetadata{,Multipart,Context}
 #include "frontend/BinASTTokenReaderContext.h"  // HuffmanDictionaryForMetadata,AutoClearHuffmanDictionaryForMetadata
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/BytecodeEmitter.h"
 #include "frontend/SharedContext.h"
+#include "frontend/SourceNotes.h"  // SrcNote, SrcNoteType, SrcNoteIterator
 #include "gc/FreeOp.h"
 #include "jit/BaselineJIT.h"
 #include "jit/Ion.h"
 #include "jit/IonCode.h"
 #include "jit/JitOptions.h"
 #include "jit/JitRealm.h"
 #include "js/CompileOptions.h"
 #include "js/MemoryMetrics.h"
@@ -728,17 +729,17 @@ XDRResult js::PrivateScriptData::XDR(XDR
 
 /* static */ size_t ImmutableScriptData::AllocationSize(
     uint32_t codeLength, uint32_t noteLength, uint32_t numResumeOffsets,
     uint32_t numScopeNotes, uint32_t numTryNotes) {
   size_t size = sizeof(ImmutableScriptData);
 
   size += sizeof(Flags);
   size += codeLength * sizeof(jsbytecode);
-  size += noteLength * sizeof(jssrcnote);
+  size += noteLength * sizeof(SrcNote);
 
 #ifdef DEBUG
   // The compact arrays need to maintain uint32_t alignment. This should have
   // been done by padding out source notes.
   MOZ_ASSERT(size % sizeof(uint32_t) == 0,
              "Source notes should have been padded already");
 #endif
 
@@ -852,20 +853,20 @@ ImmutableScriptData::ImmutableScriptData
     new (offsetToPointer<void>(cursor)) Flags{};
     cursor += sizeof(Flags);
 
     static_assert(alignof(Flags) >= alignof(jsbytecode),
                   "Incompatible alignment");
     initElements<jsbytecode>(cursor, codeLength);
     cursor += codeLength * sizeof(jsbytecode);
 
-    static_assert(alignof(jsbytecode) >= alignof(jssrcnote),
+    static_assert(alignof(jsbytecode) >= alignof(SrcNote),
                   "Incompatible alignment");
-    initElements<jssrcnote>(cursor, noteLength);
-    cursor += noteLength * sizeof(jssrcnote);
+    initElements<SrcNote>(cursor, noteLength);
+    cursor += noteLength * sizeof(SrcNote);
 
     MOZ_ASSERT(cursor % CodeNoteAlign == 0);
   }
 
   // Initialization for remaining arrays.
   initOptionalArrays(&cursor, &flagsRef(), numResumeOffsets, numScopeNotes,
                      numTryNotes);
 
@@ -918,20 +919,20 @@ XDRResult ImmutableScriptData::XDR(XDRSt
   MOZ_TRY(xdr->codeUint32(&isd->nfixed));
   MOZ_TRY(xdr->codeUint32(&isd->nslots));
   MOZ_TRY(xdr->codeUint32(&isd->bodyScopeIndex));
   MOZ_TRY(xdr->codeUint32(&isd->numICEntries));
   MOZ_TRY(xdr->codeUint16(&isd->funLength));
   MOZ_TRY(xdr->codeUint16(&isd->numBytecodeTypeSets));
 
   static_assert(sizeof(jsbytecode) == 1);
-  static_assert(sizeof(jssrcnote) == 1);
+  static_assert(sizeof(SrcNote) == 1);
 
   jsbytecode* code = isd->code();
-  jssrcnote* notes = isd->notes();
+  SrcNote* notes = isd->notes();
   MOZ_TRY(xdr->codeBytes(code, codeLength));
   MOZ_TRY(xdr->codeBytes(notes, noteLength));
 
   for (uint32_t& elem : isd->resumeOffsets()) {
     MOZ_TRY(xdr->codeUint32(&elem));
   }
 
   for (ScopeNote& elem : isd->scopeNotes()) {
@@ -4562,102 +4563,105 @@ js::GlobalObject& JSScript::uninlinedGlo
 
 static const uint32_t GSN_CACHE_THRESHOLD = 100;
 
 void GSNCache::purge() {
   code = nullptr;
   map.clearAndCompact();
 }
 
-jssrcnote* js::GetSrcNote(GSNCache& cache, JSScript* script, jsbytecode* pc) {
+const js::SrcNote* js::GetSrcNote(GSNCache& cache, JSScript* script,
+                                  jsbytecode* pc) {
   size_t target = pc - script->code();
   if (target >= script->length()) {
     return nullptr;
   }
 
   if (cache.code == script->code()) {
     GSNCache::Map::Ptr p = cache.map.lookup(pc);
     return p ? p->value() : nullptr;
   }
 
   size_t offset = 0;
-  jssrcnote* result;
-  for (jssrcnote* sn = script->notes();; sn = SN_NEXT(sn)) {
-    if (SN_IS_TERMINATOR(sn)) {
+  const js::SrcNote* result;
+  for (SrcNoteIterator iter(script->notes());; ++iter) {
+    auto sn = *iter;
+    if (sn->isTerminator()) {
       result = nullptr;
       break;
     }
-    offset += SN_DELTA(sn);
-    if (offset == target && SN_IS_GETTABLE(sn)) {
+    offset += sn->delta();
+    if (offset == target && sn->isGettable()) {
       result = sn;
       break;
     }
   }
 
   if (cache.code != script->code() && script->length() >= GSN_CACHE_THRESHOLD) {
     unsigned nsrcnotes = 0;
-    for (jssrcnote* sn = script->notes(); !SN_IS_TERMINATOR(sn);
-         sn = SN_NEXT(sn)) {
-      if (SN_IS_GETTABLE(sn)) {
+    for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) {
+      auto sn = *iter;
+      if (sn->isGettable()) {
         ++nsrcnotes;
       }
     }
     if (cache.code) {
       cache.map.clear();
       cache.code = nullptr;
     }
     if (cache.map.reserve(nsrcnotes)) {
       pc = script->code();
-      for (jssrcnote* sn = script->notes(); !SN_IS_TERMINATOR(sn);
-           sn = SN_NEXT(sn)) {
-        pc += SN_DELTA(sn);
-        if (SN_IS_GETTABLE(sn)) {
+      for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) {
+        auto sn = *iter;
+        pc += sn->delta();
+        if (sn->isGettable()) {
           cache.map.putNewInfallible(pc, sn);
         }
       }
       cache.code = script->code();
     }
   }
 
   return result;
 }
 
-jssrcnote* js::GetSrcNote(JSContext* cx, JSScript* script, jsbytecode* pc) {
+const js::SrcNote* js::GetSrcNote(JSContext* cx, JSScript* script,
+                                  jsbytecode* pc) {
   return GetSrcNote(cx->caches().gsnCache, script, pc);
 }
 
-unsigned js::PCToLineNumber(unsigned startLine, jssrcnote* notes,
+unsigned js::PCToLineNumber(unsigned startLine, SrcNote* notes,
                             jsbytecode* code, jsbytecode* pc,
                             unsigned* columnp) {
   unsigned lineno = startLine;
   unsigned column = 0;
 
   /*
    * Walk through source notes accumulating their deltas, keeping track of
    * line-number notes, until we pass the note for pc's offset within
    * script->code.
    */
   ptrdiff_t offset = 0;
   ptrdiff_t target = pc - code;
-  for (jssrcnote* sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
-    offset += SN_DELTA(sn);
+  for (SrcNoteIterator iter(notes); !iter.atEnd(); ++iter) {
+    auto sn = *iter;
+    offset += sn->delta();
     if (offset > target) {
       break;
     }
 
-    SrcNoteType type = SN_TYPE(sn);
-    if (type == SRC_SETLINE) {
-      lineno = unsigned(GetSrcNoteOffset(sn, SrcNote::SetLine::Line));
+    SrcNoteType type = sn->type();
+    if (type == SrcNoteType::SetLine) {
+      lineno = SrcNote::SetLine::getLine(sn);
       column = 0;
-    } else if (type == SRC_NEWLINE) {
+    } else if (type == SrcNoteType::NewLine) {
       lineno++;
       column = 0;
-    } else if (type == SRC_COLSPAN) {
-      ptrdiff_t colspan =
-          SN_OFFSET_TO_COLSPAN(GetSrcNoteOffset(sn, SrcNote::ColSpan::Span));
+    } else if (type == SrcNoteType::ColSpan) {
+      ptrdiff_t colspan = SrcNote::ColSpan::getSpan(sn);
       MOZ_ASSERT(ptrdiff_t(column) + colspan >= 0);
       column += colspan;
     }
   }
 
   if (columnp) {
     *columnp = column;
   }
@@ -4675,57 +4679,57 @@ unsigned js::PCToLineNumber(JSScript* sc
   return PCToLineNumber(script->lineno(), script->notes(), script->code(), pc,
                         columnp);
 }
 
 jsbytecode* js::LineNumberToPC(JSScript* script, unsigned target) {
   ptrdiff_t offset = 0;
   ptrdiff_t best = -1;
   unsigned lineno = script->lineno();
-  unsigned bestdiff = SN_MAX_OFFSET;
-  for (jssrcnote* sn = script->notes(); !SN_IS_TERMINATOR(sn);
-       sn = SN_NEXT(sn)) {
+  unsigned bestdiff = SrcNote::MaxOperand;
+  for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) {
+    auto sn = *iter;
     /*
      * Exact-match only if offset is not in the prologue; otherwise use
      * nearest greater-or-equal line number match.
      */
     if (lineno == target && offset >= ptrdiff_t(script->mainOffset())) {
       goto out;
     }
     if (lineno >= target) {
       unsigned diff = lineno - target;
       if (diff < bestdiff) {
         bestdiff = diff;
         best = offset;
       }
     }
-    offset += SN_DELTA(sn);
-    SrcNoteType type = SN_TYPE(sn);
-    if (type == SRC_SETLINE) {
-      lineno = unsigned(GetSrcNoteOffset(sn, SrcNote::SetLine::Line));
-    } else if (type == SRC_NEWLINE) {
+    offset += sn->delta();
+    SrcNoteType type = sn->type();
+    if (type == SrcNoteType::SetLine) {
+      lineno = SrcNote::SetLine::getLine(sn);
+    } else if (type == SrcNoteType::NewLine) {
       lineno++;
     }
   }
   if (best >= 0) {
     offset = best;
   }
 out:
   return script->offsetToPC(offset);
 }
 
 JS_FRIEND_API unsigned js::GetScriptLineExtent(JSScript* script) {
   unsigned lineno = script->lineno();
   unsigned maxLineNo = lineno;
-  for (jssrcnote* sn = script->notes(); !SN_IS_TERMINATOR(sn);
-       sn = SN_NEXT(sn)) {
-    SrcNoteType type = SN_TYPE(sn);
-    if (type == SRC_SETLINE) {
-      lineno = unsigned(GetSrcNoteOffset(sn, SrcNote::SetLine::Line));
-    } else if (type == SRC_NEWLINE) {
+  for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) {
+    auto sn = *iter;
+    SrcNoteType type = sn->type();
+    if (type == SrcNoteType::SetLine) {
+      lineno = SrcNote::SetLine::getLine(sn);
+    } else if (type == SrcNoteType::NewLine) {
       lineno++;
     }
 
     if (maxLineNo < lineno) {
       maxLineNo = lineno;
     }
   }
 
@@ -5103,23 +5107,23 @@ void CopySpan(const SourceSpan& source, 
   std::copy(source.cbegin(), source.cend(), target.begin());
 }
 
 /* static */
 js::UniquePtr<ImmutableScriptData> ImmutableScriptData::new_(
     JSContext* cx, uint32_t mainOffset, uint32_t nfixed, uint32_t nslots,
     uint32_t bodyScopeIndex, uint32_t numICEntries,
     uint32_t numBytecodeTypeSets, bool isFunction, uint16_t funLength,
-    mozilla::Span<const jsbytecode> code, mozilla::Span<const jssrcnote> notes,
+    mozilla::Span<const jsbytecode> code, mozilla::Span<const SrcNote> notes,
     mozilla::Span<const uint32_t> resumeOffsets,
     mozilla::Span<const ScopeNote> scopeNotes,
     mozilla::Span<const TryNote> tryNotes) {
   MOZ_RELEASE_ASSERT(code.Length() <= frontend::MaxBytecodeLength);
 
-  // There are 1-4 copies of SN_MAKE_TERMINATOR appended after the source
+  // There are 1-4 copies of SrcNoteType::Null appended after the source
   // notes. These are a combination of sentinel and padding values.
   static_assert(frontend::MaxSrcNotesLength <= UINT32_MAX - CodeNoteAlign,
                 "Length + CodeNoteAlign shouldn't overflow UINT32_MAX");
   size_t noteLength = notes.Length();
   MOZ_RELEASE_ASSERT(noteLength <= frontend::MaxSrcNotesLength);
 
   size_t nullLength = ComputeNotePadding(code.Length(), noteLength);
 
@@ -5142,17 +5146,17 @@ js::UniquePtr<ImmutableScriptData> Immut
 
   if (isFunction) {
     data->funLength = funLength;
   }
 
   // Initialize trailing arrays
   CopySpan(code, data->codeSpan());
   CopySpan(notes, data->notesSpan().To(noteLength));
-  std::fill_n(data->notes() + noteLength, nullLength, SRC_NULL);
+  std::fill_n(data->notes() + noteLength, nullLength, SrcNote::terminator());
   CopySpan(resumeOffsets, data->resumeOffsets());
   CopySpan(scopeNotes, data->scopeNotes());
   CopySpan(tryNotes, data->tryNotes());
 
   return data;
 }
 
 /* static */
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -23,16 +23,17 @@
 
 #include <type_traits>  // std::is_same
 #include <utility>      // std::move
 
 #include "jstypes.h"
 
 #include "frontend/BinASTRuntimeSupport.h"
 #include "frontend/NameAnalysisTypes.h"
+#include "frontend/SourceNotes.h"  // SrcNote
 #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 "util/StructuredSpewer.h"
@@ -2573,17 +2574,17 @@ class JSScript : public js::BaseScript {
   }
 
   bool hasLoops();
 
   uint32_t numNotes() const {
     MOZ_ASSERT(sharedData_);
     return immutableScriptData()->noteLength();
   }
-  jssrcnote* notes() const {
+  js::SrcNote* notes() const {
     MOZ_ASSERT(sharedData_);
     return immutableScriptData()->notes();
   }
 
   size_t natoms() const {
     MOZ_ASSERT(sharedData_);
     return sharedData_->natoms();
   }
@@ -2742,32 +2743,34 @@ struct ScriptAndCounts {
 
 extern JS::UniqueChars FormatIntroducedFilename(JSContext* cx,
                                                 const char* filename,
                                                 unsigned lineno,
                                                 const char* introducer);
 
 struct GSNCache;
 
-jssrcnote* GetSrcNote(GSNCache& cache, JSScript* script, jsbytecode* pc);
-
-extern jssrcnote* GetSrcNote(JSContext* cx, JSScript* script, jsbytecode* pc);
+const js::SrcNote* GetSrcNote(GSNCache& cache, JSScript* script,
+                              jsbytecode* pc);
+
+extern const js::SrcNote* GetSrcNote(JSContext* cx, JSScript* script,
+                                     jsbytecode* pc);
 
 extern jsbytecode* LineNumberToPC(JSScript* script, unsigned lineno);
 
 extern JS_FRIEND_API unsigned GetScriptLineExtent(JSScript* script);
 
 } /* namespace js */
 
 namespace js {
 
 extern unsigned PCToLineNumber(JSScript* script, jsbytecode* pc,
                                unsigned* columnp = nullptr);
 
-extern unsigned PCToLineNumber(unsigned startLine, jssrcnote* notes,
+extern unsigned PCToLineNumber(unsigned startLine, SrcNote* notes,
                                jsbytecode* code, jsbytecode* pc,
                                unsigned* columnp = nullptr);
 
 /*
  * This function returns the file and line number of the script currently
  * executing on cx. If there is no current script executing on cx (e.g., a
  * native called directly through JSAPI (e.g., by setTimeout)), nullptr and 0
  * are returned as the file and line.
--- a/js/src/vm/SharedStencil.h
+++ b/js/src/vm/SharedStencil.h
@@ -389,17 +389,17 @@ class MutableScriptFlags : public Script
 //
 // Note: The '----' separators are for readability only.
 //
 // ----
 //   <ImmutableScriptData itself>
 // ----
 //   (REQUIRED) Flags structure
 //   (REQUIRED) Array of jsbytecode constituting code()
-//   (REQUIRED) Array of jssrcnote constituting notes()
+//   (REQUIRED) Array of SrcNote constituting notes()
 // ----
 //   (OPTIONAL) Array of uint32_t optional-offsets
 //  optArrayOffset:
 // ----
 //  L0:
 //   (OPTIONAL) Array of uint32_t constituting resumeOffsets()
 //  L1:
 //   (OPTIONAL) Array of ScopeNote constituting scopeNotes()
@@ -547,18 +547,17 @@ class alignas(uint32_t) ImmutableScriptD
     return this_->offsetToPointer<Offset>(optArrayOffset_)[-index];
   }
 
  public:
   static js::UniquePtr<ImmutableScriptData> new_(
       JSContext* cx, uint32_t mainOffset, uint32_t nfixed, uint32_t nslots,
       uint32_t bodyScopeIndex, uint32_t numICEntries,
       uint32_t numBytecodeTypeSets, bool isFunction, uint16_t funLength,
-      mozilla::Span<const jsbytecode> code,
-      mozilla::Span<const jssrcnote> notes,
+      mozilla::Span<const jsbytecode> code, mozilla::Span<const SrcNote> notes,
       mozilla::Span<const uint32_t> resumeOffsets,
       mozilla::Span<const ScopeNote> scopeNotes,
       mozilla::Span<const TryNote> tryNotes);
 
   static js::UniquePtr<ImmutableScriptData> new_(
       JSContext* cx, uint32_t codeLength, uint32_t noteLength,
       uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes);
 
@@ -590,18 +589,18 @@ class alignas(uint32_t) ImmutableScriptD
     return const_cast<ImmutableScriptData*>(this)->flagsRef();
   }
 
   uint32_t codeLength() const { return codeLength_; }
   jsbytecode* code() { return offsetToPointer<jsbytecode>(codeOffset()); }
   mozilla::Span<jsbytecode> codeSpan() { return {code(), codeLength()}; }
 
   uint32_t noteLength() const { return optionalOffsetsOffset() - noteOffset(); }
-  jssrcnote* notes() { return offsetToPointer<jssrcnote>(noteOffset()); }
-  mozilla::Span<jssrcnote> notesSpan() { return {notes(), noteLength()}; }
+  SrcNote* notes() { return offsetToPointer<SrcNote>(noteOffset()); }
+  mozilla::Span<SrcNote> notesSpan() { return {notes(), noteLength()}; }
 
   mozilla::Span<uint32_t> resumeOffsets() {
     return mozilla::MakeSpan(offsetToPointer<uint32_t>(resumeOffsetsOffset()),
                              offsetToPointer<uint32_t>(scopeNotesOffset()));
   }
   mozilla::Span<ScopeNote> scopeNotes() {
     return mozilla::MakeSpan(offsetToPointer<ScopeNote>(scopeNotesOffset()),
                              offsetToPointer<ScopeNote>(tryNotesOffset()));