Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef frontend_SourceNotes_h
#define frontend_SourceNotes_h
#include "mozilla/Assertions.h" // MOZ_ASSERT
#include <algorithm> // std::min
#include <stddef.h> // ptrdiff_t, size_t
#include <stdint.h> // int8_t, uint8_t, uint32_t
#include "jstypes.h" // js::{Bit, BitMask}
#include "js/ColumnNumber.h" // JS::ColumnNumberOffset, JS::LimitedColumnNumberOneOrigin
namespace js {
/**
* [SMDOC] Source Notes
*
* Source notes are generated along with bytecode for associating line/column
* to opcode, and annotating opcode as breakpoint for debugging.
*
* A source note is a uint8_t with 4 bits of type and 4 bits of offset from
* the pc of the previous note. If 4 bits of offset aren't enough, extended
* delta notes (XDelta) consisting of 1 set high order bit followed by 7 offset
* bits are emitted before the next note.
*
* 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 |
* +-------+-------+ +-+-------------+
*
* Extended Delta with `ext-delta == 0` is used as terminator, which is
* padded between the end of source notes and the next notes in the
* ImmutableScriptData.
*
* Terminator
* +7+6-5-4-3-2-1-0+
* |1|0 0 0 0 0 0 0|
* +-+-------------+
*
* Some notes have operand offsets encoded immediately after them. Each operand
* is encoded either in single-byte or 4-bytes, depending on the range.
*
* Single-byte Operand (0 <= operand <= 127)
*
* +7+6-5-4-3-2-1-0+
* |0| operand |
* +-+-------------+
*
* 4-bytes Operand (128 <= operand)
*
* (operand_3 << 24) | (operand_2 << 16) | (operand_1 << 8) | operand_0
*
* +7-6-5-4-3-2-1-0+ +7-6-5-4-3-2-1-0+ +7-6-5-4-3-2-1-0+ +7-6-5-4-3-2-1-0+
* |1| operand_3 | | operand_2 | | operand_1 | | operand_0 |
* +---------------+ +---------------+ +---------------+ +---------------+
*
* 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) \
M(ColSpan, "colspan", int8_t(SrcNote::ColSpan::Operands::Count)) \
/* Bytecode follows a source newline. */ \
M(NewLine, "newline", 0) \
M(NewLineColumn, "newlinecolumn", \
int8_t(SrcNote::NewLineColumn::Operands::Count)) \
M(SetLine, "setline", int8_t(SrcNote::SetLine::Operands::Count)) \
M(SetLineColumn, "setlinecolumn", \
int8_t(SrcNote::SetLineColumn::Operands::Count)) \
/* Bytecode is a recommended breakpoint. */ \
M(Breakpoint, "breakpoint", 0) \
/* Bytecode is a recommended breakpoint, and the first in a */ \
/* new steppable area. */ \
M(BreakpointStepSep, "breakpoint-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,
};
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:
// A default value for padding.
constexpr SrcNote() : value_(noteValueUnchecked(SrcNoteType::XDelta, 0)) {}
SrcNote(const SrcNote& other) = default;
SrcNote& operator=(const SrcNote& other) = default;
SrcNote(SrcNote&& other) = default;
SrcNote& operator=(SrcNote&& other) = default;
static constexpr SrcNote padding() { 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 isTerminator() const {
return value_ == noteValueUnchecked(SrcNoteType::XDelta, 0);
}
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 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 inline JS::ColumnNumberOffset 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 JS::ColumnNumberOffset((operand ^ ColSpanSignBit) -
ColSpanSignBit);
}
public:
static constexpr ptrdiff_t MinColSpan = -ColSpanSignBit;
static constexpr ptrdiff_t MaxColSpan = ColSpanSignBit - 1;
static inline ptrdiff_t toOperand(JS::ColumnNumberOffset colspan) {
// Truncate the two's complement colspan, for storage as an operand.
ptrdiff_t operand = colspan.value() & ((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 JS::ColumnNumberOffset getSpan(const SrcNote* sn);
};
class NewLineColumn {
public:
enum class Operands { Column, Count };
private:
static inline JS::LimitedColumnNumberOneOrigin fromOperand(
ptrdiff_t operand) {
return JS::LimitedColumnNumberOneOrigin(operand);
}
public:
static inline ptrdiff_t toOperand(JS::LimitedColumnNumberOneOrigin column) {
return column.oneOriginValue();
}
static inline JS::LimitedColumnNumberOneOrigin getColumn(const SrcNote* sn);
};
class SetLine {
public:
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, size_t initialLine) {
unsigned operandSize = toOperand(line, initialLine) >
ptrdiff_t(SrcNote::FourBytesOperandMask)
? 4
: 1;
return 1 /* SetLine */ + operandSize;
}
static inline ptrdiff_t toOperand(size_t line, size_t initialLine) {
MOZ_ASSERT(line >= initialLine);
return ptrdiff_t(line - initialLine);
}
static inline size_t getLine(const SrcNote* sn, size_t initialLine);
};
class SetLineColumn {
public:
enum class Operands { Line, Column, Count };
private:
static inline size_t lineFromOperand(ptrdiff_t operand) {
return size_t(operand);
}
static inline JS::LimitedColumnNumberOneOrigin columnFromOperand(
ptrdiff_t operand) {
return JS::LimitedColumnNumberOneOrigin(operand);
}
public:
static inline ptrdiff_t columnToOperand(
JS::LimitedColumnNumberOneOrigin column) {
return column.oneOriginValue();
}
static inline size_t getLine(const SrcNote* sn, size_t initialLine);
static inline JS::LimitedColumnNumberOneOrigin getColumn(const SrcNote* sn);
};
friend class SrcNoteWriter;
friend class SrcNoteReader;
friend class SrcNoteIterator;
};
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;
}
SrcNote* sn = allocator(1);
if (!sn) {
return false;
}
sn->value_ = SrcNote::noteValue(type, delta);
return true;
}
static void convertNote(SrcNote* sn, SrcNoteType newType) {
ptrdiff_t delta = sn->delta();
sn->value_ = SrcNote::noteValue(newType, delta);
}
// 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;
}
};
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());
T curr = sn + 1;
for (; which; which--) {
if (curr->isFourBytesOperand()) {
curr += 4;
} else {
curr++;
}
}
return curr;
}
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);
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_));
}
return ptrdiff_t(head[0].value_);
}
};
/* static */
inline JS::ColumnNumberOffset SrcNote::ColSpan::getSpan(const SrcNote* sn) {
return fromOperand(SrcNoteReader::getOperand(sn, unsigned(Operands::Span)));
}
/* static */
inline JS::LimitedColumnNumberOneOrigin SrcNote::NewLineColumn::getColumn(
const SrcNote* sn) {
return fromOperand(SrcNoteReader::getOperand(sn, unsigned(Operands::Column)));
}
/* static */
inline size_t SrcNote::SetLine::getLine(const SrcNote* sn, size_t initialLine) {
return initialLine +
fromOperand(SrcNoteReader::getOperand(sn, unsigned(Operands::Line)));
}
/* static */
inline size_t SrcNote::SetLineColumn::getLine(const SrcNote* sn,
size_t initialLine) {
return initialLine + lineFromOperand(SrcNoteReader::getOperand(
sn, unsigned(Operands::Line)));
}
/* static */
inline JS::LimitedColumnNumberOneOrigin SrcNote::SetLineColumn::getColumn(
const SrcNote* sn) {
return columnFromOperand(
SrcNoteReader::getOperand(sn, unsigned(Operands::Column)));
}
// 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_;
const SrcNote* end_;
void next() {
unsigned arity = current_->arity();
current_++;
for (; arity; arity--) {
if (current_->isFourBytesOperand()) {
current_ += 4;
} else {
current_++;
}
}
}
public:
SrcNoteIterator() = delete;
SrcNoteIterator(const SrcNoteIterator& other) = delete;
SrcNoteIterator& operator=(const SrcNoteIterator& other) = delete;
SrcNoteIterator(SrcNoteIterator&& other) = default;
SrcNoteIterator& operator=(SrcNoteIterator&& other) = default;
SrcNoteIterator(const SrcNote* sn, const SrcNote* end)
: current_(sn), end_(end) {}
bool atEnd() const {
MOZ_ASSERT(current_ <= end_);
return current_ == end_ || current_->isTerminator();
}
const SrcNote* operator*() const { return current_; }
// Pre-increment
SrcNoteIterator& operator++() {
next();
return *this;
}
// Post-increment
SrcNoteIterator operator++(int) = delete;
};
} // namespace js
#endif /* frontend_SourceNotes_h */