Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can anyone explain V8 bytecode LdaTheHole?

I know the V8 bytecode "LdaUndefined" load the constant "undefined" into the accumulator register.
Then what does "LdaTheHole" load into the accumulator?
Maybe "TheHole"?
What is the meaning of the "TheHole"?
Thank you.

like image 866
ZhefengJin Avatar asked Apr 25 '20 03:04

ZhefengJin


1 Answers

(V8 developer here.)

what does "LdaTheHole" load into the accumulator? Maybe "TheHole"?

Yes.

What is the meaning of the "TheHole"?

As Mark's answer correctly guesses, it's an internal sentinel that means "no value here". The reason it's needed is not performance (or allocations), though; it's needed to get the correct behavior in a few situations.

For an array-based example, consider this code:

var a = [1, 2, 3];           // (1)
a.__proto__ = [11, 22, 33];  // (2)
delete a[1];                 // (3)
console.log(a[1]);           // (4)

This will print 22, because a will conceptually have a "hole" at index [1], and you can "see its prototype through that hole", so to speak. (You could create the same situation if you replaced lines (1) and (3) with var a = [1, , 3].) If a had the default prototype, then instead of delete a[1] you could write a[1] = undefined, or that's what delete could do under the hood, and the result would be the same. But with a custom prototype, there is a difference: if you wrote a[1] = undefined instead of line (3), then line (4) would print undefined. We need the "hole" sentinel to distinguish whether element [1] is undefined or not present at all.

There are a few other cases where distinguishing between "no value" and "defined to be 'undefined'" is useful or necessary, for example for the "temporal dead-zone" of block-scoped variables. Old-style var-variables implicitly get their declarations hoisted, whereas accessing new-style let-variables before their definition is an error. The correct behavior is:

var a = 1; print(a);  // 1
let b = 1; print(b);  // 1
print(c); var c = 1;  // undefined
print(d); let d = 1;  // ReferenceError: Cannot access 'd' before initialization
print(e);             // ReferenceError: e is not defined

So the print statement (which is just an example; the same holds for many other operations) must do three different things depending on the surrounding code. V8 accomplishes this by internally transforming the lines to:

let c = undefined; print(c); c = 1;
let d = <TheHole>; print(d); d = 1;

That way, when a variable that's being accessed has the "hole" as its value, the system knows that something's wrong, so in this case rather than loading the variable's value and passing it to the print function, it knows that this must be a block-scoped variable being accessed before its definition, so it produces an appropriate error message.

If you get tempted to play with this stuff, be aware that "the hole" is purely an internal sentinel. It can never "leak" out to JavaScript. There is no way to check from your code that a variable or property currently has this value. It's a hidden implementation detail -- engines could do things differently; it just so happens that having an internal "hole" sentinel is a reasonably elegant and efficient way to solve a bunch of problems, so V8 chooses to do it that way.

like image 88
jmrk Avatar answered Oct 02 '22 19:10

jmrk