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.

@msankhala
Copy link

Can you please explain "Start of a call object's memory", "Reference to invoked function" and "Number of references in this call object". Sorry if i seems like a dumb, i am newbie in javascript. i know in javascript every function is object, what is difference between call object and invoked function in this case? Aren't they same.

@mraleph
Copy link

mraleph commented Jan 30, 2013

@msankhala If I'm guessing @justinbmeyer intentions right then call object is usually called activation record.

Function and activation record are not the same because every time you call a function you get a new activation record that exists until the call has returned. There can be multiple activation records for the same function if it is called recursively.

@CotunaAurelian
Copy link

I have a question. What happens when I have another encoding? For instance I have a string written in Unicode. I'm asking because I'm curious where it is stored the encoding type. Thanks,

@mraleph
Copy link

mraleph commented Jan 31, 2013

@CotunaAurelian string encoding is stored in the hidden class (aka map internally in V8). strings are either one byte (latin1) or two byte (utf16) encoded, but a particular encoding is not visible to JavaScript code. ECMA-262 5th actually specifies that string is a sequence of 16bit integers

@justinbmeyer
Copy link
Author

@mraleph,

(Question Summary: If the native call stack is used, and a function's activation frame is popped when the function returns, how do inner functions "walk up" to parent function's activation frame to get a value).

Thank you VERY much for your explanation! I thought I had some time when I started this post to work on my JSConf talk, but that time evaporated, so I am resuming it now. I owe you a dinner / beer / etc. If you are coming to JSConf, I can pay that debt sooner than later.

By call object I'm referring to whatever mechanism allows closures to work. For the following example:

var counter = function(){
  var i = 0;
  return function(){
    return i++;
  }
}
index = counter()
index()

Some record (possibly stack frame / activation record) needs to exist for the inner function to find the value of i. I've assumed that an inner function's call object (or activation record) has a reference to its parent call object (or activation record), that i is retrieved by checking the current call object for i and walking up to the parent call objects until i is found.

I'm not sure how this would work with the native call stack. It's my (mis)understanding that the call stack is popped when the current function has completed running. If that's true, then I'm missing something else. Perhaps when a function is run and its activation record is created, the new activation record gets all of its parent references?

Thanks again for your help!

@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