The transmission of evaluative or corrective information about an action, event, or process to the original or controlling source; also : the information so transmitted
source: Merriam-Webster
The transmission of evaluative or corrective information about an action, event, or process to the original or controlling source; also : the information so transmitted
source: Merriam-Webster
Run for a while
Gather feedback with ICs
Slot | Type | Value |
---|---|---|
1 | LOAD | MONO |
2 | CALL | UNINIT |
... | ... | ... |
Feedback Vector
Slot | Type | Value |
---|---|---|
1 | LOAD | MONO |
2 | CALL | UNINIT |
... | ... | ... |
Feedback Vector
Slot | Type | Value |
---|---|---|
1 | LOAD | MONO |
2 | CALL | UNINIT |
... | ... | ... |
Map M
x: 1
let o = { x: 1 };
o.y = 2;
Map M
x: 1
Map N
x: 1
y: 2
let o = { x: 1 };
o.y = 2;
IC Slot | IC Type | Value |
---|---|---|
1 | LOAD | MONO |
2 | CALL | UNINIT |
... | ... | ... |
Every function has a Feedback Vector. It's just an array that holds state for each IC.
function processLogFile(fileName) {
this.collectEntries = true;
this.lastLogFileName_ = fileName;
var line;
while (line = readline()) {
this.processLogLine(line);
}
print();
print("Load: " + this.LoadIC);
print("Store: " + this.StoreIC);
...
}
IC Slot | IC Type | State |
---|---|---|
... | ... | ... |
26 | LOAD | MONO(M) |
27 | LOAD | UNINITIALIZED |
Uninitialized
Premonomorphic
Monomorphic
Polymorphic
Megamorphic
Slow
a[b]; // where b is not a number
// and changes
o[i] = x; // where o is sometimes
// a typed array and
// sometimes a normal array.
this.name = "wut"; // where name is an
// accessor property
// with no setter.
...
function load(a) {
return a.key;
}
// pseudo-code for the LOAD IC:
if (a.map == vector[slot].map) {
return valueAtOffset(a, vector[slot].offset);
} else {
// Hmpf. Go to the Runtime for an hour
// or three.
}
function load(a) {
return a.key;
}
; assembly level in the IC
mov ebx, [ebx+edx*4] ; ebx = vector[slot]
cmp ebx, [a] ; is ebx == a.map?
jne &miss ; if not, MISS
mov eax, [a+0xc] ; return a.key
ret
miss: ; ah, jeesh
call ReallySlowC++Thing
function load(a) {
return a.key;
}
; assembly level optimized code
cmp [a], 0x4830eee0 ; is a.map the one we saw?
jne &deopt ; if not, DEOPTIMIZE
mov eax, [a+0xc] ; return a.key
ret
deopt: ; ah, double jeesh
call TearDownTheWorld
Let's look at a simple property load.
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Here is the AST from parsing:
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
// Run with --print-ast
FUNC
. NAME "load"
. PARAMS
. . VAR "a"
. RETURN
. . PROPERTY Slot(0) at 29
. . . VAR PROXY parameter[0] "a"
. . . NAME key
Here is the AST from parsing:
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
// Run with --print-ast
FUNC
. NAME "load"
. PARAMS
. . VAR "a"
. RETURN
. . PROPERTY Slot(0) at 29
. . . VAR PROXY parameter[0] "a"
. . . NAME key
Slot | Type |
---|---|
0 | LOAD_IC |
Here is the feedback vector specification
Here is the bytecode:
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Slot | Type |
---|---|
0 | LOAD_IC |
Here is the feedback vector specification:
"load" -- Parameter count 2
Frame size 0
StackCheck
Nop
LdaNamedProperty a0, [0], [3] // "key"
Return
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Slot | Type | Value |
---|---|---|
0 | LOAD_IC |
Here is the feedback vector:
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Slot | Type | Value |
---|---|---|
0 | LOAD_IC |
We execute the LoadIC for "a.key"...
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Slot | Type | Value |
---|---|---|
0 | LOAD_IC | MONO(o.map) |
Return the answer & remember the Map.
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Slot | Type | Value |
---|---|---|
0 | LOAD_IC | MONO(o.map) |
Now with a different object...
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Slot | Type | Value |
---|---|---|
0 | LOAD_IC | MONO(o.map) |
Is the map the same?
o1.map == o.map?
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Slot | Type | Value |
---|---|---|
0 | LOAD_IC | MONO(o.map) |
We remain monomorphic.
Yes
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Slot | Type | Value |
---|---|---|
0 | LOAD_IC | MONO(o.map) |
The Runtime Profiler asks:
(Run with --nouse-osr to ensure we optimize load(), and not the whole script)
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Slot | Type | Value |
---|---|---|
0 | LOAD_IC | MONO(o.map) |
The Runtime Profiler asks:
// --trace-opt output
[marking <JSFunction load> for
optimized recompilation,
reason: small function,
ICs with typeinfo: 1/1 (100%),
generic ICs: 0/1 (0%)]
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
Slot | Type | Value |
---|---|---|
0 | LOAD_IC | MONO(o.map) |
The Runtime Profiler asks:
// --trace-opt output
[marking <JSFunction load> for
optimized recompilation,
reason: small function,
ICs with typeinfo: 1/1 (100%),
generic ICs: 0/1 (0%)]
// load(a) - Turbofanned
push ebp // Build frame
mov ebp,esp //
push esi //
push edi //
mov eax,[ebp+0x8] // eax = a
test al,0x1 // is a an object?
jz DEOPT_0 // If not, deoptimize
mov ecx,[eax-0x1] // ecx = a.map
mov edx,0x37e0b4ad // edx = o.map
jnz DEOPT_1 // if not same, deopt.
mov eax,[eax+0xb] // return a.key!
mov esp,ebp // Tear down frame
pop ebp //
ret 0x8 // return for realz
...
DEOPT_0: call 0x52d06000 // Sad!
DEOPT_1: call 0x52d0600a // Very sad!
// load(a) - Turbofanned
push ebp // Build frame
mov ebp,esp //
push esi //
push edi //
mov eax,[ebp+0x8] // eax = a
test al,0x1 // is a an object?
jz DEOPT_0 // If not, deoptimize
mov ecx,[eax-0x1] // ecx = a.map
mov edx,0x37e0b4ad // edx = o.map
jnz DEOPT_1 // if not same, deopt.
mov eax,[eax+0xb] // return a.key!
mov esp,ebp // Tear down frame
pop ebp //
ret 0x8 // return for realz
...
DEOPT_0: call 0x52d06000 // Sad!
DEOPT_1: call 0x52d0600a // Very sad!
Boilerplate
// load(a) - Turbofanned
push ebp // Build frame
mov ebp,esp //
push esi //
push edi //
mov eax,[ebp+0x8] // eax = a
test al,0x1 // is a an object?
jz DEOPT_0 // If not, deoptimize
mov ecx,[eax-0x1] // ecx = a.map
mov edx,0x37e0b4ad // edx = o.map
jnz DEOPT_1 // if not same, deopt.
mov eax,[eax+0xb] // return a.key!
mov esp,ebp // Tear down frame
pop ebp //
ret 0x8 // return for realz
...
DEOPT_0: call 0x52d06000 // Sad!
DEOPT_1: call 0x52d0600a // Very sad!
Boilerplate
Object check
// load(a) - Turbofanned
push ebp // Build frame
mov ebp,esp //
push esi //
push edi //
mov eax,[ebp+0x8] // eax = a
test al,0x1 // is a an object?
jz DEOPT_0 // If not, deoptimize
mov ecx,[eax-0x1] // ecx = a.map
mov edx,0x37e0b4ad // edx = o.map
jnz DEOPT_1 // if not same, deopt.
mov eax,[eax+0xb] // return a.key!
mov esp,ebp // Tear down frame
pop ebp //
ret 0x8 // return for realz
...
DEOPT_0: call 0x52d06000 // Sad!
DEOPT_1: call 0x52d0600a // Very sad!
Boilerplate
Object check
Map check
// load(a) - Turbofanned
push ebp // Build frame
mov ebp,esp //
push esi //
push edi //
mov eax,[ebp+0x8] // eax = a
test al,0x1 // is a an object?
jz DEOPT_0 // If not, deoptimize
mov ecx,[eax-0x1] // ecx = a.map
mov edx,0x37e0b4ad // edx = o.map
jnz DEOPT_1 // if not same, deopt.
mov eax,[eax+0xb] // return a.key!
mov esp,ebp // Tear down frame
pop ebp //
ret 0x8 // return for realz
...
DEOPT_0: call 0x52d06000 // Sad!
DEOPT_1: call 0x52d0600a // Very sad!
Boilerplate
Object check
Map check
The actual load!
The more references there are to "a" downwind of the feedback, the greater the benefit of knowing the object's class.
Downwind includes inlined functions.
cmp eax, 0x43501230 // is this the map?
je load_map_1 // yes, handle it
cmp eax, 0x99503210 // how about this one?
je load_map_2 // han'l it different-style
jmp DEOPT // Oh jeesh
load_map_1: mov eax, [edx+0xc]
ret
load_map_2: mov eax, [edx+0x10]
ret
It's tempting to just compile generic code, and allow the IC to learn through the feedback vector.
This way, you at least won't deoptimize...
But in practice, the less efficient code over many iterations costs more than the cost of deoptimization and reoptimization.
So we deoptimize unconditionally. And save time compiling because we don't bother with that whole block.
You lose touch with the Feedback Vector, and have nowhere to put the new map.
Eventually V8 stops reoptimizing.
Slot | Type | Value |
---|---|---|
0 | LOAD_IC | MONO(o.map) |
// load(a) - Turbofanned
...
mov ecx,[eax-0x1] // ecx = a.map
mov edx,0x37e0b4ad // edx = o.map
jnz DEOPT_1 // if not same, deopt
mov eax,[eax+0xb] // return a.key
...
Feedback Vector not being used
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
load({ key: "usb", name: "francis" });
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
load({ key: "usb", name: "francis" });
Slot | Type | Value |
---|---|---|
0 | LOAD_IC | MONO(o.map) |
// load(a) - Turbofanned
...
mov ecx,[eax-0x1] // ecx = a.map
mov edx,0x37e0b4ad // edx = o.map
jnz DEOPT_1 // if not same, deopt
mov eax,[eax+0xb] // return a.key
...
The map will be different...
Slot | Type | Value |
---|---|---|
1 | LOAD | MONO |
2 | CALL | UNINIT |
... | ... | ... |
function load(a) {
return a.key;
}
var o = { key: "usb" };
var o1 = { key: "port" };
for (var i = 0; i < 100000; i++) {
load(o);
load(o1);
}
load({ key: "usb", name: "francis" });
Slot | Type | Value |
---|---|---|
0 | LOAD_IC | POLY(o.map, new_map) |
The map will be different...