Skip to content

Instantly share code, notes, and snippets.

@justinbmeyer
Last active June 29, 2024 16:00
Show Gist options
  • Save justinbmeyer/4662050 to your computer and use it in GitHub Desktop.
Save justinbmeyer/4662050 to your computer and use it in GitHub Desktop.
JS Memory

JavaScript Code

var str = "hi";

Memory allocation:

Address Value Description
...... ...
0x1001 call object Start of a call object's memory
0x1002 0x00af Reference to invoked function
0x1003 1 Number of references in this call object
0x1004 str Name of variable (in practice would not be in a single address)
0x1005 0x1001 Memory address of "hi"
...... ...
0x2001 string type identifier
0x2002 2 number of bytes
0x2003 h byte of first character
0x2004 i byte of second character

Explanation

When JS runs: var str = "hi"; by calling some function, it first hoists all variable declarations and creates a spot in memory for the variable. This might leave memory something like:

Address Value Description
...... ...
0x1001 call object Start of a call object's memory
0x1002 0x00af Reference to invoked function
0x1003 1 Number of references in this call object
0x1004 str Name of variable
0x1005 empty
...... ...

In practice, the name of the variable, str, would not be held in a single memory address. Also, the variable names and locations would not be stored in a fixed-memory array (possibly in a hash-table).

Next, the string "hi" would be created in memory like:

Address Value Description
...... ...
0x2001 string type identifier
0x2002 2 number of bytes
0x2003 h byte of first character
0x2004 i byte of second character

Finally, the pointer address of str would be set to the memory address of "hi" (0x2001), leaving memory as indicated at the top of the page.

@justinbmeyer
Copy link
Author

@mraleph

I doubt that the new activation record gets all of its parent references, otherwise Chrome's and FF's dev tools would not give "Scope Variables" grouped by closure. Each "closure" contains the same information as what I was calling call object.

Is this structure part of the native call stack? How is it that they exist after the function runs? Thanks.

@mraleph
Copy link

mraleph commented May 21, 2013

If we are talking about V8 then part of the activation record that needs to survive after function returns is "detached" from the stack and is allocated a normal heap in a structure called context. Up in my first comment there is a slot for it on the native stack.

After execution just entered counter native stack looks like this:

...
receiver this (global object)
retaddr return address into the caller
caller ebp frame pointer for the caller ← ebp points here
function tagged pointer to the invoked function (counter)
context tagged pointer to the function context (empty context) ← esp points here

as you can see there is no space reserved for i unlike in the previous example. Where does it go? Next thing that happens just after entering this code V8 will create a local context object that looks like this:

Lets call it Context_5fff02b (random digits at the end to reflect that each time we enter function counter we allocate a new one on the heap):

...
map pointer to a type descriptor (aka map, hidden class)
closure pointer to a function that created this context counter
previous pointer to a previous context used by with contexts null
extension dynamic scope data for with or eval null
global object global object for quick access
i place for a variable i undefined

You can see there are quite a few internal fields because contexts are used for multiple purposes. You can look through source code to get a grasp of the details involved.

After local context was created the stack slot where context is stored will be updated:

...
receiver this (global object)
retaddr return address into the caller
caller ebp frame pointer for the caller ← ebp points here
function tagged pointer to the invoked function (counter)
context tagged pointer to the function context ( Context_5fff02b ) ← esp points here

This context pointer will be used when working with variable i inside function counter and all closures allocated in it will get this context as its outer context.

Inside the function code current context is always cached in a register esi so storing 0 into i looks like this:

mov [esi + (5 * 4 - 1)], 0

I decomposed immediate offset into parts to make it clearer where each came from (5th field, 4 bytes per field, -1 to untag tagged pointer) generated code will just say offset 29.

Allocated function will approximately look like this (in fact there are more fields in closures, I skip irrelevant ones)

...
map pointer to a type descriptor (aka map, hidden class)
code pointer to the code
context pointer to the outer context Context_5fff02b

Hope this helps.

@martianmartian
Copy link

i would really appreciate it if someone can help me to check my understanding of this whole process. i wrote them down here, here, and here. Justing wondering coz these things have been bugging me for a while.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment