Structure ID

Structure ID

The structure ID is used to store type information. Each ID has a matched number to find Structure Table. Inside the structure table, it has a structure which can identify property table. For example var v = o.f:

OSR

When type check fails in JIT stage, it will fallback to baseline JIT.

Inline Caching

To increate code speed(especially in get/write properties). Compiler will store information to reduce type checking.

Single Proto

var o = {f: 5, g: 6}; // Assume the structure ID is 42
var v = o.f;

We have inline cache to get property faster, here is a cached version:

get_by_id <result>, <base>, <properyName>

The following is a cached version:

get_by_id <result>, <base>, <properyName>, <cachedStructureID>, <cachedOffset>

Let’s represent it by graph:

get_by_id loc42, loc43, “g”, 42, 1 ----------|
                              |              |
                       (structure ID) (inline offset)
+------------------+------------------+--------+---------------+---------------+
|structure ID: 42  | other properties |  null  | f: 0xffff.... | g: 0xffff.... |
+------------------+------------------+--------+---------------+---------------+

To represent it in pseudocode, it’s like:

if (o->structureID == 42)
 v = o->inlineStorage[0]
else
 v = slowGet(o, f)

Mono Proto

We might meet following cases:

class Foo {
 constructor(f) {
   this.f = f;
 }

 getF() {
   return this.f;
 }
}

var o = new Foo(42);
var tmp = o.getF();

We might generate multiple objects from Foo. So we have mono caching. That is, when multiple objects share the same properties(here we assume additional structureID 32, 43 and 44), they use same inline caching(same offset):

// tmp = o.getF
if (o->structureID == 42 && P1->structureID == 43 && P2->structureID == 44)
 return P2->inlineStorage[];
else
 slowGet(o, getF);

The above code is for getting an attribute. When we try to add a property(o.newField = 123) for thousands of times:

if (o->structureID == 42 && P1->structureID == 43
 && P2->structureID == 44) {
 o->inlineStorage[] = value;
 o->structureID = 100;
}
else
 slowPut(o, newField, value)

In JavaScript, the prototype is reused in OOP. So the new prototype will share same optimized code. When the property in one of the prototype has changed. It will trigger watchpointFire to change optimized code. In each optimized code, there is a watchpoint inserted to receive such messages of falling back:

                                 +--------------+
                                 | structure 44 |
                                 +-----------+--+
                                             |
                                 +-----------+--+
          +--------------------->+ prototype P2 |
          |                      +------+-------+
          |                             
          |                             
+---------+-----+           +--------------+
| structure 666 |           | structure 43 |
+--------------++           +--------------+
               +---------+             
   (new structure field) |             (obsoleted)
+--------------+    +----+--------------+
| structure 42 +--->+ prototype P1      +
+--------------+    +-------------------+

In our example, the prototype P1 represents structure 43 and derive from structure 42. Now, we add P1.thingy = “boom”;. The old structure is obsoleted and we used 666 instead.

inline caching

var s1 = {f: 1, g: 2}
var s2 = {f: 1, g: 2}
var s3 = {g: 1, f: 2}
function foo(o) {
 return o.f
}

for (var i = 0; i < 10000; ++i) {
 foo({f: 1, g: 2})
 foo({f: 1, g: 2, h:3})
 foo({g: 2, f: 1})
}

Solution:

if (o->structureID == S1 || o->structureID == S2)
 o->inlineStorage[0];
else if (o->structureID == S3)
 o->inlineStorage[1];
else
 slowGet(o, f);

Watchpoint

What is watchpoint

Properly Trigger Watchpoint

In DFG stage, watchpoint(CodeBlockJettisoningWatchpoint) will fire depend on different structure. To trigger WatchpointSet::fireAll, we need to use removePropertyTransition.(In 35C3 CTF, they use thisObject->setStructure(vm, previous);, the legacy watchpoint still stay there). WatchPointSet is dependent on structure index. WatchPointSet is indexed by structure object. And structure object is depend on structure ID. But still, the butterfly pointer remains the same. We need to change both to the JITed function via WatchPointSet.

clobberWorld

The Data Flow Graph (DFG) JIT engine applies the same type of compiler optimizations that you’d see in any other compiler. However, it also needs to make sure that it is safe to apply certain optimizations after a given operation has been executed.

For example, it is unsafe to remove bounds checks around an operation that could change the size of the underlying buffer. One of the ways this is performed within DFG is within a file called DFGAbstractInterpreterInlines.h, which contains a method named executeEffects responsible for changing program state based on the operation and arguments.

The way to state that an operation is potentially dangerous to prevent later optimizations is to call a function called clobberWorld which, among other things, will break all assumptions about the types of all Arrays within the graph.