SourceNotes.h (16407B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef frontend_SourceNotes_h 8 #define frontend_SourceNotes_h 9 10 #include "mozilla/Assertions.h" // MOZ_ASSERT 11 12 #include <algorithm> // std::min 13 #include <stddef.h> // ptrdiff_t, size_t 14 #include <stdint.h> // int8_t, uint8_t, uint32_t 15 16 #include "jstypes.h" // js::{Bit, BitMask} 17 #include "js/ColumnNumber.h" // JS::ColumnNumberOffset, JS::LimitedColumnNumberOneOrigin 18 19 namespace js { 20 21 /** 22 * [SMDOC] Source Notes 23 * 24 * Source notes are generated along with bytecode for associating line/column 25 * to opcode, and annotating opcode as breakpoint for debugging. 26 * 27 * A source note is a uint8_t with 4 bits of type and 4 bits of offset from 28 * the pc of the previous note. If 4 bits of offset aren't enough, extended 29 * delta notes (XDelta) consisting of 1 set high order bit followed by 7 offset 30 * bits are emitted before the next note. 31 * 32 * Source Note Extended Delta 33 * +7-6-5-4+3-2-1-0+ +7+6-5-4-3-2-1-0+ 34 * | type | delta | |1| ext-delta | 35 * +-------+-------+ +-+-------------+ 36 * 37 * Extended Delta with `ext-delta == 0` is used as terminator, which is 38 * padded between the end of source notes and the next notes in the 39 * ImmutableScriptData. 40 * 41 * Terminator 42 * +7+6-5-4-3-2-1-0+ 43 * |1|0 0 0 0 0 0 0| 44 * +-+-------------+ 45 * 46 * Some notes have operand offsets encoded immediately after them. Each operand 47 * is encoded either in single-byte or 4-bytes, depending on the range. 48 * 49 * Single-byte Operand (0 <= operand <= 127) 50 * 51 * +7+6-5-4-3-2-1-0+ 52 * |0| operand | 53 * +-+-------------+ 54 * 55 * 4-bytes Operand (128 <= operand) 56 * 57 * (operand_3 << 24) | (operand_2 << 16) | (operand_1 << 8) | operand_0 58 * 59 * +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+ 60 * |1| operand_3 | | operand_2 | | operand_1 | | operand_0 | 61 * +---------------+ +---------------+ +---------------+ +---------------+ 62 * 63 * NB: the js::SrcNote::specs_ array is indexed by this enum, so its 64 * initializers need to match the order here. 65 */ 66 67 #define FOR_EACH_SRC_NOTE_TYPE(M) \ 68 M(ColSpan, "colspan", int8_t(SrcNote::ColSpan::Operands::Count)) \ 69 /* Bytecode follows a source newline. */ \ 70 M(NewLine, "newline", 0) \ 71 M(NewLineColumn, "newlinecolumn", \ 72 int8_t(SrcNote::NewLineColumn::Operands::Count)) \ 73 M(SetLine, "setline", int8_t(SrcNote::SetLine::Operands::Count)) \ 74 M(SetLineColumn, "setlinecolumn", \ 75 int8_t(SrcNote::SetLineColumn::Operands::Count)) \ 76 /* Bytecode is a recommended breakpoint. */ \ 77 M(Breakpoint, "breakpoint", 0) \ 78 /* Bytecode is a recommended breakpoint, and the first in a */ \ 79 /* new steppable area. */ \ 80 M(BreakpointStepSep, "breakpoint-step-sep", 0) \ 81 M(Unused7, "unused", 0) \ 82 /* 8-15 (0b1xxx) are for extended delta notes. */ \ 83 M(XDelta, "xdelta", 0) 84 85 // Note: need to add a new source note? If there's no Unused* note left, 86 // consider bumping SrcNoteType::XDelta to 12-15 and change 87 // SrcNote::XDeltaBits from 7 to 6. 88 89 enum class SrcNoteType : uint8_t { 90 #define DEFINE_SRC_NOTE_TYPE(sym, name, arity) sym, 91 FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_TYPE) 92 #undef DEFINE_SRC_NOTE_TYPE 93 94 Last, 95 }; 96 97 static_assert(uint8_t(SrcNoteType::XDelta) == 8, "XDelta should be 8"); 98 99 class SrcNote { 100 struct Spec { 101 const char* name_; 102 int8_t arity_; 103 }; 104 105 static const Spec specs_[]; 106 107 static constexpr unsigned TypeBits = 4; 108 static constexpr unsigned DeltaBits = 4; 109 static constexpr unsigned XDeltaBits = 7; 110 111 static constexpr uint8_t TypeMask = js::BitMask(TypeBits) << DeltaBits; 112 static constexpr ptrdiff_t DeltaMask = js::BitMask(DeltaBits); 113 static constexpr ptrdiff_t XDeltaMask = js::BitMask(XDeltaBits); 114 115 static constexpr ptrdiff_t DeltaLimit = js::Bit(DeltaBits); 116 static constexpr ptrdiff_t XDeltaLimit = js::Bit(XDeltaBits); 117 118 static constexpr inline uint8_t toShiftedTypeBits(SrcNoteType type) { 119 return (uint8_t(type) << DeltaBits); 120 } 121 122 static inline uint8_t noteValue(SrcNoteType type, ptrdiff_t delta) { 123 MOZ_ASSERT((delta & DeltaMask) == delta); 124 return noteValueUnchecked(type, delta); 125 } 126 127 static constexpr inline uint8_t noteValueUnchecked(SrcNoteType type, 128 ptrdiff_t delta) { 129 return toShiftedTypeBits(type) | (delta & DeltaMask); 130 } 131 132 static inline uint8_t xDeltaValue(ptrdiff_t delta) { 133 return toShiftedTypeBits(SrcNoteType::XDelta) | (delta & XDeltaMask); 134 } 135 136 uint8_t value_; 137 138 constexpr explicit SrcNote(uint8_t value) : value_(value) {} 139 140 public: 141 // A default value for padding. 142 constexpr SrcNote() : value_(noteValueUnchecked(SrcNoteType::XDelta, 0)) {} 143 144 SrcNote(const SrcNote& other) = default; 145 SrcNote& operator=(const SrcNote& other) = default; 146 147 SrcNote(SrcNote&& other) = default; 148 SrcNote& operator=(SrcNote&& other) = default; 149 150 static constexpr SrcNote padding() { return SrcNote(); } 151 152 private: 153 inline uint8_t typeBits() const { return (value_ >> DeltaBits); } 154 155 inline bool isXDelta() const { 156 return typeBits() >= uint8_t(SrcNoteType::XDelta); 157 } 158 159 inline bool isFourBytesOperand() const { 160 return value_ & FourBytesOperandFlag; 161 } 162 163 // number of operands 164 inline unsigned arity() const { 165 MOZ_ASSERT(uint8_t(type()) < uint8_t(SrcNoteType::Last)); 166 return specs_[uint8_t(type())].arity_; 167 } 168 169 public: 170 inline SrcNoteType type() const { 171 if (isXDelta()) { 172 return SrcNoteType::XDelta; 173 } 174 return SrcNoteType(typeBits()); 175 } 176 177 // name for disassembly/debugging output 178 const char* name() const { 179 MOZ_ASSERT(uint8_t(type()) < uint8_t(SrcNoteType::Last)); 180 return specs_[uint8_t(type())].name_; 181 } 182 183 inline bool isTerminator() const { 184 return value_ == noteValueUnchecked(SrcNoteType::XDelta, 0); 185 } 186 187 inline ptrdiff_t delta() const { 188 if (isXDelta()) { 189 return value_ & XDeltaMask; 190 } 191 return value_ & DeltaMask; 192 } 193 194 private: 195 /* 196 * Operand fields follow certain notes and are frequency-encoded: an operand 197 * in [0,0x7f] consumes one byte, an operand in [0x80,0x7fffffff] takes four, 198 * and the high bit of the first byte is set. 199 */ 200 static constexpr unsigned FourBytesOperandFlag = 0x80; 201 static constexpr unsigned FourBytesOperandMask = 0x7f; 202 203 static constexpr unsigned OperandBits = 31; 204 205 public: 206 static constexpr size_t MaxOperand = (size_t(1) << OperandBits) - 1; 207 208 static inline bool isRepresentableOperand(ptrdiff_t operand) { 209 return 0 <= operand && size_t(operand) <= MaxOperand; 210 } 211 212 class ColSpan { 213 public: 214 enum class Operands { 215 // The column span (the diff between the column corresponds to the 216 // current op and last known column). 217 Span, 218 Count 219 }; 220 221 private: 222 /* 223 * SrcNoteType::ColSpan values represent changes to the column number. 224 * Colspans are signed: negative changes arise in describing constructs like 225 * for(;;) loops, that generate code in non-source order. (Negative colspans 226 * also have a history of indicating bugs in updating ParseNodes' source 227 * locations.) 228 * 229 * We store colspans in operands. However, unlike normal operands, colspans 230 * are signed, so we truncate colspans (toOperand) for storage as 231 * operands, and sign-extend operands into colspans when we read them 232 * (fromOperand). 233 */ 234 static constexpr ptrdiff_t ColSpanSignBit = 1 << (OperandBits - 1); 235 236 static inline JS::ColumnNumberOffset fromOperand(ptrdiff_t operand) { 237 // There should be no bits set outside the field we're going to 238 // sign-extend. 239 MOZ_ASSERT(!(operand & ~((1U << OperandBits) - 1))); 240 241 // Sign-extend the least significant OperandBits bits. 242 return JS::ColumnNumberOffset((operand ^ ColSpanSignBit) - 243 ColSpanSignBit); 244 } 245 246 public: 247 static constexpr ptrdiff_t MinColSpan = -ColSpanSignBit; 248 static constexpr ptrdiff_t MaxColSpan = ColSpanSignBit - 1; 249 250 static inline ptrdiff_t toOperand(JS::ColumnNumberOffset colspan) { 251 // Truncate the two's complement colspan, for storage as an operand. 252 ptrdiff_t operand = colspan.value() & ((1U << OperandBits) - 1); 253 254 // When we read this back, we'd better get the value we stored. 255 MOZ_ASSERT(fromOperand(operand) == colspan); 256 return operand; 257 } 258 259 static inline JS::ColumnNumberOffset getSpan(const SrcNote* sn); 260 }; 261 262 class NewLineColumn { 263 public: 264 enum class Operands { Column, Count }; 265 266 private: 267 static inline JS::LimitedColumnNumberOneOrigin fromOperand( 268 ptrdiff_t operand) { 269 return JS::LimitedColumnNumberOneOrigin(operand); 270 } 271 272 public: 273 static inline ptrdiff_t toOperand(JS::LimitedColumnNumberOneOrigin column) { 274 return column.oneOriginValue(); 275 } 276 277 static inline JS::LimitedColumnNumberOneOrigin getColumn(const SrcNote* sn); 278 }; 279 280 class SetLine { 281 public: 282 enum class Operands { 283 // The file-absolute source line number of the current op. 284 Line, 285 Count 286 }; 287 288 private: 289 static inline size_t fromOperand(ptrdiff_t operand) { 290 return size_t(operand); 291 } 292 293 public: 294 static inline unsigned lengthFor(unsigned line, size_t initialLine) { 295 unsigned operandSize = toOperand(line, initialLine) > 296 ptrdiff_t(SrcNote::FourBytesOperandMask) 297 ? 4 298 : 1; 299 return 1 /* SetLine */ + operandSize; 300 } 301 302 static inline ptrdiff_t toOperand(size_t line, size_t initialLine) { 303 MOZ_ASSERT(line >= initialLine); 304 return ptrdiff_t(line - initialLine); 305 } 306 307 static inline size_t getLine(const SrcNote* sn, size_t initialLine); 308 }; 309 310 class SetLineColumn { 311 public: 312 enum class Operands { Line, Column, Count }; 313 314 private: 315 static inline size_t lineFromOperand(ptrdiff_t operand) { 316 return size_t(operand); 317 } 318 319 static inline JS::LimitedColumnNumberOneOrigin columnFromOperand( 320 ptrdiff_t operand) { 321 return JS::LimitedColumnNumberOneOrigin(operand); 322 } 323 324 public: 325 static inline ptrdiff_t columnToOperand( 326 JS::LimitedColumnNumberOneOrigin column) { 327 return column.oneOriginValue(); 328 } 329 330 static inline size_t getLine(const SrcNote* sn, size_t initialLine); 331 static inline JS::LimitedColumnNumberOneOrigin getColumn(const SrcNote* sn); 332 }; 333 334 friend class SrcNoteWriter; 335 friend class SrcNoteReader; 336 friend class SrcNoteIterator; 337 }; 338 339 class SrcNoteWriter { 340 public: 341 // Write a source note with given `type`, and `delta` from the last source 342 // note. This writes the source note itself, and `XDelta`s if necessary. 343 // 344 // This doesn't write or allocate space for operands. 345 // If the source note is not nullary, the caller is responsible for calling 346 // `writeOperand` immediately after this. 347 // 348 // `allocator` is called with the number of bytes required to store the notes. 349 // `allocator` can be called multiple times for each source note. 350 // The last call corresponds to the source note for `type`. 351 template <typename T> 352 static bool writeNote(SrcNoteType type, ptrdiff_t delta, T allocator) { 353 while (delta >= SrcNote::DeltaLimit) { 354 ptrdiff_t xdelta = std::min(delta, SrcNote::XDeltaMask); 355 SrcNote* sn = allocator(1); 356 if (!sn) { 357 return false; 358 } 359 sn->value_ = SrcNote::xDeltaValue(xdelta); 360 delta -= xdelta; 361 } 362 363 SrcNote* sn = allocator(1); 364 if (!sn) { 365 return false; 366 } 367 sn->value_ = SrcNote::noteValue(type, delta); 368 return true; 369 } 370 371 static void convertNote(SrcNote* sn, SrcNoteType newType) { 372 ptrdiff_t delta = sn->delta(); 373 sn->value_ = SrcNote::noteValue(newType, delta); 374 } 375 376 // Write source note operand. 377 // 378 // `allocator` is called with the number of bytes required to store the 379 // operand. `allocator` is called only once. 380 template <typename T> 381 static bool writeOperand(ptrdiff_t operand, T allocator) { 382 if (operand > ptrdiff_t(SrcNote::FourBytesOperandMask)) { 383 SrcNote* sn = allocator(4); 384 if (!sn) { 385 return false; 386 } 387 388 sn[0].value_ = (SrcNote::FourBytesOperandFlag | (operand >> 24)); 389 sn[1].value_ = operand >> 16; 390 sn[2].value_ = operand >> 8; 391 sn[3].value_ = operand; 392 } else { 393 SrcNote* sn = allocator(1); 394 if (!sn) { 395 return false; 396 } 397 398 sn[0].value_ = operand; 399 } 400 401 return true; 402 } 403 }; 404 405 class SrcNoteReader { 406 template <typename T> 407 static T getOperandHead(T sn, unsigned which) { 408 MOZ_ASSERT(sn->type() != SrcNoteType::XDelta); 409 MOZ_ASSERT(uint8_t(which) < sn->arity()); 410 411 T curr = sn + 1; 412 for (; which; which--) { 413 if (curr->isFourBytesOperand()) { 414 curr += 4; 415 } else { 416 curr++; 417 } 418 } 419 return curr; 420 } 421 422 public: 423 // Return the operand of source note `sn`, specified by `which`. 424 static ptrdiff_t getOperand(const SrcNote* sn, unsigned which) { 425 const SrcNote* head = getOperandHead(sn, which); 426 427 if (head->isFourBytesOperand()) { 428 return ptrdiff_t( 429 (uint32_t(head[0].value_ & SrcNote::FourBytesOperandMask) << 24) | 430 (uint32_t(head[1].value_) << 16) | (uint32_t(head[2].value_) << 8) | 431 uint32_t(head[3].value_)); 432 } 433 434 return ptrdiff_t(head[0].value_); 435 } 436 }; 437 438 /* static */ 439 inline JS::ColumnNumberOffset SrcNote::ColSpan::getSpan(const SrcNote* sn) { 440 return fromOperand(SrcNoteReader::getOperand(sn, unsigned(Operands::Span))); 441 } 442 443 /* static */ 444 inline JS::LimitedColumnNumberOneOrigin SrcNote::NewLineColumn::getColumn( 445 const SrcNote* sn) { 446 return fromOperand(SrcNoteReader::getOperand(sn, unsigned(Operands::Column))); 447 } 448 449 /* static */ 450 inline size_t SrcNote::SetLine::getLine(const SrcNote* sn, size_t initialLine) { 451 return initialLine + 452 fromOperand(SrcNoteReader::getOperand(sn, unsigned(Operands::Line))); 453 } 454 455 /* static */ 456 inline size_t SrcNote::SetLineColumn::getLine(const SrcNote* sn, 457 size_t initialLine) { 458 return initialLine + lineFromOperand(SrcNoteReader::getOperand( 459 sn, unsigned(Operands::Line))); 460 } 461 462 /* static */ 463 inline JS::LimitedColumnNumberOneOrigin SrcNote::SetLineColumn::getColumn( 464 const SrcNote* sn) { 465 return columnFromOperand( 466 SrcNoteReader::getOperand(sn, unsigned(Operands::Column))); 467 } 468 469 // Iterate over SrcNote array, until it hits terminator. 470 // 471 // Usage: 472 // for (SrcNoteIterator iter(notes); !iter.atEnd(); ++iter) { 473 // auto sn = *iter; // `sn` is `const SrcNote*` typed. 474 // ... 475 // } 476 class SrcNoteIterator { 477 const SrcNote* current_; 478 const SrcNote* end_; 479 480 void next() { 481 unsigned arity = current_->arity(); 482 current_++; 483 484 for (; arity; arity--) { 485 if (current_->isFourBytesOperand()) { 486 current_ += 4; 487 } else { 488 current_++; 489 } 490 } 491 } 492 493 public: 494 SrcNoteIterator() = delete; 495 496 SrcNoteIterator(const SrcNoteIterator& other) = delete; 497 SrcNoteIterator& operator=(const SrcNoteIterator& other) = delete; 498 499 SrcNoteIterator(SrcNoteIterator&& other) = default; 500 SrcNoteIterator& operator=(SrcNoteIterator&& other) = default; 501 502 SrcNoteIterator(const SrcNote* sn, const SrcNote* end) 503 : current_(sn), end_(end) {} 504 505 bool atEnd() const { 506 MOZ_ASSERT(current_ <= end_); 507 return current_ == end_ || current_->isTerminator(); 508 } 509 510 const SrcNote* operator*() const { return current_; } 511 512 // Pre-increment 513 SrcNoteIterator& operator++() { 514 next(); 515 return *this; 516 } 517 518 // Post-increment 519 SrcNoteIterator operator++(int) = delete; 520 }; 521 522 } // namespace js 523 524 #endif /* frontend_SourceNotes_h */