summaryrefslogtreecommitdiffstats
path: root/js/src/jit/IonScript.h
blob: c1f7a4810ac08400b7395fef3fc3f20531ceca56 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
/* -*- 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 jit_IonScript_h
#define jit_IonScript_h

#include "mozilla/MemoryReporting.h"  // MallocSizeOf

#include <stddef.h>  // size_t
#include <stdint.h>  // uint8_t, uint32_t

#include "jstypes.h"

#include "gc/Barrier.h"          // HeapPtr{JitCode,Object}
#include "jit/IonTypes.h"        // IonCompilationId
#include "jit/JitCode.h"         // JitCode
#include "jit/JitOptions.h"      // JitOptions
#include "js/TypeDecls.h"        // jsbytecode
#include "util/TrailingArray.h"  // TrailingArray

namespace js {
namespace jit {

class SnapshotWriter;
class RecoverWriter;
class SafepointWriter;
class CodegenSafepointIndex;
class SafepointIndex;
class OsiIndex;
class IonIC;

// An IonScript attaches Ion-generated information to a JSScript. The header
// structure is followed by several arrays of data. These trailing arrays have a
// layout based on offsets (bytes from 'this') stored in the IonScript header.
//
//    <IonScript itself>
//    --
//    PreBarriered<Value>[] constantTable()
//    uint8_t[]             runtimeData()
//    OsiIndex[]            osiIndex()
//    SafepointIndex[]      safepointIndex()
//    uint32_t[]            icIndex()
//    --
//    uint8_t[]             safepoints()
//    uint8_t[]             snapshots()
//    uint8_t[]             snapshotsRVATable()
//    uint8_t[]             recovers()
//
// Note: These are arranged in order of descending alignment requirements to
// avoid the need for padding. The `runtimeData` uses uint64_t alignement due to
// its use of mozilla::AlignedStorage2.
class alignas(8) IonScript final : public TrailingArray {
 private:
  // Offset (in bytes) from `this` to the start of each trailing array. Each
  // array ends where following one begins. There is no implicit padding (except
  // possible at very end).
  Offset constantTableOffset_ = 0;   // JS::Value aligned
  Offset runtimeDataOffset_ = 0;     // uint64_t aligned
  Offset nurseryObjectsOffset_ = 0;  // pointer aligned
  Offset osiIndexOffset_ = 0;
  Offset safepointIndexOffset_ = 0;
  Offset icIndexOffset_ = 0;
  Offset safepointsOffset_ = 0;
  Offset snapshotsOffset_ = 0;
  Offset rvaTableOffset_ = 0;
  Offset recoversOffset_ = 0;
  Offset allocBytes_ = 0;

  // Code pointer containing the actual method.
  HeapPtr<JitCode*> method_ = nullptr;

  // Entrypoint for OSR, or nullptr.
  jsbytecode* osrPc_ = nullptr;

  // Offset to OSR entrypoint from method_->raw(), or 0.
  uint32_t osrEntryOffset_ = 0;

  // Offset of the invalidation epilogue (which pushes this IonScript
  // and calls the invalidation thunk).
  uint32_t invalidateEpilogueOffset_ = 0;

  // The offset immediately after the IonScript immediate.
  // NOTE: technically a constant delta from
  // |invalidateEpilogueOffset_|, so we could hard-code this
  // per-platform if we want.
  uint32_t invalidateEpilogueDataOffset_ = 0;

  // Number of bailouts that have occurred for reasons that could be
  // fixed if we invalidated and recompiled.
  uint16_t numFixableBailouts_ = 0;

  // Number of bailouts that have occurred for reasons that can't be
  // fixed by recompiling: for example, bailing out to catch an exception.
  uint16_t numUnfixableBailouts_ = 0;

 public:
  enum class LICMState : uint8_t { NeverBailed, Bailed, BailedAndHitFallback };

 private:
  // Tracks the state of LICM bailouts.
  LICMState licmState_ = LICMState::NeverBailed;

  // Flag set if IonScript was compiled with profiling enabled.
  bool hasProfilingInstrumentation_ = false;

  // Number of bytes this function reserves on the stack for slots spilled by
  // the register allocator.
  uint32_t localSlotsSize_ = 0;

  // Number of bytes used passed in as formal arguments or |this|.
  uint32_t argumentSlotsSize_ = 0;

  // Frame size is the value that can be added to the StackPointer along
  // with the frame prefix to get a valid JitFrameLayout.
  uint32_t frameSize_ = 0;

  // Number of references from invalidation records.
  uint32_t invalidationCount_ = 0;

  // Identifier of the compilation which produced this code.
  IonCompilationId compilationId_;

  // Number of times we tried to enter this script via OSR but failed due to
  // a LOOPENTRY pc other than osrPc_.
  uint32_t osrPcMismatchCounter_ = 0;

#ifdef DEBUG
  // A hash of the ICScripts used in this compilation.
  mozilla::HashNumber icHash_ = 0;
#endif

  // End of fields.

 private:
  // Layout helpers
  Offset constantTableOffset() const { return constantTableOffset_; }
  Offset runtimeDataOffset() const { return runtimeDataOffset_; }
  Offset nurseryObjectsOffset() const { return nurseryObjectsOffset_; }
  Offset osiIndexOffset() const { return osiIndexOffset_; }
  Offset safepointIndexOffset() const { return safepointIndexOffset_; }
  Offset icIndexOffset() const { return icIndexOffset_; }
  Offset safepointsOffset() const { return safepointsOffset_; }
  Offset snapshotsOffset() const { return snapshotsOffset_; }
  Offset rvaTableOffset() const { return rvaTableOffset_; }
  Offset recoversOffset() const { return recoversOffset_; }
  Offset endOffset() const { return allocBytes_; }

  // Hardcode size of incomplete types. These are verified in Ion.cpp.
  static constexpr size_t SizeOf_OsiIndex = 2 * sizeof(uint32_t);
  static constexpr size_t SizeOf_SafepointIndex = 2 * sizeof(uint32_t);

 public:
  //
  // Table of constants referenced in snapshots. (JS::Value alignment)
  //
  PreBarriered<Value>* constants() {
    // Nursery constants are manually barriered in CodeGenerator::link() so a
    // post barrier is not required..
    return offsetToPointer<PreBarriered<Value>>(constantTableOffset());
  }
  size_t numConstants() const {
    return numElements<PreBarriered<Value>>(constantTableOffset(),
                                            runtimeDataOffset());
  }

  //
  // IonIC data structures. (uint64_t alignment)
  //
  uint8_t* runtimeData() {
    return offsetToPointer<uint8_t>(runtimeDataOffset());
  }
  size_t runtimeSize() const {
    return numElements<uint8_t>(runtimeDataOffset(), nurseryObjectsOffset());
  }

  //
  // List of (originally) nursery-allocated objects referenced from JIT code.
  // (JSObject* alignment)
  //
  HeapPtr<JSObject*>* nurseryObjects() {
    return offsetToPointer<HeapPtr<JSObject*>>(nurseryObjectsOffset());
  }
  size_t numNurseryObjects() const {
    return numElements<HeapPtr<JSObject*>>(nurseryObjectsOffset(),
                                           osiIndexOffset());
  }
  void* addressOfNurseryObject(uint32_t index) {
    MOZ_ASSERT(index < numNurseryObjects());
    return &nurseryObjects()[index];
  }

  //
  // Map OSI-point displacement to snapshot.
  //
  OsiIndex* osiIndices() { return offsetToPointer<OsiIndex>(osiIndexOffset()); }
  const OsiIndex* osiIndices() const {
    return offsetToPointer<OsiIndex>(osiIndexOffset());
  }
  size_t numOsiIndices() const {
    return numElements<SizeOf_OsiIndex>(osiIndexOffset(),
                                        safepointIndexOffset());
  }

  //
  // Map code displacement to safepoint / OSI-patch-delta.
  //
  SafepointIndex* safepointIndices() {
    return offsetToPointer<SafepointIndex>(safepointIndexOffset());
  }
  const SafepointIndex* safepointIndices() const {
    return offsetToPointer<SafepointIndex>(safepointIndexOffset());
  }
  size_t numSafepointIndices() const {
    return numElements<SizeOf_SafepointIndex>(safepointIndexOffset(),
                                              icIndexOffset());
  }

  //
  // Offset into `runtimeData` for each (variable-length) IonIC.
  //
  uint32_t* icIndex() { return offsetToPointer<uint32_t>(icIndexOffset()); }
  size_t numICs() const {
    return numElements<uint32_t>(icIndexOffset(), safepointsOffset());
  }

  //
  // Safepoint table as a CompactBuffer.
  //
  const uint8_t* safepoints() const {
    return offsetToPointer<uint8_t>(safepointsOffset());
  }
  size_t safepointsSize() const {
    return numElements<uint8_t>(safepointsOffset(), snapshotsOffset());
  }

  //
  // Snapshot and RValueAllocation tables as CompactBuffers.
  //
  const uint8_t* snapshots() const {
    return offsetToPointer<uint8_t>(snapshotsOffset());
  }
  size_t snapshotsListSize() const {
    return numElements<uint8_t>(snapshotsOffset(), rvaTableOffset());
  }
  size_t snapshotsRVATableSize() const {
    return numElements<uint8_t>(rvaTableOffset(), recoversOffset());
  }

  //
  // Recover instruction table as a CompactBuffer.
  //
  const uint8_t* recovers() const {
    return offsetToPointer<uint8_t>(recoversOffset());
  }
  size_t recoversSize() const {
    return numElements<uint8_t>(recoversOffset(), endOffset());
  }

 private:
  IonScript(IonCompilationId compilationId, uint32_t localSlotsSize,
            uint32_t argumentSlotsSize, uint32_t frameSize);

 public:
  static IonScript* New(JSContext* cx, IonCompilationId compilationId,
                        uint32_t localSlotsSize, uint32_t argumentSlotsSize,
                        uint32_t frameSize, size_t snapshotsListSize,
                        size_t snapshotsRVATableSize, size_t recoversSize,
                        size_t constants, size_t nurseryObjects,
                        size_t safepointIndices, size_t osiIndices,
                        size_t icEntries, size_t runtimeSize,
                        size_t safepointsSize);

  static void Destroy(JS::GCContext* gcx, IonScript* script);

  void trace(JSTracer* trc);

  static inline size_t offsetOfInvalidationCount() {
    return offsetof(IonScript, invalidationCount_);
  }

 public:
  JitCode* method() const { return method_; }
  void setMethod(JitCode* code) {
    MOZ_ASSERT(!invalidated());
    method_ = code;
  }
  void setOsrPc(jsbytecode* osrPc) { osrPc_ = osrPc; }
  jsbytecode* osrPc() const { return osrPc_; }
  void setOsrEntryOffset(uint32_t offset) {
    MOZ_ASSERT(!osrEntryOffset_);
    osrEntryOffset_ = offset;
  }
  uint32_t osrEntryOffset() const { return osrEntryOffset_; }
  bool containsCodeAddress(uint8_t* addr) const {
    return method()->raw() <= addr &&
           addr <= method()->raw() + method()->instructionsSize();
  }
  bool containsReturnAddress(uint8_t* addr) const {
    // This accounts for an off by one error caused by the return address of a
    // bailout sitting outside the range of the containing function.
    return method()->raw() <= addr &&
           addr <= method()->raw() + method()->instructionsSize();
  }
  void setInvalidationEpilogueOffset(uint32_t offset) {
    MOZ_ASSERT(!invalidateEpilogueOffset_);
    invalidateEpilogueOffset_ = offset;
  }
  uint32_t invalidateEpilogueOffset() const {
    MOZ_ASSERT(invalidateEpilogueOffset_);
    return invalidateEpilogueOffset_;
  }
  void setInvalidationEpilogueDataOffset(uint32_t offset) {
    MOZ_ASSERT(!invalidateEpilogueDataOffset_);
    invalidateEpilogueDataOffset_ = offset;
  }
  uint32_t invalidateEpilogueDataOffset() const {
    MOZ_ASSERT(invalidateEpilogueDataOffset_);
    return invalidateEpilogueDataOffset_;
  }

  void incNumFixableBailouts() { numFixableBailouts_++; }
  void resetNumFixableBailouts() { numFixableBailouts_ = 0; }
  void incNumUnfixableBailouts() { numUnfixableBailouts_++; }

  bool shouldInvalidate() const {
    return numFixableBailouts_ >= JitOptions.frequentBailoutThreshold;
  }
  bool shouldInvalidateAndDisable() const {
    return numUnfixableBailouts_ >= JitOptions.frequentBailoutThreshold * 5;
  }

  LICMState licmState() const { return licmState_; }
  void setHadLICMBailout() {
    if (licmState_ == LICMState::NeverBailed) {
      licmState_ = LICMState::Bailed;
    }
  }
  void noteBaselineFallback() {
    if (licmState_ == LICMState::Bailed) {
      licmState_ = LICMState::BailedAndHitFallback;
    }
  }

  void setHasProfilingInstrumentation() { hasProfilingInstrumentation_ = true; }
  void clearHasProfilingInstrumentation() {
    hasProfilingInstrumentation_ = false;
  }
  bool hasProfilingInstrumentation() const {
    return hasProfilingInstrumentation_;
  }

  size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    return mallocSizeOf(this);
  }
  PreBarriered<Value>& getConstant(size_t index) {
    MOZ_ASSERT(index < numConstants());
    return constants()[index];
  }
  uint32_t localSlotsSize() const { return localSlotsSize_; }
  uint32_t argumentSlotsSize() const { return argumentSlotsSize_; }
  uint32_t frameSize() const { return frameSize_; }
  const SafepointIndex* getSafepointIndex(uint32_t disp) const;
  const SafepointIndex* getSafepointIndex(uint8_t* retAddr) const {
    MOZ_ASSERT(containsCodeAddress(retAddr));
    return getSafepointIndex(retAddr - method()->raw());
  }
  const OsiIndex* getOsiIndex(uint32_t disp) const;
  const OsiIndex* getOsiIndex(uint8_t* retAddr) const;

  IonIC& getICFromIndex(uint32_t index) {
    MOZ_ASSERT(index < numICs());
    uint32_t offset = icIndex()[index];
    return getIC(offset);
  }
  inline IonIC& getIC(uint32_t offset) {
    MOZ_ASSERT(offset < runtimeSize());
    return *reinterpret_cast<IonIC*>(runtimeData() + offset);
  }
  void purgeICs(Zone* zone);
  void copySnapshots(const SnapshotWriter* writer);
  void copyRecovers(const RecoverWriter* writer);
  void copyConstants(const Value* vp);
  void copySafepointIndices(const CodegenSafepointIndex* si);
  void copyOsiIndices(const OsiIndex* oi);
  void copyRuntimeData(const uint8_t* data);
  void copyICEntries(const uint32_t* icEntries);
  void copySafepoints(const SafepointWriter* writer);

  bool invalidated() const { return invalidationCount_ != 0; }

  // Invalidate the current compilation.
  void invalidate(JSContext* cx, JSScript* script, bool resetUses,
                  const char* reason);

  size_t invalidationCount() const { return invalidationCount_; }
  void incrementInvalidationCount() { invalidationCount_++; }
  void decrementInvalidationCount(JS::GCContext* gcx) {
    MOZ_ASSERT(invalidationCount_);
    invalidationCount_--;
    if (!invalidationCount_) {
      Destroy(gcx, this);
    }
  }
  IonCompilationId compilationId() const { return compilationId_; }
  uint32_t incrOsrPcMismatchCounter() { return ++osrPcMismatchCounter_; }
  void resetOsrPcMismatchCounter() { osrPcMismatchCounter_ = 0; }

  size_t allocBytes() const { return allocBytes_; }

  static void preWriteBarrier(Zone* zone, IonScript* ionScript);

#ifdef DEBUG
  mozilla::HashNumber icHash() const { return icHash_; }
  void setICHash(mozilla::HashNumber hash) { icHash_ = hash; }
#endif
};

// Execution information for a basic block which may persist after the
// accompanying IonScript is destroyed, for use during profiling.
struct IonBlockCounts {
 private:
  uint32_t id_;

  // Approximate bytecode in the outer (not inlined) script this block
  // was generated from.
  uint32_t offset_;

  // File and line of the inner script this block was generated from.
  char* description_;

  // ids for successors of this block.
  uint32_t numSuccessors_;
  uint32_t* successors_;

  // Hit count for this block.
  uint64_t hitCount_;

  // Text information about the code generated for this block.
  char* code_;

 public:
  [[nodiscard]] bool init(uint32_t id, uint32_t offset, char* description,
                          uint32_t numSuccessors) {
    id_ = id;
    offset_ = offset;
    description_ = description;
    numSuccessors_ = numSuccessors;
    if (numSuccessors) {
      successors_ = js_pod_calloc<uint32_t>(numSuccessors);
      if (!successors_) {
        return false;
      }
    }
    return true;
  }

  void destroy() {
    js_free(description_);
    js_free(successors_);
    js_free(code_);
  }

  uint32_t id() const { return id_; }

  uint32_t offset() const { return offset_; }

  const char* description() const { return description_; }

  size_t numSuccessors() const { return numSuccessors_; }

  void setSuccessor(size_t i, uint32_t id) {
    MOZ_ASSERT(i < numSuccessors_);
    successors_[i] = id;
  }

  uint32_t successor(size_t i) const {
    MOZ_ASSERT(i < numSuccessors_);
    return successors_[i];
  }

  uint64_t* addressOfHitCount() { return &hitCount_; }

  uint64_t hitCount() const { return hitCount_; }

  void setCode(const char* code) {
    char* ncode = js_pod_malloc<char>(strlen(code) + 1);
    if (ncode) {
      strcpy(ncode, code);
      code_ = ncode;
    }
  }

  const char* code() const { return code_; }

  size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    return mallocSizeOf(description_) + mallocSizeOf(successors_) +
           mallocSizeOf(code_);
  }
};

// Execution information for a compiled script which may persist after the
// IonScript is destroyed, for use during profiling.
struct IonScriptCounts {
 private:
  // Any previous invalidated compilation(s) for the script.
  IonScriptCounts* previous_ = nullptr;

  // Information about basic blocks in this script.
  size_t numBlocks_ = 0;
  IonBlockCounts* blocks_ = nullptr;

 public:
  IonScriptCounts() = default;

  ~IonScriptCounts() {
    for (size_t i = 0; i < numBlocks_; i++) {
      blocks_[i].destroy();
    }
    js_free(blocks_);
    // The list can be long in some corner cases (bug 1140084), so
    // unroll the recursion.
    IonScriptCounts* victims = previous_;
    while (victims) {
      IonScriptCounts* victim = victims;
      victims = victim->previous_;
      victim->previous_ = nullptr;
      js_delete(victim);
    }
  }

  [[nodiscard]] bool init(size_t numBlocks) {
    blocks_ = js_pod_calloc<IonBlockCounts>(numBlocks);
    if (!blocks_) {
      return false;
    }

    numBlocks_ = numBlocks;
    return true;
  }

  size_t numBlocks() const { return numBlocks_; }

  IonBlockCounts& block(size_t i) {
    MOZ_ASSERT(i < numBlocks_);
    return blocks_[i];
  }

  void setPrevious(IonScriptCounts* previous) { previous_ = previous; }

  IonScriptCounts* previous() const { return previous_; }

  size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    size_t size = 0;
    auto currCounts = this;
    do {
      size += currCounts->sizeOfOneIncludingThis(mallocSizeOf);
      currCounts = currCounts->previous_;
    } while (currCounts);
    return size;
  }

  size_t sizeOfOneIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    size_t size = mallocSizeOf(this) + mallocSizeOf(blocks_);
    for (size_t i = 0; i < numBlocks_; i++) {
      blocks_[i].sizeOfExcludingThis(mallocSizeOf);
    }
    return size;
  }
};

}  // namespace jit
}  // namespace js

namespace JS {

template <>
struct DeletePolicy<js::jit::IonScript> {
  explicit DeletePolicy(JSRuntime* rt) : rt_(rt) {}
  void operator()(const js::jit::IonScript* script);

 private:
  JSRuntime* rt_;
};

}  // namespace JS

#endif /* jit_IonScript_h */