An open API service indexing awesome lists of open source software.

https://github.com/danbev/learning-v8

Project for learning V8 internals
https://github.com/danbev/learning-v8

Last synced: about 1 year ago
JSON representation

Project for learning V8 internals

Awesome Lists containing this project

README

          

## Learning Google V8
The sole purpose of this project is to aid me in leaning Google's V8 JavaScript engine.

### Contents
1. [Introduction](./notes/intro.md)
1. [Address](#address)
1. [TaggedImpl](#taggedimpl)
1. [Object](#object)
1. [Handle](#handle)
1. [FunctionTemplate](#functiontemplate)
1. [ObjectTemplate](#objecttemplate)
1. [Small Integers](#small-integers)
1. [String types](./notes/string.md)
1. [Roots](#roots)
1. [Heap](./notes/heap.md)
1. [Builtins](#builtins)
1. [Compiler pipeline](#compiler-pipeline)
1. [CodeStubAssembler](#codestubassembler)
1. [Torque](#torque)
1. [WebAssembly](#webassembly)
1. [Promises](./notes/promises.md)
1. [Snapshots](./notes/snapshots.md)
1. [V8 Build artifacts](#v8-build-artifacts)
1. [V8 Startup walkthrough](#startup-walk-through)
1. [Building V8](#building-v8)
1. [Contributing a change](#contributing-a-change)
1. [Debugging](#debugging)
1. [Building chromium](#building-chromium)
1. [Goma chromium](#goma)
1. [EcmaScript notes](./notes/ecmaspec.md)
1. [GN notes](./notes/gn.md)

### Isolate
An Isolate is an independant copy of the V8 runtime which includes its own heap.
Two different Isolates can run in parallel and can be seen as entirely different
sandboxed instances of a V8 runtime.

### Context
To allow separate JavaScript applications to run in the same isolate a context
must be specified for each one. This is to avoid them interfering with each
other, for example by changing the builtin objects provided.

### Template
This is the super class of both ObjecTemplate and FunctionTemplate. Remember
that in JavaScript a function can have fields just like objects.

```c++
class V8_EXPORT Template : public Data {
public:
void Set(Local name, Local value,
PropertyAttribute attributes = None);
void SetPrivate(Local name, Local value,
PropertyAttribute attributes = None);
V8_INLINE void Set(Isolate* isolate, const char* name, Local value);

void SetAccessorProperty(
Local name,
Local getter = Local(),
Local setter = Local(),
PropertyAttribute attribute = None,
AccessControl settings = DEFAULT);
```
The `Set` function can be used to have an name and a value set on an instance
created from this template.
The `SetAccessorProperty` is for properties that are get/set using functions.

```c++
enum PropertyAttribute {
/** None. **/
None = 0,
/** ReadOnly, i.e., not writable. **/
ReadOnly = 1 << 0,
/** DontEnum, i.e., not enumerable. **/
DontEnum = 1 << 1,
/** DontDelete, i.e., not configurable. **/
DontDelete = 1 << 2
};

enum AccessControl {
DEFAULT = 0,
ALL_CAN_READ = 1,
ALL_CAN_WRITE = 1 << 1,
PROHIBITS_OVERWRITING = 1 << 2
};
```

### ObjectTemplate
These allow you to create JavaScript objects without a dedicated constructor.
When an instance is created using an ObjectTemplate the new instance will have
the properties and functions configured on the ObjectTemplate.

This would be something like:
```js
const obj = {};
```
This class is declared in include/v8.h and extends Template:
```c++
class V8_EXPORT ObjectTemplate : public Template {
...
}
class V8_EXPORT Template : public Data {
...
}
class V8_EXPORT Data {
private:
Data();
};
```
We create an instance of ObjectTemplate and we can add properties to it that
all instance created using this ObjectTemplate instance will have. This is done
by calling `Set` which is member of the `Template` class. You specify a
Local for the property. `Name` is a superclass for `Symbol` and `String`
which can be both be used as names for a property.

The implementation for `Set` can be found in `src/api/api.cc`:
```c++
void Template::Set(v8::Local name, v8::Local value, v8::PropertyAttribute attribute) {
...

i::ApiNatives::AddDataProperty(isolate, templ, Utils::OpenHandle(*name),
value_obj,
static_cast(attribute));
}
```

There is an example in [objecttemplate_test.cc](./test/objecttemplate_test.cc)

### FunctionTemplate
Is a template that is used to create functions and like ObjectTemplate it inherits
from Template:
```c++
class V8_EXPORT FunctionTemplate : public Template {
}
```
Rememeber that a function in javascript can have properties just like object.

There is an example in [functiontemplate_test.cc](./test/functiontemplate_test.cc)

An instance of a function template can be created using:
```c++
Local ft = FunctionTemplate::New(isolate_, function_callback, data);
Local function = ft->GetFunction(context).ToLocalChecked();
```
And the function can be called using:
```c++
MaybeLocal ret = function->Call(context, recv, 0, nullptr);
```
Function::Call can be found in `src/api/api.cc`:
```c++
bool has_pending_exception = false;
auto self = Utils::OpenHandle(this);
i::Handle recv_obj = Utils::OpenHandle(*recv);
i::Handle* args = reinterpret_cast*>(argv);
Local result;
has_pending_exception = !ToLocal(
i::Execution::Call(isolate, self, recv_obj, argc, args), &result);
```
Notice that the return value of `Call` which is a `MaybeHandle` will be
passed to ToLocal which is defined in `api.h`:
```c++
template
inline bool ToLocal(v8::internal::MaybeHandle maybe,
Local* local) {
v8::internal::Handle handle;
if (maybe.ToHandle(&handle)) {
*local = Utils::Convert(handle);
return true;
}
return false;
```
So lets take a look at `Execution::Call` which can be found in `execution/execution.cc`
and it calls:
```c++
return Invoke(isolate, InvokeParams::SetUpForCall(isolate, callable, receiver, argc, argv));
```
`SetUpForCall` will return an `InvokeParams`.
TODO: Take a closer look at InvokeParams.
```c++
V8_WARN_UNUSED_RESULT MaybeHandle Invoke(Isolate* isolate,
const InvokeParams& params) {
```
```c++
Handle receiver = params.is_construct
? isolate->factory()->the_hole_value()
: params.receiver;
```
In our case `is_construct` is false as we are not using `new` and the receiver,
the `this` in the function should be set to the receiver that we passed in. After
that we have `Builtins::InvokeApiFunction`
```c++
auto value = Builtins::InvokeApiFunction(
isolate, params.is_construct, function, receiver, params.argc,
params.argv, Handle::cast(params.new_target));
```

```c++
result = HandleApiCallHelper(isolate, function, new_target,
fun_data, receiver, arguments);
```

`api-arguments-inl.h` has:
```c++
FunctionCallbackArguments::Call(CallHandlerInfo handler) {
...
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
FunctionCallbackInfo info(values_, argv_, argc_);
f(info);
return GetReturnValue(isolate);
}
```
The call to f(info) is what invokes the callback, which is just a normal
function call.

Back in `HandleApiCallHelper` we have:
```c++
Handle result = custom.Call(call_data);

RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object);
```
`RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION` expands to:
```c++
Handle result = custom.Call(call_data);
do {
Isolate* __isolate__ = (isolate);
((void) 0);
if (__isolate__->has_scheduled_exception()) {
__isolate__->PromoteScheduledException();
return MaybeHandle();
}
} while (false);
```
Notice that if there was an exception an empty object is returned.
Later in `Invoke` in `execution.cc`a:
```c++
auto value = Builtins::InvokeApiFunction(
isolate, params.is_construct, function, receiver, params.argc,
params.argv, Handle::cast(params.new_target));
bool has_exception = value.is_null();
if (has_exception) {
if (params.message_handling == Execution::MessageHandling::kReport) {
isolate->ReportPendingMessages();
}
return MaybeHandle();
} else {
isolate->clear_pending_message();
}
return value;
```
Looking at this is looks like passing back an empty object will cause an
exception to be triggered?

### Address
`Address` can be found in `include/v8-internal.h`:

```c++
typedef uintptr_t Address;
```
`uintptr_t` is an optional type specified in `cstdint` and is capable of storing
a data pointer. It is an unsigned integer type that any valid pointer to void
can be converted to this type (and back).

### TaggedImpl
This class is declared in `src/objects/tagged-impl.h and has a single private
member which is declared as:
```c++
public
constexpr StorageType ptr() const { return ptr_; }
private:
StorageType ptr_;
```
An instance can be created using:
```c++
i::TaggedImpl tagged{};
```
Storage type can also be `Tagged_t` which is defined in globals.h:
```c++
using Tagged_t = uint32_t;
```
It looks like it can be a different value when using pointer compression.

See [tagged_test.cc](./test/tagged_test.cc) for an example.

### Object
This class extends TaggedImpl:
```c++
class Object : public TaggedImpl {
```
An Object can be created using the default constructor, or by passing in an
Address which will delegate to TaggedImpl constructors. Object itself does
not have any members (apart from `ptr_` which is inherited from TaggedImpl that is).
So if we create an Object on the stack this is like a pointer/reference to
an object:
```
+------+
|Object|
|------|
|ptr_ |---->
+------+
```
Now, `ptr_` is a StorageType so it could be a `Smi` in which case it would just
contains the value directly, for example a small integer:
```
+------+
|Object|
|------|
| 18 |
+------+
```
See [object_test.cc](./test/object_test.cc) for an example.

### ObjectSlot
```c++
i::Object obj{18};
i::FullObjectSlot slot{&obj};
```

```
+----------+ +---------+
|ObjectSlot| | Object |
|----------| |---------|
| address | ---> | 18 |
+----------+ +---------+
```
See [objectslot_test.cc](./test/objectslot_test.cc) for an example.

### Maybe
A Maybe is like an optional which can either hold a value or nothing.
```c++
template
class Maybe {
public:
V8_INLINE bool IsNothing() const { return !has_value_; }
V8_INLINE bool IsJust() const { return has_value_; }
...

private:
bool has_value_;
T value_;
}
```
I first thought that name `Just` was a little confusing but if you read this
like:
```c++
bool cond = true;
Maybe maybe = cond ? Just(10) : Nothing();
```
I think it makes more sense. There are functions that check if the Maybe is
nothing and crash the process if so. You can also check and return the value
by using `FromJust`.

The usage of Maybe is where api calls can fail and returning Nothing is a way
of signaling this.

See [maybe_test.cc](./test/maybe_test.cc) for an example.

### MaybeLocal
```c++
template
class MaybeLocal {
public:
V8_INLINE MaybeLocal() : val_(nullptr) {}
V8_INLINE Local ToLocalChecked();
V8_INLINE bool IsEmpty() const { return val_ == nullptr; }
template
V8_WARN_UNUSED_RESULT V8_INLINE bool ToLocal(Local* out) const {
out->val_ = IsEmpty() ? nullptr : this->val_;
return !IsEmpty();
}

private:
T* val_;
```
`ToLocalChecked` will crash the process if `val_` is a nullptr. If you want to
avoid a crash one can use `ToLocal`.

See [maybelocal_test.cc](./test/maybelocal_test.cc) for an example.

### Data
Is the super class of all objects that can exist the V8 heap:
```c++
class V8_EXPORT Data {
private:
Data();
};
```

### Value
Value extends `Data` and adds a number of methods that check if a Value
is of a certain type, like `IsUndefined()`, `IsNull`, `IsNumber` etc.
It also has useful methods to convert to a Local, for example:
```c++
V8_WARN_UNUSED_RESULT MaybeLocal ToNumber(Local context) const;
V8_WARN_UNUSED_RESULT MaybeLocal ToNumber(Local context) const;
...
```

### Handle
A Handle is similar to a Object and ObjectSlot in that it also contains
an Address member (called `location_` and declared in `HandleBase`), but with the
difference is that Handles acts as a layer of abstraction and can be relocated
by the garbage collector.
Can be found in `src/handles/handles.h`.

```c++
class HandleBase {
...
protected:
Address* location_;
}
template
class Handle final : public HandleBase {
...
}
```

```
+----------+ +--------+ +---------+
| Handle | | Object | | int |
|----------| +-----+ |--------| |---------|
|*location_| ---> |&ptr_| --> | ptr_ | -----> | 5 |
+----------+ +-----+ +--------+ +---------+
```
```console
(gdb) p handle
$8 = { = {location_ = 0x7ffdf81d60c0}, }
```
Notice that `location_` contains a pointer:
```console
(gdb) p /x *(int*)0x7ffdf81d60c0
$9 = 0xa9d330
```
And this is the same as the value in obj:
```console
(gdb) p /x obj.ptr_
$14 = 0xa9d330
```
And we can access the int using any of the pointers:
```console
(gdb) p /x *value
$16 = 0x5
(gdb) p /x *obj.ptr_
$17 = 0x5
(gdb) p /x *(int*)0x7ffdf81d60c0
$18 = 0xa9d330
(gdb) p /x *(*(int*)0x7ffdf81d60c0)
$19 = 0x5
```

See [handle_test.cc](./test/handle_test.cc) for an example.

### HandleScope
Contains a number of Local/Handle's (think pointers to objects but is managed
by V8) and will take care of deleting the Local/Handles for us. HandleScopes
are stack allocated

When ~HandleScope is called all handles created within that scope are removed
from the stack maintained by the HandleScope which makes objects to which the
handles point being eligible for deletion from the heap by the GC.

A HandleScope only has three members:
```c++
internal::Isolate* isolate_;
internal::Address* prev_next_;
internal::Address* prev_limit_;
```

Lets take a closer look at what happens when we construct a HandleScope:
```c++
v8::HandleScope handle_scope{isolate_};
```
The constructor call will end up in `src/api/api.cc` and the constructor simply
delegates to `Initialize`:
```c++
HandleScope::HandleScope(Isolate* isolate) { Initialize(isolate); }

void HandleScope::Initialize(Isolate* isolate) {
i::Isolate* internal_isolate = reinterpret_cast(isolate);
...
i::HandleScopeData* current = internal_isolate->handle_scope_data();
isolate_ = internal_isolate;
prev_next_ = current->next;
prev_limit_ = current->limit;
current->level++;
}
```
Every `v8::internal::Isolate` has member of type HandleScopeData:
```c++
HandleScopeData* handle_scope_data() { return &handle_scope_data_; }
HandleScopeData handle_scope_data_;
```
HandleScopeData is a struct defined in `src/handles/handles.h`:
```c++
struct HandleScopeData final {
Address* next;
Address* limit;
int level;
int sealed_level;
CanonicalHandleScope* canonical_scope;

void Initialize() {
next = limit = nullptr;
sealed_level = level = 0;
canonical_scope = nullptr;
}
};
```
Notice that there are two pointers (Address*) to next and a limit. When a
HandleScope is Initialized the current handle_scope_data will be retrieved
from the internal isolate. The HandleScope instance that is getting created
stores the next/limit pointers of the current isolate so that they can be restored
when this HandleScope is closed (see CloseScope).

So with a HandleScope created, how does a Local interact with this instance?

When a Local is created this will/might go through FactoryBase::NewStruct
which will allocate a new Map and then create a Handle for the InstanceType
being created:
```c++
Handle str = handle(Struct::cast(result), isolate());
```
This will land in the constructor Handlesrc/handles/handles-inl.h
```c++
template
Handle::Handle(T object, Isolate* isolate): HandleBase(object.ptr(), isolate) {}

HandleBase::HandleBase(Address object, Isolate* isolate)
: location_(HandleScope::GetHandle(isolate, object)) {}
```
Notice that `object.ptr()` is used to pass the Address to HandleBase.
And also notice that HandleBase sets its location_ to the result of HandleScope::GetHandle.

```c++
Address* HandleScope::GetHandle(Isolate* isolate, Address value) {
DCHECK(AllowHandleAllocation::IsAllowed());
HandleScopeData* data = isolate->handle_scope_data();
CanonicalHandleScope* canonical = data->canonical_scope;
return canonical ? canonical->Lookup(value) : CreateHandle(isolate, value);
}
```
Which will call `CreateHandle` in this case and this function will retrieve the
current isolate's handle_scope_data:
```c++
HandleScopeData* data = isolate->handle_scope_data();
Address* result = data->next;
if (result == data->limit) {
result = Extend(isolate);
}
```
In this case both next and limit will be 0x0 so Extend will be called.
Extend will also get the isolates handle_scope_data and check the current level
and after that get the isolates HandleScopeImplementer:
```c++
HandleScopeImplementer* impl = isolate->handle_scope_implementer();
```
`HandleScopeImplementer` is declared in `src/api/api.h`

HandleScope:CreateHandle will get the handle_scope_data from the isolate:
```c++
Address* HandleScope::CreateHandle(Isolate* isolate, Address value) {
HandleScopeData* data = isolate->handle_scope_data();
if (result == data->limit) {
result = Extend(isolate);
}
// Update the current next field, set the value in the created handle,
// and return the result.
data->next = reinterpret_cast

(reinterpret_cast
(result) + sizeof(Address));
*result = value;
return result;
}
```
Notice that `data->next` is set to the address passed in + the size of an
Address.

The destructor for HandleScope will call CloseScope.
See [handlescope_test.cc](./test/handlescope_test.cc) for an example.

### EscapableHandleScope
Local handles are located on the stack and are deleted when the appropriate
destructor is called. If there is a local HandleScope then it will take care
of this when the scope returns. When there are no references left to a handle
it can be garbage collected. This means if a function has a HandleScope and
wants to return a handle/local it will not be available after the function
returns. This is what EscapableHandleScope is for, it enable the value to be
placed in the enclosing handle scope to allow it to survive. When the enclosing
HandleScope goes out of scope it will be cleaned up.

```c++
class V8_EXPORT EscapableHandleScope : public HandleScope {
public:
explicit EscapableHandleScope(Isolate* isolate);
V8_INLINE ~EscapableHandleScope() = default;
template
V8_INLINE Local Escape(Local value) {
internal::Address* slot = Escape(reinterpret_cast(*value));
return Local(reinterpret_cast(slot));
}

template
V8_INLINE MaybeLocal EscapeMaybe(MaybeLocal value) {
return Escape(value.FromMaybe(Local()));
}

private:
...
internal::Address* escape_slot_;
};
```

From `api.cc`
```c++
EscapableHandleScope::EscapableHandleScope(Isolate* v8_isolate) {
i::Isolate* isolate = reinterpret_cast(v8_isolate);
escape_slot_ = CreateHandle(isolate, i::ReadOnlyRoots(isolate).the_hole_value().ptr());
Initialize(v8_isolate);
}
```
So when an EscapableHandleScope is created it will create a handle with the
hole value and store it in the `escape_slot_` which is of type Address. This
Handle will be created in the current HandleScope, and EscapableHandleScope
can later set a value for that pointer/address which it want to be escaped.
Later when that HandleScope goes out of scope it will be cleaned up.
It then calls Initialize just like a normal HandleScope would.

```c++
i::Address* HandleScope::CreateHandle(i::Isolate* isolate, i::Address value) {
return i::HandleScope::CreateHandle(isolate, value);
}
```
From `handles-inl.h`:
```c++
Address* HandleScope::CreateHandle(Isolate* isolate, Address value) {
DCHECK(AllowHandleAllocation::IsAllowed());
HandleScopeData* data = isolate->handle_scope_data();
Address* result = data->next;
if (result == data->limit) {
result = Extend(isolate);
}
// Update the current next field, set the value in the created handle,
// and return the result.
DCHECK_LT(reinterpret_cast

(result),
reinterpret_cast
(data->limit));
data->next = reinterpret_cast
(reinterpret_cast
(result) +
sizeof(Address));
*result = value;
return result;
}
```

When Escape is called the following happens (v8.h):
```c++
template
V8_INLINE Local Escape(Local value) {
internal::Address* slot = Escape(reinterpret_cast(*value));
return Local(reinterpret_cast(slot));
}
```
An the EscapeableHandleScope::Escape (api.cc):
```c++
i::Address* EscapableHandleScope::Escape(i::Address* escape_value) {
i::Heap* heap = reinterpret_cast(GetIsolate())->heap();
Utils::ApiCheck(i::Object(*escape_slot_).IsTheHole(heap->isolate()),
"EscapableHandleScope::Escape", "Escape value set twice");
if (escape_value == nullptr) {
*escape_slot_ = i::ReadOnlyRoots(heap).undefined_value().ptr();
return nullptr;
}
*escape_slot_ = *escape_value;
return escape_slot_;
}
```
If the escape_value is null, the `escape_slot` that is a pointer into the
parent HandleScope is set to the undefined_value() instead of the hole value
which is was previously, and nullptr will be returned. This returned
address/pointer will then be returned after being casted to T*.
Next, we take a look at what happens when the EscapableHandleScope goes out of
scope. This will call HandleScope::~HandleScope which makes sense as any other
Local handles should be cleaned up.

`Escape` copies the value of its argument into the enclosing scope, deletes alli
its local handles, and then gives back the new handle copy which can safely be
returned.

### HeapObject
TODO:

### Local
Has a single member `val_` which is of type pointer to `T`:
```c++
template class Local {
...
private:
T* val_
}
```
Notice that this is a pointer to T. We could create a local using:
```c++
v8::Local empty_value;
```

So a Local contains a pointer to type T. We can access this pointer using
`operator->` and `operator*`.

We can cast from a subtype to a supertype using Local::Cast:
```c++
v8::Local nr = v8::Local(v8::Number::New(isolate_, 12));
v8::Local val = v8::Local::Cast(nr);
```
And there is also the
```c++
v8::Local val2 = nr.As();
```

See [local_test.cc](./test/local_test.cc) for an example.

### PrintObject
Using _v8_internal_Print_Object from c++:
```console
$ nm -C libv8_monolith.a | grep Print_Object
0000000000000000 T _v8_internal_Print_Object(void*)
```
Notice that this function does not have a namespace.
We can use this as:
```c++
extern void _v8_internal_Print_Object(void* object);

_v8_internal_Print_Object(*((v8::internal::Object**)(*global)));
```
Lets take a closer look at the above:
```c++
v8::internal::Object** gl = ((v8::internal::Object**)(*global));
```
We use the dereference operator to get the value of a Local (*global), which is
just of type `T*`, a pointer to the type the Local:
```c++
template
class Local {
...
private:
T* val_;
}
```

We are then casting that to be of type pointer-to-pointer to Object.
```
gl** Object* Object
+-----+ +------+ +-------+
| |----->| |----->| |
+-----+ +------+ +-------+
```
An instance of `v8::internal::Object` only has a single data member which is a
field named `ptr_` of type `Address`:

`src/objects/objects.h`:
```c++
class Object : public TaggedImpl {
public:
constexpr Object() : TaggedImpl(kNullAddress) {}
explicit constexpr Object(Address ptr) : TaggedImpl(ptr) {}

#define IS_TYPE_FUNCTION_DECL(Type) \
V8_INLINE bool Is##Type() const; \
V8_INLINE bool Is##Type(const Isolate* isolate) const;
OBJECT_TYPE_LIST(IS_TYPE_FUNCTION_DECL)
HEAP_OBJECT_TYPE_LIST(IS_TYPE_FUNCTION_DECL)
IS_TYPE_FUNCTION_DECL(HashTableBase)
IS_TYPE_FUNCTION_DECL(SmallOrderedHashTable)
#undef IS_TYPE_FUNCTION_DECL
V8_INLINE bool IsNumber(ReadOnlyRoots roots) const;
}
```
Lets take a look at one of these functions and see how it is implemented. For
example in the OBJECT_TYPE_LIST we have:
```c++
#define OBJECT_TYPE_LIST(V) \
V(LayoutDescriptor) \
V(Primitive) \
V(Number) \
V(Numeric)
```
So the object class will have a function that looks like:
```c++
inline bool IsNumber() const;
inline bool IsNumber(const Isolate* isolate) const;
```
And in src/objects/objects-inl.h we will have the implementations:
```c++
bool Object::IsNumber() const {
return IsHeapObject() && HeapObject::cast(*this).IsNumber();
}
```
`IsHeapObject` is defined in TaggedImpl:
```c++
constexpr inline bool IsHeapObject() const { return IsStrong(); }

constexpr inline bool IsStrong() const {
#if V8_HAS_CXX14_CONSTEXPR
DCHECK_IMPLIES(!kCanBeWeak, !IsSmi() == HAS_STRONG_HEAP_OBJECT_TAG(ptr_));
#endif
return kCanBeWeak ? HAS_STRONG_HEAP_OBJECT_TAG(ptr_) : !IsSmi();
}
```

The macro can be found in src/common/globals.h:
```c++
#define HAS_STRONG_HEAP_OBJECT_TAG(value) \
(((static_cast(value) & ::i::kHeapObjectTagMask) == \
::i::kHeapObjectTag))
```
So we are casting `ptr_` which is of type Address into type `Tagged_t` which
is defined in src/common/global.h and can be different depending on if compressed
pointers are used or not. If they are not supported it is the same as Address:
```
using Tagged_t = Address;
```

`src/objects/tagged-impl.h`:
```c++
template
class TaggedImpl {

StorageType ptr_;
}
```
The HeapObjectReferenceType can be either WEAK or STRONG. And the storage type
is `Address` in this case. So Object itself only has one member that is inherited
from its only super class and this is `ptr_`.

So the following is telling the compiler to treat the value of our Local,
`*global`, as a pointer (which it already is) to a pointer that points to
a memory location that adhers to the layout of an `v8::internal::Object` type,
which we know now has a `prt_` member. And we want to dereference it and pass
it into the function.
```c++
_v8_internal_Print_Object(*((v8::internal::Object**)(*global)));
```

### ObjectTemplate
But I'm still missing the connection between ObjectTemplate and object.
When we create it we use:
```c++
Local global = ObjectTemplate::New(isolate);
```
In `src/api/api.cc` we have:
```c++
static Local ObjectTemplateNew(
i::Isolate* isolate, v8::Local constructor,
bool do_not_cache) {
i::Handle struct_obj = isolate->factory()->NewStruct(
i::OBJECT_TEMPLATE_INFO_TYPE, i::AllocationType::kOld);
i::Handle obj = i::Handle::cast(struct_obj);
InitializeTemplate(obj, Consts::OBJECT_TEMPLATE);
int next_serial_number = 0;
if (!constructor.IsEmpty())
obj->set_constructor(*Utils::OpenHandle(*constructor));
obj->set_data(i::Smi::zero());
return Utils::ToLocal(obj);
}
```
What is a `Struct` in this context?
`src/objects/struct.h`
```c++
#include "torque-generated/class-definitions-tq.h"

class Struct : public TorqueGeneratedStruct {
public:
inline void InitializeBody(int object_size);
void BriefPrintDetails(std::ostream& os);
TQ_OBJECT_CONSTRUCTORS(Struct)
```
Notice that the include is specifying `torque-generated` include which can be
found `out/x64.release_gcc/gen/torque-generated/class-definitions-tq`. So, somewhere
there must be an call to the `torque` executable which generates the Code Stub
Assembler C++ headers and sources before compiling the main source files. There is
and there is a section about this in `Building V8`.
The macro `TQ_OBJECT_CONSTRUCTORS` can be found in `src/objects/object-macros.h`
and expands to:
```c++
constexpr Struct() = default;

protected:
template
friend class TaggedField;

inline explicit Struct(Address ptr);
```

So what does the TorqueGeneratedStruct look like?
```
template
class TorqueGeneratedStruct : public P {
public:
```
Where D is Struct and P is HeapObject in this case. But the above is the declartion
of the type but what we have in the .h file is what was generated.

This type is defined in `src/objects/struct.tq`:
```
@abstract
@generatePrint
@generateCppClass
extern class Struct extends HeapObject {
}
```

`NewStruct` can be found in `src/heap/factory-base.cc`
```c++
template
HandleFor FactoryBase::NewStruct(
InstanceType type, AllocationType allocation) {
Map map = Map::GetStructMap(read_only_roots(), type);
int size = map.instance_size();
HeapObject result = AllocateRawWithImmortalMap(size, allocation, map);
HandleFor str = handle(Struct::cast(result), isolate());
str->InitializeBody(size);
return str;
}
```
Every object that is stored on the v8 heap has a Map (`src/objects/map.h`) that
describes the structure of the object being stored.
```c++
class Map : public HeapObject {
```

```console
1725 return Utils::ToLocal(obj);
(gdb) p obj
$6 = { = {location_ = 0x30b5160}, }
```
So this is the connection, what we see as a Local is a HandleBase.
TODO: dig into this some more when I have time.

```console
(lldb) expr gl
(v8::internal::Object **) $0 = 0x00000000020ee160
(lldb) memory read -f x -s 8 -c 1 gl
0x020ee160: 0x00000aee081c0121

(lldb) memory read -f x -s 8 -c 1 *gl
0xaee081c0121: 0x0200000002080433
```

You can reload `.lldbinit` using the following command:
```console
(lldb) command source ~/.lldbinit
```
This can be useful when debugging a lldb command. You can set a breakpoint
and break at that location and make updates to the command and reload without
having to restart lldb.

Currently, the lldb-commands.py that ships with v8 contains an extra operation
of the parameter pased to `ptr_arg_cmd`:
```python
def ptr_arg_cmd(debugger, name, param, cmd):
if not param:
print("'{}' requires an argument".format(name))
return
param = '(void*)({})'.format(param)
no_arg_cmd(debugger, cmd.format(param))
```
Notice that `param` is the object that we want to print, for example lets say
it is a local named obj:
```
param = "(void*)(obj)"
```
This will then be "passed"/formatted into the command string:
```
"_v8_internal_Print_Object(*(v8::internal::Object**)(*(void*)(obj))")
```

#### Threads
V8 is single threaded (the execution of the functions of the stack) but there
are supporting threads used for garbage collection, profiling (IC, and perhaps
other things) (I think).
Lets see what threads there are:

$ LD_LIBRARY_PATH=../v8_src/v8/out/x64.release_gcc/ lldb ./hello-world
(lldb) br s -n main
(lldb) r
(lldb) thread list
thread #1: tid = 0x2efca6, 0x0000000100001e16 hello-world`main(argc=1, argv=0x00007fff5fbfee98) + 38 at hello-world.cc:40, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1

So at startup there is only one thread which is what we expected. Lets skip ahead to where we create the platform:

Platform* platform = platform::CreateDefaultPlatform();
...
DefaultPlatform* platform = new DefaultPlatform(idle_task_support, tracing_controller);
platform->SetThreadPoolSize(thread_pool_size);

(lldb) fr v thread_pool_size
(int) thread_pool_size = 0

Next there is a check for 0 and the number of processors -1 is used as the size of the thread pool:

(lldb) fr v thread_pool_size
(int) thread_pool_size = 7

This is all that `SetThreadPoolSize` does. After this we have:

platform->EnsureInitialized();

for (int i = 0; i < thread_pool_size_; ++i)
thread_pool_.push_back(new WorkerThread(&queue_));

`new WorkerThread` will create a new pthread (on my system which is MacOSX):

result = pthread_create(&data_->thread_, &attr, ThreadEntry, this);

ThreadEntry can be found in src/base/platform/platform-posix.

### International Component for Unicode (ICU)
International Components for Unicode (ICU) deals with internationalization (i18n).
ICU provides support locale-sensitve string comparisons, date/time/number/currency formatting
etc.

There is an optional API called ECMAScript 402 which V8 suppports and which is enabled by
default. [i18n-support](https://github.com/v8/v8/wiki/i18n-support) says that even if your application does
not use ICU you still need to call InitializeICU :

V8::InitializeICU();

### Local

```c++
Local script_name = ...;
```
So what is script_name. Well it is an object reference that is managed by the v8 GC.
The GC needs to be able to move things (pointers around) and also track if
things should be GC'd. Local handles as opposed to persistent handles are light
weight and mostly used local operations. These handles are managed by
HandleScopes so you must have a handlescope on the stack and the local is only
valid as long as the handlescope is valid. This uses Resource Acquisition Is
Initialization (RAII) so when the HandleScope instance goes out of scope it
will remove all the Local instances.

The `Local` class (in `include/v8.h`) only has one member which is of type
pointer to the type `T`. So for the above example it would be:
```c++
String* val_;
```
You can find the available operations for a Local in `include/v8.h`.

```shell
(lldb) p script_name.IsEmpty()
(bool) $12 = false
````

A Local has overloaded a number of operators, for example ->:
```shell
(lldb) p script_name->Length()
(int) $14 = 7
````
Where Length is a method on the v8 String class.

The handle stack is not part of the C++ call stack, but the handle scopes are
embedded in the C++ stack. Handle scopes can only be stack-allocated, not
allocated with new.

### Persistent
https://v8.dev/docs/embed:
Persistent handles provide a reference to a heap-allocated JavaScript Object,
just like a local handle. There are two flavors, which differ in the lifetime
management of the reference they handle. Use a persistent handle when you need
to keep a reference to an object for more than one function call, or when handle
lifetimes do not correspond to C++ scopes. Google Chrome, for example, uses
persistent handles to refer to Document Object Model (DOM) nodes.

A persistent handle can be made weak, using PersistentBase::SetWeak, to trigger
a callback from the garbage collector when the only references to an object are
from weak persistent handles.

A UniquePersistent handle relies on C++ constructors and destructors
to manage the lifetime of the underlying object.
A Persistent can be constructed with its constructor, but must be
explicitly cleared with Persistent::Reset.

So how is a persistent object created?
Let's write a test and find out (`test/persistent-object_text.cc`):
```console
$ make test/persistent-object_test
$ ./test/persistent-object_test --gtest_filter=PersistentTest.value
```
Now, to create an instance of Persistent we need a Local instance or the
Persistent instance will just be empty.
```c++
Local o = Local::New(isolate_, Object::New(isolate_));
```
`Local::New` can be found in `src/api/api.cc`:
```c++
Local v8::Object::New(Isolate* isolate) {
i::Isolate* i_isolate = reinterpret_cast(isolate);
LOG_API(i_isolate, Object, New);
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate);
i::Handle obj =
i_isolate->factory()->NewJSObject(i_isolate->object_function());
return Utils::ToLocal(obj);
}
```
The first thing that happens is that the public Isolate pointer is cast to an
pointer to the internal `Isolate` type.
`LOG_API` is a macro in the same source file (src/api/api.cc):
```c++
#define LOG_API(isolate, class_name, function_name) \
i::RuntimeCallTimerScope _runtime_timer( \
isolate, i::RuntimeCallCounterId::kAPI_##class_name##_##function_name); \
LOG(isolate, ApiEntryCall("v8::" #class_name "::" #function_name))
```
If our case the preprocessor would expand that to:
```c++
i::RuntimeCallTimerScope _runtime_timer(
isolate, i::RuntimeCallCounterId::kAPI_Object_New);
LOG(isolate, ApiEntryCall("v8::Object::New))
```
`LOG` is a macro that can be found in `src/log.h`:
```c++
#define LOG(isolate, Call) \
do { \
v8::internal::Logger* logger = (isolate)->logger(); \
if (logger->is_logging()) logger->Call; \
} while (false)
```
And this would expand to:
```c++
v8::internal::Logger* logger = isolate->logger();
if (logger->is_logging()) logger->ApiEntryCall("v8::Object::New");
```
So with the LOG_API macro expanded we have:
```c++
Local v8::Object::New(Isolate* isolate) {
i::Isolate* i_isolate = reinterpret_cast(isolate);
i::RuntimeCallTimerScope _runtime_timer( isolate, i::RuntimeCallCounterId::kAPI_Object_New);
v8::internal::Logger* logger = isolate->logger();
if (logger->is_logging()) logger->ApiEntryCall("v8::Object::New");

ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate);
i::Handle obj =
i_isolate->factory()->NewJSObject(i_isolate->object_function());
return Utils::ToLocal(obj);
}
```
Next we have `ENTER_V8_NO_SCRIPT_NO_EXCEPTION`:
```c++
#define ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate) \
i::VMState __state__((isolate)); \
i::DisallowJavascriptExecutionDebugOnly __no_script__((isolate)); \
i::DisallowExceptions __no_exceptions__((isolate))
```
So with the macros expanded we have:
```c++
Local v8::Object::New(Isolate* isolate) {
i::Isolate* i_isolate = reinterpret_cast(isolate);
i::RuntimeCallTimerScope _runtime_timer( isolate, i::RuntimeCallCounterId::kAPI_Object_New);
v8::internal::Logger* logger = isolate->logger();
if (logger->is_logging()) logger->ApiEntryCall("v8::Object::New");

i::VMState __state__(i_isolate));
i::DisallowJavascriptExecutionDebugOnly __no_script__(i_isolate);
i::DisallowExceptions __no_exceptions__(i_isolate));

i::Handle obj =
i_isolate->factory()->NewJSObject(i_isolate->object_function());

return Utils::ToLocal(obj);
}
```
TODO: Look closer at `VMState`.

First, `i_isolate->object_function()` is called and the result passed to
`NewJSObject`. `object_function` is generated by a macro named
`NATIVE_CONTEXT_FIELDS`:
```c++
#define NATIVE_CONTEXT_FIELD_ACCESSOR(index, type, name) \
Handle Isolate::name() { \
return Handle(raw_native_context()->name(), this); \
} \
bool Isolate::is_##name(type* value) { \
return raw_native_context()->is_##name(value); \
}
NATIVE_CONTEXT_FIELDS(NATIVE_CONTEXT_FIELD_ACCESSOR)
```
`NATIVE_CONTEXT_FIELDS` is a macro in `src/contexts` and it c
```c++
#define NATIVE_CONTEXT_FIELDS(V) \
... \
V(OBJECT_FUNCTION_INDEX, JSFunction, object_function) \
```

```c++
Handle Isolate::object_function() {
return Handle(raw_native_context()->object_function(), this);
}

bool Isolate::is_object_function(JSFunction* value) {
return raw_native_context()->is_object_function(value);
}
```
I'm not clear on the different types of context, there is a native context, a "normal/public" context.
In `src/contexts-inl.h` we have the native_context function:
```c++
Context* Context::native_context() const {
Object* result = get(NATIVE_CONTEXT_INDEX);
DCHECK(IsBootstrappingOrNativeContext(this->GetIsolate(), result));
return reinterpret_cast(result);
}
```
`Context` extends `FixedArray` so the get function is the get function of FixedArray and `NATIVE_CONTEXT_INDEX`
is the index into the array where the native context is stored.

Now, lets take a closer look at `NewJSObject`. If you search for NewJSObject in `src/heap/factory.cc`:
```c++
Handle Factory::NewJSObject(Handle constructor, PretenureFlag pretenure) {
JSFunction::EnsureHasInitialMap(constructor);
Handle map(constructor->initial_map(), isolate());
return NewJSObjectFromMap(map, pretenure);
}
```
`NewJSObjectFromMap`
```c++
...
HeapObject* obj = AllocateRawWithAllocationSite(map, pretenure, allocation_site);
```
So we have created a new map

### Map
So an HeapObject contains a pointer to a Map, or rather has a function that
returns a pointer to Map. I can't see any member map in the HeapObject class.

Lets take a look at when a map is created.
```console
(lldb) br s -f map_test.cc -l 63
```

```c++
Handle Factory::NewMap(InstanceType type,
int instance_size,
ElementsKind elements_kind,
int inobject_properties) {
HeapObject* result = isolate()->heap()->AllocateRawWithRetryOrFail(Map::kSize, MAP_SPACE);
result->set_map_after_allocation(*meta_map(), SKIP_WRITE_BARRIER);
return handle(InitializeMap(Map::cast(result), type, instance_size,
elements_kind, inobject_properties),
isolate());
}
```
We can see that the above is calling `AllocateRawWithRetryOrFail` on the heap
instance passing a size of `88` and specifying the `MAP_SPACE`:
```c++
HeapObject* Heap::AllocateRawWithRetryOrFail(int size, AllocationSpace space,
AllocationAlignment alignment) {
AllocationResult alloc;
HeapObject* result = AllocateRawWithLigthRetry(size, space, alignment);
if (result) return result;

isolate()->counters()->gc_last_resort_from_handles()->Increment();
CollectAllAvailableGarbage(GarbageCollectionReason::kLastResort);
{
AlwaysAllocateScope scope(isolate());
alloc = AllocateRaw(size, space, alignment);
}
if (alloc.To(&result)) {
DCHECK(result != exception());
return result;
}
// TODO(1181417): Fix this.
FatalProcessOutOfMemory("CALL_AND_RETRY_LAST");
return nullptr;
}
```
The default value for `alignment` is `kWordAligned`. Reading the docs in the header it says that this function
will try to perform an allocation of size `88` in the `MAP_SPACE` and if it fails a full GC will be performed
and the allocation retried.
Lets take a look at `AllocateRawWithLigthRetry`:
```c++
AllocationResult alloc = AllocateRaw(size, space, alignment);
```
`AllocateRaw` can be found in `src/heap/heap-inl.h`. There are different paths that will be taken depending on the
`space` parameteter. Since it is `MAP_SPACE` in our case we will focus on that path:
```c++
AllocationResult Heap::AllocateRaw(int size_in_bytes, AllocationSpace space, AllocationAlignment alignment) {
...
HeapObject* object = nullptr;
AllocationResult allocation;
if (OLD_SPACE == space) {
...
} else if (MAP_SPACE == space) {
allocation = map_space_->AllocateRawUnaligned(size_in_bytes);
}
...
}
```
`map_space_` is a private member of Heap (src/heap/heap.h):
```c++
MapSpace* map_space_;
```
`AllocateRawUnaligned` can be found in `src/heap/spaces-inl.h`:
```c++
AllocationResult PagedSpace::AllocateRawUnaligned( int size_in_bytes, UpdateSkipList update_skip_list) {
if (!EnsureLinearAllocationArea(size_in_bytes)) {
return AllocationResult::Retry(identity());
}

HeapObject* object = AllocateLinearly(size_in_bytes);
MSAN_ALLOCATED_UNINITIALIZED_MEMORY(object->address(), size_in_bytes);
return object;
}
```
The default value for `update_skip_list` is `UPDATE_SKIP_LIST`.
So lets take a look at `AllocateLinearly`:
```c++
HeapObject* PagedSpace::AllocateLinearly(int size_in_bytes) {
Address current_top = allocation_info_.top();
Address new_top = current_top + size_in_bytes;
allocation_info_.set_top(new_top);
return HeapObject::FromAddress(current_top);
}
```
Recall that `size_in_bytes` in our case is `88`.
```console
(lldb) expr current_top
(v8::internal::Address) $5 = 24847457492680
(lldb) expr new_top
(v8::internal::Address) $6 = 24847457492768
(lldb) expr new_top - current_top
(unsigned long) $7 = 88
```
Notice that first the top is set to the new_top and then the current_top is returned and that will be a pointer
to the start of the object in memory (which in this case is of v8::internal::Map which is also of type HeapObject).
I've been wondering why Map (and other HeapObject) don't have any member fields and only/mostly
getters/setters for the various fields that make up an object. Well the answer is that pointers to instances of
for example Map point to the first memory location of the instance. And the getters/setter functions use indexed
to read/write to memory locations. The indexes are mostly in the form of enum fields that define the memory layout
of the type.

Next, in `AllocateRawUnaligned` we have the `MSAN_ALLOCATED_UNINITIALIZED_MEMORY` macro:
```c++
MSAN_ALLOCATED_UNINITIALIZED_MEMORY(object->address(), size_in_bytes);
```
`MSAN_ALLOCATED_UNINITIALIZED_MEMORY` can be found in `src/msan.h` and `ms` stands for `Memory Sanitizer` and
would only be used if `V8_US_MEMORY_SANITIZER` is defined.
The returned `object` will be used to construct an `AllocationResult` when returned.
Back in `AllocateRaw` we have:
```c++
if (allocation.To(&object)) {
...
OnAllocationEvent(object, size_in_bytes);
}

return allocation;
```
This will return us in `AllocateRawWithLightRetry`:
```c++
AllocationResult alloc = AllocateRaw(size, space, alignment);
if (alloc.To(&result)) {
DCHECK(result != exception());
return result;
}
```
This will return us back in `AllocateRawWithRetryOrFail`:
```c++
HeapObject* result = AllocateRawWithLigthRetry(size, space, alignment);
if (result) return result;
```
And that return will return to `NewMap` in `src/heap/factory.cc`:
```c++
result->set_map_after_allocation(*meta_map(), SKIP_WRITE_BARRIER);
return handle(InitializeMap(Map::cast(result), type, instance_size,
elements_kind, inobject_properties),
isolate());
```
`InitializeMap`:
```c++
map->set_instance_type(type);
map->set_prototype(*null_value(), SKIP_WRITE_BARRIER);
map->set_constructor_or_backpointer(*null_value(), SKIP_WRITE_BARRIER);
map->set_instance_size(instance_size);
if (map->IsJSObjectMap()) {
DCHECK(!isolate()->heap()->InReadOnlySpace(map));
map->SetInObjectPropertiesStartInWords(instance_size / kPointerSize - inobject_properties);
DCHECK_EQ(map->GetInObjectProperties(), inobject_properties);
map->set_prototype_validity_cell(*invalid_prototype_validity_cell());
} else {
DCHECK_EQ(inobject_properties, 0);
map->set_inobject_properties_start_or_constructor_function_index(0);
map->set_prototype_validity_cell(Smi::FromInt(Map::kPrototypeChainValid));
}
map->set_dependent_code(DependentCode::cast(*empty_fixed_array()), SKIP_WRITE_BARRIER);
map->set_weak_cell_cache(Smi::kZero);
map->set_raw_transitions(MaybeObject::FromSmi(Smi::kZero));
map->SetInObjectUnusedPropertyFields(inobject_properties);
map->set_instance_descriptors(*empty_descriptor_array());

map->set_visitor_id(Map::GetVisitorId(map));
map->set_bit_field(0);
map->set_bit_field2(Map::IsExtensibleBit::kMask);
int bit_field3 = Map::EnumLengthBits::encode(kInvalidEnumCacheSentinel) |
Map::OwnsDescriptorsBit::encode(true) |
Map::ConstructionCounterBits::encode(Map::kNoSlackTracking);
map->set_bit_field3(bit_field3);
map->set_elements_kind(elements_kind); //HOLEY_ELEMENTS
map->set_new_target_is_base(true);
isolate()->counters()->maps_created()->Increment();
if (FLAG_trace_maps) LOG(isolate(), MapCreate(map));
return map;
```

Creating a new map ([map_test.cc](./test/map_test.cc):
```c++
i::Handle map = i::Map::Create(asInternal(isolate_), 10);
std::cout << map->instance_type() << '\n';
```
`Map::Create` can be found in objects.cc:
```c++
Handle Map::Create(Isolate* isolate, int inobject_properties) {
Handle copy = Copy(handle(isolate->object_function()->initial_map()), "MapCreate");
```
So, the first thing that will happen is `isolate->object_function()` will be called. This is function
that is generated by the preprocessor.

```c++
// from src/context.h
#define NATIVE_CONTEXT_FIELDS(V) \
... \
V(OBJECT_FUNCTION_INDEX, JSFunction, object_function) \

// from src/isolate.h
#define NATIVE_CONTEXT_FIELD_ACCESSOR(index, type, name) \
Handle Isolate::name() { \
return Handle(raw_native_context()->name(), this); \
} \
bool Isolate::is_##name(type* value) { \
return raw_native_context()->is_##name(value); \
}
NATIVE_CONTEXT_FIELDS(NATIVE_CONTEXT_FIELD_ACCESSOR)
```
`object_function()` will become:
```c++
Handle Isolate::object_function() {
return Handle(raw_native_context()->object_function(), this);
}
```

Lets look closer at `JSFunction::initial_map()` in in object-inl.h:
```c++
Map* JSFunction::initial_map() {
return Map::cast(prototype_or_initial_map());
}
```

`prototype_or_initial_map` is generated by a macro:
```c++
ACCESSORS_CHECKED(JSFunction, prototype_or_initial_map, Object,
kPrototypeOrInitialMapOffset, map()->has_prototype_slot())
```
`ACCESSORS_CHECKED` can be found in `src/objects/object-macros.h`:
```c++
#define ACCESSORS_CHECKED(holder, name, type, offset, condition) \
ACCESSORS_CHECKED2(holder, name, type, offset, condition, condition)

#define ACCESSORS_CHECKED2(holder, name, type, offset, get_condition, \
set_condition) \
type* holder::name() const { \
type* value = type::cast(READ_FIELD(this, offset)); \
DCHECK(get_condition); \
return value; \
} \
void holder::set_##name(type* value, WriteBarrierMode mode) { \
DCHECK(set_condition); \
WRITE_FIELD(this, offset, value); \
CONDITIONAL_WRITE_BARRIER(GetHeap(), this, offset, value, mode); \
}

#define FIELD_ADDR(p, offset) \
(reinterpret_cast

(p) + offset - kHeapObjectTag)

#define READ_FIELD(p, offset) \
(*reinterpret_cast(FIELD_ADDR(p, offset)))
```
The preprocessor will expand `prototype_or_initial_map` to:
```c++
JSFunction* JSFunction::prototype_or_initial_map() const {
JSFunction* value = JSFunction::cast(
(*reinterpret_cast(
(reinterpret_cast

(this) + kPrototypeOrInitialMapOffset - kHeapObjectTag))))
DCHECK(map()->has_prototype_slot());
return value;
}
```
Notice that `map()->has_prototype_slot())` will be called first which looks like this:
```c++
Map* HeapObject::map() const {
return map_word().ToMap();
}
```
__TODO: Add notes about MapWord__
```c++
MapWord HeapObject::map_word() const {
return MapWord(
reinterpret_cast(RELAXED_READ_FIELD(this, kMapOffset)));
}
```
First thing that will happen is `RELAXED_READ_FIELD(this, kMapOffset)`
```c++
#define RELAXED_READ_FIELD(p, offset) \
reinterpret_cast(base::Relaxed_Load( \
reinterpret_cast(FIELD_ADDR(p, offset))))

#define FIELD_ADDR(p, offset) \
(reinterpret_cast

(p) + offset - kHeapObjectTag)
```
This will get expanded by the preprocessor to:
```c++
reinterpret_cast(base::Relaxed_Load(
reinterpret_cast(
(reinterpret_cast
(this) + kMapOffset - kHeapObjectTag)))
```
`src/base/atomicops_internals_portable.h`:
```c++
inline Atomic8 Relaxed_Load(volatile const Atomic8* ptr) {
return __atomic_load_n(ptr, __ATOMIC_RELAXED);
}
```
So this will do an atomoic load of the ptr with the memory order of __ATOMIC_RELELAXED.

`ACCESSORS_CHECKED` also generates a `set_prototyp_or_initial_map`:
```c++
void JSFunction::set_prototype_or_initial_map(JSFunction* value, WriteBarrierMode mode) {
DCHECK(map()->has_prototype_slot());
WRITE_FIELD(this, kPrototypeOrInitialMapOffset, value);
CONDITIONAL_WRITE_BARRIER(GetHeap(), this, kPrototypeOrInitialMapOffset, value, mode);
}
```
What does `WRITE_FIELD` do?
```c++
#define WRITE_FIELD(p, offset, value) \
base::Relaxed_Store( \
reinterpret_cast(FIELD_ADDR(p, offset)), \
reinterpret_cast(value));
```
Which would expand into:
```c++
base::Relaxed_Store( \
reinterpret_cast(
(reinterpret_cast

(this) + kPrototypeOrInitialMapOffset - kHeapObjectTag)
reinterpret_cast(value));
```

Lets take a look at what `instance_type` does:
```c++
InstanceType Map::instance_type() const {
return static_cast(READ_UINT16_FIELD(this, kInstanceTypeOffset));
}
```

To see what the above is doing we can do the same thing in the debugger:
Note that I got `11` below from `map->kInstanceTypeOffset - i::kHeapObjectTag`
```console
(lldb) memory read -f u -c 1 -s 8 `*map + 11`
0x6d4e6609ed4: 585472345729139745
(lldb) expr static_cast(585472345729139745)
(v8::internal::InstanceType) $34 = JS_OBJECT_TYPE
```

Take `map->has_non_instance_prototype()`:
```console
(lldb) br s -n has_non_instance_prototype
(lldb) expr -i 0 -- map->has_non_instance_prototype()
```
The above command will break in `src/objects/map-inl.h`:
```c++
BIT_FIELD_ACCESSORS(Map, bit_field, has_non_instance_prototype, Map::HasNonInstancePrototypeBit)

// src/objects/object-macros.h
#define BIT_FIELD_ACCESSORS(holder, field, name, BitField) \
typename BitField::FieldType holder::name() const { \
return BitField::decode(field()); \
} \
void holder::set_##name(typename BitField::FieldType value) { \
set_##field(BitField::update(field(), value)); \
}
```
The preprocessor will expand that to:
```c++
typename Map::HasNonInstancePrototypeBit::FieldType Map::has_non_instance_prototype() const {
return Map::HasNonInstancePrototypeBit::decode(bit_field());
} \
void holder::set_has_non_instance_prototype(typename BitField::FieldType value) { \
set_bit_field(Map::HasNonInstancePrototypeBit::update(bit_field(), value)); \
}
```
So where can we find `Map::HasNonInstancePrototypeBit`?
It is generated by a macro in `src/objects/map.h`:
```c++
// Bit positions for |bit_field|.
#define MAP_BIT_FIELD_FIELDS(V, _) \
V(HasNonInstancePrototypeBit, bool, 1, _) \
...
DEFINE_BIT_FIELDS(MAP_BIT_FIELD_FIELDS)
#undef MAP_BIT_FIELD_FIELDS

#define DEFINE_BIT_FIELDS(LIST_MACRO) \
DEFINE_BIT_RANGES(LIST_MACRO) \
LIST_MACRO(DEFINE_BIT_FIELD_TYPE, LIST_MACRO##_Ranges)

#define DEFINE_BIT_RANGES(LIST_MACRO) \
struct LIST_MACRO##_Ranges { \
enum { LIST_MACRO(DEFINE_BIT_FIELD_RANGE_TYPE, _) kBitsCount }; \
};

#define DEFINE_BIT_FIELD_RANGE_TYPE(Name, Type, Size, _) \
k##Name##Start, k##Name##End = k##Name##Start + Size - 1,
```
Alright, lets see what preprocessor expands that to:
```c++
struct MAP_BIT_FIELD_FIELDS_Ranges {
enum {
kHasNonInstancePrototypeBitStart,
kHasNonInstancePrototypeBitEnd = kHasNonInstancePrototypeBitStart + 1 - 1,
... // not showing the rest of the entries.
kBitsCount
};
};

```
So this would create a struct with an enum and it could be accessed using:
`i::Map::MAP_BIT_FIELD_FIELDS_Ranges::kHasNonInstancePrototypeBitStart`
The next part of the macro is
```c++
LIST_MACRO(DEFINE_BIT_FIELD_TYPE, LIST_MACRO##_Ranges)

#define DEFINE_BIT_FIELD_TYPE(Name, Type, Size, RangesName) \
typedef BitField Name;
```
Which will get expanded to:
```c++
typedef BitField HasNonInstancePrototypeBit;
```
So this is how `HasNonInstancePrototypeBit` is declared and notice that it is of type `BitField` which can be
found in `src/utils.h`:
```c++
template
class BitField : public BitFieldBase { };

template
class BitFieldBase {
public:
typedef T FieldType;
```

Map::HasNonInstancePrototypeBit::decode(bit_field());
first bit_field is called:
```c++
byte Map::bit_field() const { return READ_BYTE_FIELD(this, kBitFieldOffset); }

```
And the result of that is passed to `Map::HasNonInstancePrototypeBit::decode`:

```console
(lldb) br s -n bit_field
(lldb) expr -i 0 -- map->bit_field()
```

```c++
byte Map::bit_field() const { return READ_BYTE_FIELD(this, kBitFieldOffset); }
```
So, `this` is the current Map instance, and we are going to read from.
```c++
#define READ_BYTE_FIELD(p, offset) \
(*reinterpret_cast(FIELD_ADDR(p, offset)))

#define FIELD_ADDR(p, offset) \
(reinterpret_cast

(p) + offset - kHeapObjectTag)
```
Which will get expanded to:
```c++
byte Map::bit_field() const {
return *reinterpret_cast(
reinterpret_cast
(this) + kBitFieldOffset - kHeapObjectTag)
}
```

The instance_size is the instance_size_in_words << kPointerSizeLog2 (3 on my machine):
```console
(lldb) memory read -f x -s 1 -c 1 *map+8
0x24d1cd509ed1: 0x03
(lldb) expr 0x03 << 3
(int) $2 = 24
(lldb) expr map->instance_size()
(int) $3 = 24
```
`i::HeapObject::kHeaderSize` is 8 on my system which is used in the `DEFINE_FIELD_OFFSET_CONSTANTS:
```c++
#define MAP_FIELDS(V)
V(kInstanceSizeInWordsOffset, kUInt8Size)
V(kInObjectPropertiesStartOrConstructorFunctionIndexOffset, kUInt8Size)
...
DEFINE_FIELD_OFFSET_CONSTANTS(HeapObject::kHeaderSize, MAP_FIELDS)
```

So we can use this information to read the `inobject_properties_start_or_constructor_function_index` directly from memory using:
```console
(lldb) expr map->inobject_properties_start_or_constructor_function_index()
(lldb) memory read -f x -s 1 -c 1 map+9
error: invalid start address expression.
error: address expression "map+9" evaluation failed
(lldb) memory read -f x -s 1 -c 1 *map+9
0x17b027209ed2: 0x03
```

Inspect the visitor_id (which is the last of the first byte):
```console
lldb) memory read -f x -s 1 -c 1 *map+10
0x17b027209ed3: 0x15
(lldb) expr (int) 0x15
(int) $8 = 21
(lldb) expr map->visitor_id()
(v8::internal::VisitorId) $11 = kVisitJSObjectFast
(lldb) expr (int) $11
(int) $12 = 21
```

Inspect the instance_type (which is part of the second byte):
```console
(lldb) expr map->instance_type()
(v8::internal::InstanceType) $41 = JS_OBJECT_TYPE
(lldb) expr v8::internal::InstanceType::JS_OBJECT_TYPE
(uint16_t) $35 = 1057
(lldb) memory read -f x -s 2 -c 1 *map+11
0x17b027209ed4: 0x0421
(lldb) expr (int)0x0421
(int) $40 = 1057
```
Notice that `instance_type` is a short so that will take up 2 bytes
```console
(lldb) expr map->has_non_instance_prototype()
(bool) $60 = false
(lldb) expr map->is_callable()
(bool) $46 = false
(lldb) expr map->has_named_interceptor()
(bool) $51 = false
(lldb) expr map->has_indexed_interceptor()
(bool) $55 = false
(lldb) expr map->is_undetectable()
(bool) $56 = false
(lldb) expr map->is_access_check_needed()
(bool) $57 = false
(lldb) expr map->is_constructor()
(bool) $58 = false
(lldb) expr map->has_prototype_slot()
(bool) $59 = false
```

Verify that the above is correct:
```console
(lldb) expr map->has_non_instance_prototype()
(bool) $44 = false
(lldb) memory read -f x -s 1 -c 1 *map+13
0x17b027209ed6: 0x00

(lldb) expr map->set_has_non_instance_prototype(true)
(lldb) memory read -f x -s 1 -c 1 *map+13
0x17b027209ed6: 0x01

(lldb) expr map->set_has_prototype_slot(true)
(lldb) memory read -f x -s 1 -c 1 *map+13
0x17b027209ed6: 0x81
```

Inspect second int field (bit_field2):
```console
(lldb) memory read -f x -s 1 -c 1 *map+14
0x17b027209ed7: 0x19
(lldb) expr map->is_extensible()
(bool) $78 = true
(lldb) expr -- 0x19 & (1 << 0)
(bool) $90 = 1

(lldb) expr map->is_prototype_map()
(bool) $79 = false

(lldb) expr map->is_in_retained_map_list()
(bool) $80 = false

(lldb) expr map->elements_kind()
(v8::internal::ElementsKind) $81 = HOLEY_ELEMENTS
(lldb) expr v8::internal::ElementsKind::HOLEY_ELEMENTS
(int) $133 = 3
(lldb) expr 0x19 >> 3
(int) $134 = 3
```

Inspect third int field (bit_field3):
```console
(lldb) memory read -f b -s 4 -c 1 *map+15
0x17b027209ed8: 0b00001000001000000000001111111111
(lldb) memory read -f x -s 4 -c 1 *map+15
0x17b027209ed8: 0x082003ff
```

So we know that a Map instance is a pointer allocated by the Heap and with a specific
size. Fields are accessed using indexes (remember there are no member fields in the Map class).
We also know that all HeapObject have a Map. The Map is sometimes referred to as the HiddenClass
and sometimes the shape of an object. If two objects have the same properties they would share
the same Map. This makes sense and I've see blog post that show this but I'd like to verify
this to fully understand it.
I'm going to try to match https://v8project.blogspot.com/2017/08/fast-properties.html with
the code.

So, lets take a look at adding a property to a JSObject. We start by creating a new Map and then
use it to create a new JSObject:
```c++
i::Handle map = factory->NewMap(i::JS_OBJECT_TYPE, 32);
i::Handle js_object = factory->NewJSObjectFromMap(map);

i::Handle prop_name = factory->InternalizeUtf8String("prop_name");
i::Handle prop_value = factory->InternalizeUtf8String("prop_value");
i::JSObject::AddProperty(js_object, prop_name, prop_value, i::NONE);
```
Lets take a closer look at `AddProperty` and how it interacts with the Map. This function can be
found in `src/objects.cc`:
```c++
void JSObject::AddProperty(Handle object, Handle name,
Handle value,
PropertyAttributes attributes) {
LookupIterator it(object, name, object, LookupIterator::OWN_SKIP_INTERCEPTOR);
CHECK_NE(LookupIterator::ACCESS_CHECK, it.state());
```
First we have the LookupIterator constructor (`src/lookup.h`) but since this is a new property which
we know does not exist it will not find any property.
```c++
CHECK(AddDataProperty(&it, value, attributes, kThrowOnError,
CERTAINLY_NOT_STORE_FROM_KEYED)
.IsJust());
```
```c++
Handle receiver = it->GetStoreTarget();
...
it->UpdateProtector();
// Migrate to the most up-to-date map that will be able to store |value|
// under it->name() with |attributes|.
it->PrepareTransitionToDataProperty(receiver, value, attributes, store_mode);
DCHECK_EQ(LookupIterator::TRANSITION, it->state());
it->ApplyTransitionToDataProperty(receiver);

// Write the property value.
it->WriteDataValue(value, true);
```
`PrepareTransitionToDataProperty`:
```c++
Representation representation = value->OptimalRepresentation();
Handle type = value->OptimalType(isolate, representation);
maybe_map = Map::CopyWithField(map, name, type, attributes, constness,
representation, flag);
```
`Map::CopyWithField`:
```c++
Descriptor d = Descriptor::DataField(name, index, attributes, constness, representation, wrapped_type);
```
Lets take a closer look the Decriptor which can be found in `src/property.cc`:
```c++
Descriptor Descriptor::DataField(Handle key, int field_index,
PropertyAttributes attributes,
PropertyConstness constness,
Representation representation,
MaybeObjectHandle wrapped_field_type) {
DCHECK(wrapped_field_type->IsSmi() || wrapped_field_type->IsWeakHeapObject());
PropertyDetails details(kData, attributes, kField, constness, representation,
field_index);
return Descriptor(key, wrapped_field_type, details);
}
```
`Descriptor` is declared in `src/property.h` and describes the elements in a instance-descriptor array. These
are returned when calling `map->instance_descriptors()`. Let check some of the arguments:
```console
(lldb) job *key
#prop_name
(lldb) expr attributes
(v8::internal::PropertyAttributes) $27 = NONE
(lldb) expr constness
(v8::internal::PropertyConstness) $28 = kMutable
(lldb) expr representation
(v8::internal::Representation) $29 = (kind_ = '\b')
```
The Descriptor class contains three members:
```c++
private:
Handle key_;
MaybeObjectHandle value_;
PropertyDetails details_;
```
Lets take a closer look `PropertyDetails` which only has a single member named `value_`
```c++
uint32_t value_;
```
It also declares a number of classes the extend BitField, for example:
```c++
class KindField : public BitField {};
class LocationField : public BitField {};
class ConstnessField : public BitField {};
class AttributesField : public BitField {};
class PropertyCellTypeField : public BitField {};
class DictionaryStorageField : public BitField {};

// Bit fields for fast objects.
class RepresentationField : public BitField {};
class DescriptorPointer : public BitField {};
class FieldIndexField : public BitField {

enum PropertyKind { kData = 0, kAccessor = 1 };
enum PropertyLocation { kField = 0, kDescriptor = 1 };
enum class PropertyConstness { kMutable = 0, kConst = 1 };
enum PropertyAttributes {
NONE = ::v8::None,
READ_ONLY = ::v8::ReadOnly,
DONT_ENUM = ::v8::DontEnum,
DONT_DELETE = ::v8::DontDelete,
ALL_ATTRIBUTES_MASK = READ_ONLY | DONT_ENUM | DONT_DELETE,
SEALED = DONT_DELETE,
FROZEN = SEALED | READ_ONLY,
ABSENT = 64, // Used in runtime to indicate a property is absent.
// ABSENT can never be stored in or returned from a descriptor's attributes
// bitfield. It is only used as a return value meaning the attributes of
// a non-existent property.
};
enum class PropertyCellType {
// Meaningful when a property cell does not contain the hole.
kUndefined, // The PREMONOMORPHIC of property cells.
kConstant, // Cell has been assigned only once.
kConstantType, // Cell has been assigned only one type.
kMutable, // Cell will no longer be tracked as constant.
// Meaningful when a property cell contains the hole.
kUninitialized = kUndefined, // Cell has never been initialized.
kInvalidated = kConstant, // Cell has been deleted, invalidated or never
// existed.
// For dictionaries not holding cells.
kNoCell = kMutable,
};

template
class BitField : public BitFieldBase { };
```
The Type T of KindField will be `PropertyKind`, the `shift` will be 0 , and the `size` 1.
Notice that `LocationField` is using `KindField::kNext` as its shift. This is a static class constant
of type `uint32_t` and is defined as:
```c++
static const U kNext = kShift + kSize;
```
So `LocationField` would get the value from KindField which should be:
```c++
class LocationField : public BitField {};
```

The constructor for PropertyDetails looks like this:
```c++
PropertyDetails(PropertyKind kind, PropertyAttributes attributes, PropertyCellType cell_type, int dictionary_index = 0) {
value_ = KindField::encode(kind) | LocationField::encode(kField) |
AttributesField::encode(attributes) |
DictionaryStorageField::encode(dictionary_index) |
PropertyCellTypeField::encode(cell_type);
}
```
So what does KindField::encode(kind) actualy do then?
```console
(lldb) expr static_cast(kind())
(uint32_t) $36 = 0
(lldb) expr static_cast(kind()) << 0
(uint32_t) $37 = 0
```
This value is later returned by calling `kind()`:
```c++
PropertyKind kind() const { return KindField::decode(value_); }
```
So we have all this information about this property, its type (Representation), constness, if it is
read-only, enumerable, deletable, sealed, frozen. After that little detour we are back in `Descriptor::DataField`:
```c++
return Descriptor(key, wrapped_field_type, details);
```
Here we are using the key (name of the property), the wrapped_field_type, and PropertyDetails we created.
What is `wrapped_field_type` again?
If we back up a few frames back into `Map::TransitionToDataProperty` we can see that the type passed in
is taken from the following code:
```c++
Representation representation = value->OptimalRepresentation();
Handle type = value->OptimalType(isolate, representation);
```
So this is only taking the type of the field:
```console
(lldb) expr representation.kind()
(v8::internal::Representation::Kind) $51 = kHeapObject
```
This makes sense as the map only deals with the shape of the propery and not the value.
Next in `Map::CopyWithField` we have:
```c++
Handle new_map = Map::CopyAddDescriptor(map, &d, flag);
```
`CopyAddDescriptor` does:
```c++
Handle descriptors(map->instance_descriptors());

int nof = map->NumberOfOwnDescriptors();
Handle new_descriptors = DescriptorArray::CopyUpTo(descriptors, nof, 1);
new_descriptors->Append(descriptor);

Handle new_layout_descriptor =
FLAG_unbox_double_fields
? LayoutDescriptor::New(map, new_descriptors, nof + 1)
: handle(LayoutDescriptor::FastPointerLayout(), map->GetIsolate());

return CopyReplaceDescriptors(map, new_descriptors, new_layout_descriptor,
flag, descriptor->GetKey(), "CopyAddDescriptor",
SIMPLE_PROPERTY_TRANSITION);
```
Lets take a closer look at `LayoutDescriptor`

```console
(lldb) expr new_layout_descriptor->Print()
Layout descriptor:
```
TODO: Take a closer look at LayoutDescritpor

Later when actually adding the value in `Object::AddDataProperty`:
```c++
it->WriteDataValue(value, true);
```
This call will end up in `src/lookup.cc` and in our case the path will be the following call:
```c++
JSObject::cast(*holder)->WriteToField(descriptor_number(), property_details_, *value);
```
TODO: Take a closer look at LookupIterator.
`WriteToField` can be found in `src/objects-inl.h`:
```c++
FieldIndex index = FieldIndex::ForDescriptor(map(), descriptor);
```
`FieldIndex::ForDescriptor` can be found in `src/field-index-inl.h`:
```c++
inline FieldIndex FieldIndex::ForDescriptor(const Map* map, int descriptor_index) {
PropertyDetails details = map->instance_descriptors()->GetDetails(descriptor_index);
int field_index = details.field_index();
return ForPropertyIndex(map, field_index, details.representation());
}
```
Notice that this is calling `instance_descriptors()` on the passed-in map. This as we recall from earlier returns
and DescriptorArray (which is a type of WeakFixedArray). A Descriptor array

Our DecsriptorArray only has one entry:
```console
(lldb) expr map->instance_descriptors()->number_of_descriptors()
(int) $6 = 1
(lldb) expr map->instance_descriptors()->GetKey(0)->Print()
#prop_name
(lldb) expr map->instance_descriptors()->GetFieldIndex(0)
(int) $11 = 0
```
We can also use `Print` on the DescriptorArray:
```console
lldb) expr map->instance_descriptors()->Print()

[0]: #prop_name (data field 0:h, p: 0, attrs: [WEC]) @ Any
```
In our case we are accessing the PropertyDetails and then getting the `field_index` which I think tells us
where in the object the value for this property is stored.
The last call in `ForDescriptor` is `ForProperty:
```c++
inline FieldIndex FieldIndex::ForPropertyIndex(const Map* map,
int property_index,
Representation representation) {
int inobject_properties = map->GetInObjectProperties();
bool is_inobject = property_index < inobject_properties;
int first_inobject_offset;
int offset;
if (is_inobject) {
first_inobject_offset = map->GetInObjectPropertyOffset(0);
offset = map->GetInObjectPropertyOffset(property_index);
} else {
first_inobject_offset = FixedArray::kHeaderSize;
property_index -= inobject_properties;
offset = FixedArray::kHeaderSize + property_index * kPointerSize;
}
Encoding encoding = FieldEncoding(representation);
return FieldIndex(is_inobject, offset, encoding, inobject_properties,
first_inobject_offset);
}
```
I was expecting `inobject_propertis` to be 1 here but it is 0:
```console
(lldb) expr inobject_properties
(int) $14 = 0
```
Why is that, what am I missing?
These in-object properties are stored directly on the object instance and not do not use
the properties array. All get back to an example of this later to clarify this.
TODO: Add in-object properties example.

Back in `JSObject::WriteToField`:
```c++
RawFastPropertyAtPut(index, value);
```

```c++
void JSObject::RawFastPropertyAtPut(FieldIndex index, Object* value) {
if (index.is_inobject()) {
int offset = index.offset();
WRITE_FIELD(this, offset, value);
WRITE_BARRIER(GetHeap(), this, offset, value);
} else {
property_array()->set(index.outobject_array_index(), value);
}
}
```
In our case we know that the index is not inobject()
```console
(lldb) expr index.is_inobject()
(bool) $18 = false
```
So, `property_array()->set()` will be called.
```console
(lldb) expr this
(v8::internal::JSObject *) $21 = 0x00002c31c6a88b59
```
JSObject inherits from JSReceiver which is where the property_array() function is declared.
```c++
inline PropertyArray* property_array() const;
```
```console
(lldb) expr property_array()->Print()
0x2c31c6a88bb1: [PropertyArray]
- map: 0x2c31f5603e21
- length: 3
- hash: 0
0: 0x2c31f56025a1
1-2: 0x2c31f56026f1
(lldb) expr index.outobject_array_index()
(int) $26 = 0
(lldb) expr value->Print()
#prop_value
```
Looking at the above values printed we should see the property be written to entry 0.
```console
(lldb) expr property_array()->get(0)->Print()
#uninitialized
// after call to set
(lldb) expr property_array()->get(0)->Print()
#prop_value
```

```console
(lldb) expr map->instance_descriptors()
(v8::internal::DescriptorArray *) $4 = 0x000039a927082339
```
So a map has an pointer array of instance of DescriptorArray

```console
(lldb) expr map->GetInObjectProperties()
(int) $19 = 1
```
Each Map has int that tells us the number of properties it has. This is the number specified when creating
a new Map, for example:
```console
i::Handle map = i::Map::Create(asInternal(isolate_), 1);
```
But at this stage we don't really have any properties. The value for a property is associated with the actual
instance of the Object. What the Map specifies is index of the value for a particualar property.

#### Creating a Map instance
Lets take a look at when a map is created.
```console
(lldb) br s -f map_test.cc -l 63
```

```c++
Handle Factory::NewMap(InstanceType type,
int instance_size,
ElementsKind elements_kind,
int inobject_properties) {
HeapObject* result = isolate()->heap()->AllocateRawWithRetryOrFail(Map::kSize, MAP_SPACE);
result->set_map_after_allocation(*meta_map(), SKIP_WRITE_BARRIER);
return handle(InitializeMap(Map::cast(result), type, instance_size,
elements_kind, inobject_properties),
isolate());
}
```
We can see that the above is calling `AllocateRawWithRetryOrFail` on the heap instance passing a size of `88` and
specifying the `MAP_SPACE`:
```c++
HeapObject* Heap::AllocateRawWithRetryOrFail(int size, AllocationSpace space,
AllocationAlignment alignment) {
AllocationResult alloc;
HeapObject* result = AllocateRawWithLigthRetry(size, space, alignment);
if (result) return result;

isolate()->counters()->gc_last_resort_from_handles()->Increment();
CollectAllAvailableGarbage(GarbageCollectionReason::kLastResort);
{
AlwaysAllocateScope scope(isolate());
alloc = AllocateRaw(size, space, alignment);
}
if (alloc.To(&result)) {
DCHECK(result != exception());
return result;
}
// TODO(1181417): Fix this.
FatalProcessOutOfMemory("CALL_AND_RETRY_LAST");
return nullptr;
}
```
The default value for `alignment` is `kWordAligned`. Reading the docs in the header it says that this function
will try to perform an allocation of size `88` in the `MAP_SPACE` and if it fails a full GC will be performed
and the allocation retried.
Lets take a look at `AllocateRawWithLigthRetry`:
```c++
AllocationResult alloc = AllocateRaw(size, space, alignment);
```
`AllocateRaw` can be found in `src/heap/heap-inl.h`. There are different paths that will be taken depending on the
`space` parameteter. Since it is `MAP_SPACE` in our case we will focus on that path:
```c++
AllocationResult Heap::AllocateRaw(int size_in_bytes, AllocationSpace space, AllocationAlignment alignment) {
...
HeapObject* object = nullptr;
AllocationResult allocation;
if (OLD_SPACE == space) {
...
} else if (MAP_SPACE == space) {
allocation = map_space_->AllocateRawUnaligned(size_in_bytes);
}
...
}
```
`map_space_` is a private member of Heap (src/heap/heap.h):
```c++
MapSpace* map_space_;
```
`AllocateRawUnaligned` can be found in `src/heap/spaces-inl.h`:
```c++
AllocationResult PagedSpace::AllocateRawUnaligned( int size_in_bytes, UpdateSkipList update_skip_list) {
if (!EnsureLinearAllocationArea(size_in_bytes)) {
return AllocationResult::Retry(identity());
}

HeapObject* object = AllocateLinearly(size_in_bytes);
MSAN_ALLOCATED_UNINITIALIZED_MEMORY(object->address(), size_in_bytes);
return object;
}
```
The default value for `update_skip_list` is `UPDATE_SKIP_LIST`.
So lets take a look at `AllocateLinearly`:
```c++
HeapObject* PagedSpace::AllocateLinearly(int size_in_bytes) {
Address current_top = allocation_info_.top();
Address new_top = current_top + size_in_bytes;
allocation_info_.set_top(new_top);
return HeapObject::FromAddress(current_top);
}
```
Recall that `size_in_bytes` in our case is `88`.
```console
(lldb) expr current_top
(v8::internal::Address) $5 = 24847457492680
(lldb) expr new_top
(v8::internal::Address) $6 = 24847457492768
(lldb) expr new_top - current_top
(unsigned long) $7 = 88
```
Notice that first the top is set to the new_top and then the current_top is returned and that will be a pointer
to the start of the object in memory (which in this case is of v8::internal::Map which is also of type HeapObject).
I've been wondering why Map (and other HeapObject) don't have any member fields and only/mostly
getters/setters for the various fields that make up an object. Well the answer is that pointers to instances of
for example Map point to the first memory location of the instance. And the getters/setter functions use indexed
to read/write to memory locations. The indexes are mostly in the form of enum fields that define the memory layout
of the type.

Next, in `AllocateRawUnaligned` we have the `MSAN_ALLOCATED_UNINITIALIZED_MEMORY` macro:
```c++
MSAN_ALLOCATED_UNINITIALIZED_MEMORY(object->address(), size_in_bytes);
```
`MSAN_ALLOCATED_UNINITIALIZED_MEMORY` can be found in `src/msan.h` and `ms` stands for `Memory Sanitizer` and
would only be used if `V8_US_MEMORY_SANITIZER` is defined.
The returned `object` will be used to construct an `AllocationResult` when returned.
Back in `AllocateRaw` we have:
```c++
if (allocation.To(&object)) {
...
OnAllocationEvent(object, size_in_bytes);
}

return allocation;
```
This will return us in `AllocateRawWithLightRetry`:
```c++
AllocationResult alloc = AllocateRaw(size, space, alignment);
if (alloc.To(&result)) {
DCHECK(result != exception());
return result;
}
```
This will return us back in `AllocateRawWithRetryOrFail`:
```c++
HeapObject* result = AllocateRawWithLigthRetry(size, space, alignment);
if (result) return result;
```
And that return will return to `NewMap` in `src/heap/factory.cc`:
```c++
result->set_map_after_allocation(*meta_map(), SKIP_WRITE_BARRIER);
return handle(InitializeMap(Map::cast(result), type, instance_size,
elements_kind, inobject_properties),
isolate());
```
`InitializeMap`:
```c++
map->set_instance_type(type);
map->set_prototype(*null_value(), SKIP_WRITE_BARRIER);
map->set_constructor_or_backpointer(*null_value(), SKIP_WRITE_BARRIER);
map->set_instance_size(instance_size);
if (map->IsJSObjectMap()) {
DCHECK(!isolate()->heap()->InReadOnlySpace(map));
map->SetInObjectPropertiesStartInWords(instance_size / kPointerSize - inobject_properties);
DCHECK_EQ(map->GetInObjectProperties(), inobject_properties);
map->set_prototype_validity_cell(*invalid_prototype_validity_cell());
} else {
DCHECK_EQ(inobject_properties, 0);
map->set_inobject_properties_start_or_constructor_function_index(0);
map->set_prototype_validity_cell(Smi::FromInt(Map::kPrototypeChainValid));
}
map->set_dependent_code(DependentCode::cast(*empty_fixed_array()), SKIP_WRITE_BARRIER);
map->set_weak_cell_cache(Smi::kZero);
map->set_raw_transitions(MaybeObject::FromSmi(Smi::kZero));
map->SetInObjectUnusedPropertyFields(inobject_properties);
map->set_instance_descriptors(*empty_descriptor_array());

map->set_visitor_id(Map::GetVisitorId(map));
map->set_bit_field(0);
map->set_bit_field2(Map::IsExtensibleBit::kMask);
int bit_field3 = Map::EnumLengthBits::encode(kInvalidEnumCacheSentinel) |
Map::OwnsDescriptorsBit::encode(true) |
Map::ConstructionCounterBits::encode(Map::kNoSlackTracking);
map->set_bit_field3(bit_field3);
map->set_elements_kind(elements_kind); //HOLEY_ELEMENTS
map->set_new_target_is_base(true);
isolate()->counters()->maps_created()->Increment();
if (FLAG_trace_maps) LOG(isolate(), MapCreate(map));
return map;
```

### Context
Context extends `FixedArray` (`src/context.h`). So an instance of this Context is a FixedArray and we can
use Get(index) etc to get entries in the array.

### V8_EXPORT
This can be found in quite a few places in v8 source code. For example:

class V8_EXPORT ArrayBuffer : public Object {

What is this?
It is a preprocessor macro which looks like this:

#if V8_HAS_ATTRIBUTE_VISIBILITY && defined(V8_SHARED)
# ifdef BUILDING_V8_SHARED
# define V8_EXPORT __attribute__ ((visibility("default")))
# else
# define V8_EXPORT
# endif
#else
# define V8_EXPORT
#endif

So we can see that if `V8_HAS_ATTRIBUTE_VISIBILITY`, and `defined(V8_SHARED)`, and also
if `BUILDING_V8_SHARED`, `V8_EXPORT` is set to `__attribute__ ((visibility("default"))`.
But in all other cases `V8_EXPORT` is empty and the preprocessor does not insert
anything (nothing will be there come compile time).
But what about the `__attribute__ ((visibility("default"))` what is this?

In the GNU compiler collection (GCC) environment, the term that is used for
exporting is visibility. As it applies to functions and variables in a shared
object, visibility refers to the ability of other shared objects to call a
C/C++ function. Functions with default visibility have a global scope and can
be called from other shared objects. Functions with hidden visibility have a
local scope and cannot be called from other shared objects.

Visibility can be controlled by using either compiler options or visibility attributes.
In your header files, wherever you want an interface or API made public outside
the current Dynamic Shared Object (DSO) , place
`__attribute__ ((visibility ("default")))` in struct, class and function
declarations you wish to make public. With `-fvisibility=hidden`, you are
telling GCC that every declaration not explicitly marked with a visibility
attribute has a hidden visibility. There is such a flag in build/common.gypi

### ToLocalChecked()
You'll see a few of these calls in the hello_world example:
```c++
Local source = String::NewFromUtf8(isolate, js, NewStringType::kNormal).ToLocalChecked();
```

NewFromUtf8 actually returns a Local wrapped in a MaybeLocal which forces a check to see if
the Local<> is empty before using it.
NewStringType is an enum which can be kNormalString (k for constant) or kInternalized.

The following is after running the preprocessor (clang -E src/api.cc):

# 5961 "src/api.cc"
Local String::NewFromUtf8(Isolate* isolate,
const char* data,
NewStringType type,
int length) {
MaybeLocal result;
if (length == 0) {
result = String::Empty(isolate);
} else if (length > i::String::kMaxLength) {
result = MaybeLocal();
} else {
i::Isolate* i_isolate = reinterpret_cast(isolate);
i::VMState __state__((i_isolate));
i::RuntimeCallTimerScope _runtime_timer( i_isolate, &i::RuntimeCallStats::API_String_NewFromUtf8);
LOG(i_isolate, ApiEntryCall("v8::" "String" "::" "NewFromUtf8"));
if (length < 0) length = StringLength(data);
i::Handle handle_result = NewString(i_isolate->factory(), static_cast(type), i::Vector(data, length)) .ToHandleChecked();
result = Utils::ToLocal(handle_result);
};
return result.FromMaybe(Local());;
}

I was wondering where the Utils::ToLocal was defined but could not find it until I found:

MAKE_TO_LOCAL(ToLocal, String, String)

#define MAKE_TO_LOCAL(Name, From, To) \
Local Utils::Name(v8::internal::Handle obj) { \
return Convert(obj); \
}

The above can be found in `src/api.h`. The same goes for `Local,
Local` etc.

### Small Integers
Reading through v8.h I came accross `// Tag information for Smi`
Smi stands for small integers.

A pointer is really just a integer that is treated like a memory address. We can
use that memory address to get the start of the data located in that memory slot.
But we can also just store an normal value like 18 in it. There might be cases
where it does not make sense to store a small integer somewhere in the heap and
have a pointer to it, but instead store the value directly in the pointer itself.
But that only works for small integers so there needs to be away to know if the
value we want is stored in the pointer or if we should follow the value stored to
the heap to get the value.

A word on a 64 bit machine is 8 bytes (64 bits) and all of the pointers need to
be aligned to multiples of 8. So a pointer could be:
```
1000 = 8
10000 = 16
11000 = 24
100000 = 32
1000000000 = 512
```
Remember that we are talking about the pointers and not the values store at
the memory location they point to. We can see that there are always three bits
that are zero in the pointers. So we can use them for something else and just
mask them out when using them as pointers.

Tagging involves borrowing one bit of the 32-bit, making it 31-bit and having
the leftover bit represent a tag. If the tag is zero then this is a plain value,
but if tag is 1 then the pointer must be followed.
This does not only have to be for numbers it could also be used for object (I think)

Instead the small integer is represented by the 32 bits plus a pointer to the
64-bit number. V8 needs to know if a value stored in memory represents a 32-bit
integer, or if it is really a 64-bit number, in which case it has to follow the
pointer to get the complete value. This is where the concept of tagging comes in.

### Properties/Elements
Take the following object:

{ firstname: "Jon", lastname: "Doe' }

The above object has two named properties. Named properties differ from integer indexed
which is what you have when you are working with arrays.

Memory layout of JavaScript Object:
```
Properties JavaScript Object Elements
+-----------+ +-----------------+ +----------------+
|property1 |<------+ | HiddenClass | +----->| |
+-----------+ | +-----------------+ | +----------------+
|... | +------| Properties | | | element1 |<------+
+-----------+ +-----------------+ | +----------------+ |
|... | | Elements |--+ | ... | |
+-----------+ +-----------------+ +----------------+ |
|propertyN | <---------------------+ | elementN | |
+-----------+ | +----------------+ |
| |
| |
| |
Named properties: { firstname: "Jon", lastname: "Doe' } Indexed Properties: {1: "Jon", 2: "Doe"}
```
We can see that properies and elements are stored in different data structures.
Elements are usually implemented as a plain array and the indexes can be used for fast access
to the elements.
But for the properties this is not the case. Instead there is a mapping between the property names
and the index into the properties.

In `src/objects/objects.h` we can find JSObject:

class JSObject: public JSReceiver {
...
DECL_ACCESSORS(elements, FixedArrayBase)

And looking a the `DECL_ACCESSOR` macro:

#define DECL_ACCESSORS(name, type) \
inline type* name() const; \
inline void set_##name(type* value, \
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

inline FixedArrayBase* name() const;
inline void set_elements(FixedArrayBase* value, WriteBarrierMode = UPDATE_WRITE_BARRIER)

Notice that JSObject extends JSReceiver which is extended by all types that can have properties defined on them. I think this includes all JSObjects and JSProxy. It is in JSReceiver that the we find the properties array:

DECL_ACCESSORS(raw_properties_or_hash, Object)

Now properties (named properties not elements) can be of different kinds internally. These work just
like simple dictionaries from the outside but a dictionary is only used in certain curcumstances
at runtime.

```
Properties JSObject HiddenClass (Map)
+-----------+ +-----------------+ +----------------+
|property1 |<------+ | HiddenClass |-------->| bit field1 |
+-----------+ | +-----------------+ +----------------+
|... | +------| Properties | | bit field2 |
+-----------+ +-----------------+ +----------------+
|... | | Elements | | bit field3 |
+-----------+ +-----------------+ +----------------+
|propertyN | | property1 |
+-----------+ +-----------------+
| property2 |
+-----------------+
| ... |
+-----------------+

```

#### JSObject
Each JSObject has as its first field a pointer to the generated HiddenClass. A hiddenclass contain mappings from property names to indices into the properties data type. When an instance of JSObject is created a `Map` is passed in.
As mentioned earlier JSObject inherits from JSReceiver which inherits from HeapObject

For example,in [jsobject_test.cc](./test/jsobject_test.cc) we first create a new Map using the internal Isolate Factory:

v8::internal::Handle map = factory->NewMap(v8::internal::JS_OBJECT_TYPE, 24);
v8::internal::Handle js_object = factory->NewJSObjectFromMap(map);
EXPECT_TRUE(js_object->HasFastProperties());

When we call `js_object->HasFastProperties()` this will delegate to the map instance:

return !map()->is_dictionary_map();

How do you add a property to a JSObject instance?
Take a look at [jsobject_test.cc](./test/jsobject_test.cc) for an example.

### Caching
Are ways to optimize polymorphic function calls in dynamic languages, for example JavaScript.

#### Lookup caches
Sending a message to a receiver requires the runtime to find the correct target method using
the runtime type of the receiver. A lookup cache maps the type of the receiver/message name
pair to methods and stores the most recently used lookup results. The cache is first consulted
and if there is a cache miss a normal lookup is performed and the result stored in the cache.

#### Inline caches
Using a lookup cache as described above still takes a considerable amount of time since the
cache must be probed for each message. It can be observed that the type of the target does often
not vary. If a call to type A is done at a particular call site it is very likely that the next
time it is called the type will also be A.
The method address looked up by the system lookup routine can be cached and the call instruction
can be overwritten. Subsequent calls for the same type can jump directly to the cached method and
completely avoid the lookup. The prolog of the called method must verify that the receivers
type has not changed and do the lookup if it has changed (the type if incorrect, no longer A for
example).

The target methods address is stored in the callers code, or "inline" with the callers code,
hence the name "inline cache".

If V8 is able to make a good assumption about the type of object that will be passed to a method,
it can bypass the process of figuring out how to access the objects properties, and instead use the stored information from previous lookups to the objects hidden class.

#### Polymorfic Inline cache (PIC)
A polymorfic call site is one where there are many equally likely receiver types (and thus
call targets).

- Monomorfic means there is only one receiver type
- Polymorfic a few receiver types
- Megamorfic very many receiver types

This type of caching extends inline caching to not just cache the last lookup, but cache
all lookup results for a given polymorfic call site using a specially generated stub.
Lets say we have a method that