{"id":13523515,"url":"https://github.com/danbev/learning-v8","last_synced_at":"2025-05-14T02:09:55.597Z","repository":{"id":38984398,"uuid":"57430512","full_name":"danbev/learning-v8","owner":"danbev","description":"Project for learning V8 internals","archived":false,"fork":false,"pushed_at":"2024-11-15T15:58:42.000Z","size":1760,"stargazers_count":2638,"open_issues_count":7,"forks_count":240,"subscribers_count":77,"default_branch":"master","last_synced_at":"2025-04-13T04:55:31.437Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danbev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["danbev"]}},"created_at":"2016-04-30T06:44:17.000Z","updated_at":"2025-04-12T23:35:37.000Z","dependencies_parsed_at":"2024-04-12T03:40:32.807Z","dependency_job_id":"c395190e-a572-4b91-9872-bdee3673f2a9","html_url":"https://github.com/danbev/learning-v8","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danbev%2Flearning-v8","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danbev%2Flearning-v8/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danbev%2Flearning-v8/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danbev%2Flearning-v8/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danbev","download_url":"https://codeload.github.com/danbev/learning-v8/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254053321,"owners_count":22006717,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T06:01:00.787Z","updated_at":"2025-05-14T02:09:50.586Z","avatar_url":"https://github.com/danbev.png","language":"C++","funding_links":["https://github.com/sponsors/danbev"],"categories":["Basic","C++","Chrome"],"sub_categories":[],"readme":"## Learning Google V8\nThe sole purpose of this project is to aid me in leaning Google's V8 JavaScript engine.\n\n\n### Contents\n1. [Introduction](./notes/intro.md)\n1. [Address](#address)\n1. [TaggedImpl](#taggedimpl)\n1. [Object](#object)\n1. [Handle](#handle)\n1. [FunctionTemplate](#functiontemplate)\n1. [ObjectTemplate](#objecttemplate)\n1. [Small Integers](#small-integers)\n1. [String types](./notes/string.md)\n1. [Roots](#roots)\n1. [Heap](./notes/heap.md)\n1. [Builtins](#builtins)\n1. [Compiler pipeline](#compiler-pipeline)\n1. [CodeStubAssembler](#codestubassembler)\n1. [Torque](#torque)\n1. [WebAssembly](#webassembly)\n1. [Promises](./notes/promises.md)\n1. [Snapshots](./notes/snapshots.md)\n1. [V8 Build artifacts](#v8-build-artifacts)\n1. [V8 Startup walkthrough](#startup-walk-through)\n1. [Building V8](#building-v8)\n1. [Contributing a change](#contributing-a-change)\n1. [Debugging](#debugging)\n1. [Building chromium](#building-chromium)\n1. [Goma chromium](#goma)\n1. [EcmaScript notes](./notes/ecmaspec.md)\n1. [GN notes](./notes/gn.md)\n\n### Isolate\nAn Isolate is an independant copy of the V8 runtime which includes its own heap.\nTwo different Isolates can run in parallel and can be seen as entirely different\nsandboxed instances of a V8 runtime.\n\n### Context\nTo allow separate JavaScript applications to run in the same isolate a context\nmust be specified for each one.  This is to avoid them interfering with each\nother, for example by changing the builtin objects provided.\n\n### Template\nThis is the super class of both ObjecTemplate and FunctionTemplate. Remember\nthat in JavaScript a function can have fields just like objects.\n\n```c++\nclass V8_EXPORT Template : public Data {\n public:\n  void Set(Local\u003cName\u003e name, Local\u003cData\u003e value,\n           PropertyAttribute attributes = None);\n  void SetPrivate(Local\u003cPrivate\u003e name, Local\u003cData\u003e value,\n                  PropertyAttribute attributes = None);\n  V8_INLINE void Set(Isolate* isolate, const char* name, Local\u003cData\u003e value);\n\n  void SetAccessorProperty(\n     Local\u003cName\u003e name,\n     Local\u003cFunctionTemplate\u003e getter = Local\u003cFunctionTemplate\u003e(),\n     Local\u003cFunctionTemplate\u003e setter = Local\u003cFunctionTemplate\u003e(),\n     PropertyAttribute attribute = None,\n     AccessControl settings = DEFAULT);\n```\nThe `Set` function can be used to have an name and a value set on an instance\ncreated from this template.\nThe `SetAccessorProperty` is for properties that are get/set using functions.\n\n```c++\nenum PropertyAttribute {\n  /** None. **/\n  None = 0,\n  /** ReadOnly, i.e., not writable. **/\n  ReadOnly = 1 \u003c\u003c 0,\n  /** DontEnum, i.e., not enumerable. **/\n  DontEnum = 1 \u003c\u003c 1,\n  /** DontDelete, i.e., not configurable. **/\n  DontDelete = 1 \u003c\u003c 2\n};\n\nenum AccessControl {\n  DEFAULT               = 0,\n  ALL_CAN_READ          = 1,\n  ALL_CAN_WRITE         = 1 \u003c\u003c 1,\n  PROHIBITS_OVERWRITING = 1 \u003c\u003c 2\n};\n```\n\n\n### ObjectTemplate\nThese allow you to create JavaScript objects without a dedicated constructor.\nWhen an instance is created using an ObjectTemplate the new instance will have\nthe properties and functions configured on the ObjectTemplate.\n\nThis would be something like:\n```js\nconst obj = {};\n```\nThis class is declared in include/v8.h and extends Template:\n```c++\nclass V8_EXPORT ObjectTemplate : public Template { \n  ...\n}\nclass V8_EXPORT Template : public Data {\n  ...\n}\nclass V8_EXPORT Data {\n private:                                                                       \n  Data();                                                                       \n};\n```\nWe create an instance of ObjectTemplate and we can add properties to it that\nall instance created using this ObjectTemplate instance will have. This is done\nby calling `Set` which is member of the `Template` class. You specify a\nLocal\u003cName\u003e for the property. `Name` is a superclass for `Symbol` and `String`\nwhich can be both be used as names for a property.\n\nThe implementation for `Set` can be found in `src/api/api.cc`:\n```c++\nvoid Template::Set(v8::Local\u003cName\u003e name, v8::Local\u003cData\u003e value, v8::PropertyAttribute attribute) {\n  ...\n\n  i::ApiNatives::AddDataProperty(isolate, templ, Utils::OpenHandle(*name),           \n                                 value_obj,                                     \n                                 static_cast\u003ci::PropertyAttributes\u003e(attribute));\n}\n```\n\nThere is an example in [objecttemplate_test.cc](./test/objecttemplate_test.cc)\n\n### FunctionTemplate\nIs a template that is used to create functions and like ObjectTemplate it inherits\nfrom Template:\n```c++\nclass V8_EXPORT FunctionTemplate : public Template {\n}\n```\nRememeber that a function in javascript can have properties just like object.\n\nThere is an example in [functiontemplate_test.cc](./test/functiontemplate_test.cc)\n\nAn instance of a function template can be created using:\n```c++\n  Local\u003cFunctionTemplate\u003e ft = FunctionTemplate::New(isolate_, function_callback, data);\n  Local\u003cFunction\u003e function = ft-\u003eGetFunction(context).ToLocalChecked();\n```\nAnd the function can be called using:\n```c++\n  MaybeLocal\u003cValue\u003e ret = function-\u003eCall(context, recv, 0, nullptr);\n```\nFunction::Call can be found in `src/api/api.cc`: \n```c++\n  bool has_pending_exception = false;\n  auto self = Utils::OpenHandle(this);                                               \n  i::Handle\u003ci::Object\u003e recv_obj = Utils::OpenHandle(*recv);                          \n  i::Handle\u003ci::Object\u003e* args = reinterpret_cast\u003ci::Handle\u003ci::Object\u003e*\u003e(argv);   \n  Local\u003cValue\u003e result;                                                               \n  has_pending_exception = !ToLocal\u003cValue\u003e(                                           \n      i::Execution::Call(isolate, self, recv_obj, argc, args), \u0026result);\n```\nNotice that the return value of `Call` which is a `MaybeHandle\u003cObject\u003e` will be\npassed to ToLocal\u003cValue\u003e which is defined in `api.h`:\n```c++\ntemplate \u003cclass T\u003e                                                              \ninline bool ToLocal(v8::internal::MaybeHandle\u003cv8::internal::Object\u003e maybe,      \n                    Local\u003cT\u003e* local) {                                          \n  v8::internal::Handle\u003cv8::internal::Object\u003e handle;                            \n  if (maybe.ToHandle(\u0026handle)) {                                                   \n    *local = Utils::Convert\u003cv8::internal::Object, T\u003e(handle);                   \n    return true;                                                                \n  }                                                                                \n  return false;                                                                 \n```\nSo lets take a look at `Execution::Call` which can be found in `execution/execution.cc`\nand it calls:\n```c++\nreturn Invoke(isolate, InvokeParams::SetUpForCall(isolate, callable, receiver, argc, argv));\n```\n`SetUpForCall` will return an `InvokeParams`.\nTODO: Take a closer look at InvokeParams.\n```c++\nV8_WARN_UNUSED_RESULT MaybeHandle\u003cObject\u003e Invoke(Isolate* isolate,              \n                                                 const InvokeParams\u0026 params) {\n```\n```c++\nHandle\u003cObject\u003e receiver = params.is_construct                             \n                                    ? isolate-\u003efactory()-\u003ethe_hole_value()         \n                                    : params.receiver; \n```\nIn our case `is_construct` is false as we are not using `new` and the receiver,\nthe `this` in the function should be set to the receiver that we passed in. After\nthat we have `Builtins::InvokeApiFunction` \n```c++\nauto value = Builtins::InvokeApiFunction(                                 \n          isolate, params.is_construct, function, receiver, params.argc,        \n          params.argv, Handle\u003cHeapObject\u003e::cast(params.new_target)); \n```\n\n```c++\nresult = HandleApiCallHelper\u003cfalse\u003e(isolate, function, new_target,        \n                                    fun_data, receiver, arguments);\n```\n\n`api-arguments-inl.h` has:\n```c++\nFunctionCallbackArguments::Call(CallHandlerInfo handler) {\n  ...\n  ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));                  \n  FunctionCallbackInfo\u003cv8::Value\u003e info(values_, argv_, argc_);                  \n  f(info);\n  return GetReturnValue\u003cObject\u003e(isolate);\n}\n```\nThe call to f(info) is what invokes the callback, which is just a normal\nfunction call. \n\nBack in `HandleApiCallHelper` we have:\n```c++\nHandle\u003cObject\u003e result = custom.Call(call_data);                             \n                                                                                \nRETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object);\n```\n`RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION` expands to:\n```c++\nHandle\u003cObject\u003e result = custom.Call(call_data);                             \ndo { \n  Isolate* __isolate__ = (isolate); \n  ((void) 0); \n  if (__isolate__-\u003ehas_scheduled_exception()) { \n    __isolate__-\u003ePromoteScheduledException(); \n    return MaybeHandle\u003cObject\u003e(); \n  }\n} while (false);\n```\nNotice that if there was an exception an empty object is returned.\nLater in `Invoke` in `execution.cc`a:\n```c++\n  auto value = Builtins::InvokeApiFunction(                                 \n          isolate, params.is_construct, function, receiver, params.argc,        \n          params.argv, Handle\u003cHeapObject\u003e::cast(params.new_target));            \n  bool has_exception = value.is_null();                                     \n  if (has_exception) {                                                      \n    if (params.message_handling == Execution::MessageHandling::kReport) {   \n      isolate-\u003eReportPendingMessages();                                     \n    }                                                                       \n    return MaybeHandle\u003cObject\u003e();                                           \n  } else {                                                                  \n    isolate-\u003eclear_pending_message();                                       \n  }                                                                         \n  return value;                         \n```\nLooking at this is looks like passing back an empty object will cause an \nexception to be triggered?\n\n### Address\n`Address` can be found in `include/v8-internal.h`:\n\n```c++\ntypedef uintptr_t Address;\n```\n`uintptr_t` is an optional type specified in `cstdint` and is capable of storing\na data pointer. It is an unsigned integer type that any valid pointer to void\ncan be converted to this type (and back).\n\n### TaggedImpl\nThis class is declared in `src/objects/tagged-impl.h and has a single private\nmember which is declared as:\n```c++\n public\n  constexpr StorageType ptr() const { return ptr_; }\n private:\n  StorageType ptr_;\n```\nAn instance can be created using:\n```c++\n  i::TaggedImpl\u003ci::HeapObjectReferenceType::STRONG, i::Address\u003e  tagged{};\n```\nStorage type can also be `Tagged_t` which is defined in globals.h:\n```c++\n using Tagged_t = uint32_t;\n```\nIt looks like it can be a different value when using pointer compression.\n\nSee [tagged_test.cc](./test/tagged_test.cc) for an example.\n\n### Object\nThis class extends TaggedImpl:\n```c++\nclass Object : public TaggedImpl\u003cHeapObjectReferenceType::STRONG, Address\u003e {       \n```\nAn Object can be created using the default constructor, or by passing in an \nAddress which will delegate to TaggedImpl constructors. Object itself does\nnot have any members (apart from `ptr_` which is inherited from TaggedImpl that is). \nSo if we create an Object on the stack this is like a pointer/reference to\nan object: \n```\n+------+\n|Object|\n|------|\n|ptr_  |----\u003e\n+------+\n```\nNow, `ptr_` is a StorageType so it could be a `Smi` in which case it would just\ncontains the value directly, for example a small integer:\n```\n+------+\n|Object|\n|------|\n|  18  |\n+------+\n```\nSee [object_test.cc](./test/object_test.cc) for an example.\n\n### ObjectSlot\n```c++\n  i::Object obj{18};\n  i::FullObjectSlot slot{\u0026obj};\n```\n\n```\n+----------+      +---------+\n|ObjectSlot|      | Object  |\n|----------|      |---------|\n| address  | ---\u003e |   18    |\n+----------+      +---------+\n```\nSee [objectslot_test.cc](./test/objectslot_test.cc) for an example.\n\n### Maybe\nA Maybe is like an optional which can either hold a value or nothing.\n```c++\ntemplate \u003cclass T\u003e                                                              \nclass Maybe {\n public:\n  V8_INLINE bool IsNothing() const { return !has_value_; }                      \n  V8_INLINE bool IsJust() const { return has_value_; }\n  ...\n\n private:\n  bool has_value_;                                                              \n  T value_; \n}\n```\nI first thought that name `Just` was a little confusing but if you read this\nlike:\n```c++\n  bool cond = true;\n  Maybe\u003cint\u003e maybe = cond ? Just\u003cint\u003e(10) : Nothing\u003cint\u003e();\n```\nI think it makes more sense. There are functions that check if the Maybe is\nnothing and crash the process if so. You can also check and return the value\nby using `FromJust`. \n\nThe usage of Maybe is where api calls can fail and returning Nothing is a way\nof signaling this.\n\nSee [maybe_test.cc](./test/maybe_test.cc) for an example.\n\n### MaybeLocal\n```c++\ntemplate \u003cclass T\u003e                                                              \nclass MaybeLocal {\n public:                                                                        \n  V8_INLINE MaybeLocal() : val_(nullptr) {} \n  V8_INLINE Local\u003cT\u003e ToLocalChecked();\n  V8_INLINE bool IsEmpty() const { return val_ == nullptr; }\n  template \u003cclass S\u003e                                                            \n  V8_WARN_UNUSED_RESULT V8_INLINE bool ToLocal(Local\u003cS\u003e* out) const {           \n    out-\u003eval_ = IsEmpty() ? nullptr : this-\u003eval_;                               \n    return !IsEmpty();                                                          \n  }    \n\n private:\n  T* val_;\n```\n`ToLocalChecked` will crash the process if `val_` is a nullptr. If you want to\navoid a crash one can use `ToLocal`.\n\nSee [maybelocal_test.cc](./test/maybelocal_test.cc) for an example.\n\n### Data\nIs the super class of all objects that can exist the V8 heap:\n```c++\nclass V8_EXPORT Data {                                                          \n private:                                                                       \n  Data();                                                                       \n};\n```\n\n### Value\nValue extends `Data` and adds a number of methods that check if a Value\nis of a certain type, like `IsUndefined()`, `IsNull`, `IsNumber` etc.\nIt also has useful methods to convert to a Local\u003cT\u003e, for example:\n```c++\nV8_WARN_UNUSED_RESULT MaybeLocal\u003cNumber\u003e ToNumber(Local\u003cContext\u003e context) const;\nV8_WARN_UNUSED_RESULT MaybeLocal\u003cString\u003e ToNumber(Local\u003cString\u003e context) const;\n...\n```\n\n\n### Handle\nA Handle is similar to a Object and ObjectSlot in that it also contains\nan Address member (called `location_` and declared in `HandleBase`), but with the\ndifference is that Handles acts as a layer of abstraction and can be relocated\nby the garbage collector.\nCan be found in `src/handles/handles.h`.\n\n```c++\nclass HandleBase {  \n ...\n protected:\n  Address* location_; \n}\ntemplate \u003ctypename T\u003e                                                           \nclass Handle final : public HandleBase {\n  ...\n}\n```\n\n```\n+----------+                  +--------+         +---------+\n|  Handle  |                  | Object |         |   int   |\n|----------|      +-----+     |--------|         |---------|\n|*location_| ---\u003e |\u0026ptr_| --\u003e | ptr_   | -----\u003e  |     5   |\n+----------+      +-----+     +--------+         +---------+\n```\n```console\n(gdb) p handle\n$8 = {\u003cv8::internal::HandleBase\u003e = {location_ = 0x7ffdf81d60c0}, \u003cNo data fields\u003e}\n```\nNotice that `location_` contains a pointer:\n```console\n(gdb) p /x *(int*)0x7ffdf81d60c0\n$9 = 0xa9d330\n```\nAnd this is the same as the value in obj:\n```console\n(gdb) p /x obj.ptr_\n$14 = 0xa9d330\n```\nAnd we can access the int using any of the pointers:\n```console\n(gdb) p /x *value\n$16 = 0x5\n(gdb) p /x *obj.ptr_\n$17 = 0x5\n(gdb) p /x *(int*)0x7ffdf81d60c0\n$18 = 0xa9d330\n(gdb) p /x *(*(int*)0x7ffdf81d60c0)\n$19 = 0x5\n```\n\nSee [handle_test.cc](./test/handle_test.cc) for an example.\n\n### HandleScope\nContains a number of Local/Handle's (think pointers to objects but is managed\nby V8) and will take care of deleting the Local/Handles for us. HandleScopes\nare stack allocated\n\nWhen ~HandleScope is called all handles created within that scope are removed\nfrom the stack maintained by the HandleScope which makes objects to which the\nhandles point being eligible for deletion from the heap by the GC.\n\nA HandleScope only has three members:\n```c++\n  internal::Isolate* isolate_;\n  internal::Address* prev_next_;\n  internal::Address* prev_limit_;\n```\n\nLets take a closer look at what happens when we construct a HandleScope:\n```c++\n  v8::HandleScope handle_scope{isolate_};\n```\nThe constructor call will end up in `src/api/api.cc` and the constructor simply\ndelegates to `Initialize`:\n```c++\nHandleScope::HandleScope(Isolate* isolate) { Initialize(isolate); }\n\nvoid HandleScope::Initialize(Isolate* isolate) {\n  i::Isolate* internal_isolate = reinterpret_cast\u003ci::Isolate*\u003e(isolate);\n  ...\n  i::HandleScopeData* current = internal_isolate-\u003ehandle_scope_data();\n  isolate_ = internal_isolate;\n  prev_next_ = current-\u003enext;\n  prev_limit_ = current-\u003elimit;\n  current-\u003elevel++;\n}\n```\nEvery `v8::internal::Isolate` has member of type HandleScopeData:\n```c++\nHandleScopeData* handle_scope_data() { return \u0026handle_scope_data_; }\nHandleScopeData handle_scope_data_;\n```\nHandleScopeData is a struct defined in `src/handles/handles.h`:\n```c++\nstruct HandleScopeData final {\n  Address* next;\n  Address* limit;\n  int level;\n  int sealed_level;\n  CanonicalHandleScope* canonical_scope;\n\n  void Initialize() {\n    next = limit = nullptr;\n    sealed_level = level = 0;\n    canonical_scope = nullptr;\n  }\n};\n```\nNotice that there are two pointers (Address*) to next and a limit. When a \nHandleScope is Initialized the current handle_scope_data will be retrieved \nfrom the internal isolate. The HandleScope instance that is getting created\nstores the next/limit pointers of the current isolate so that they can be restored\nwhen this HandleScope is closed (see CloseScope).\n\nSo with a HandleScope created, how does a Local\u003cT\u003e interact with this instance?  \n\nWhen a Local\u003cT\u003e is created this will/might go through FactoryBase::NewStruct\nwhich will allocate a new Map and then create a Handle for the InstanceType\nbeing created:\n```c++\nHandle\u003cStruct\u003e str = handle(Struct::cast(result), isolate()); \n```\nThis will land in the constructor Handle\u003cT\u003esrc/handles/handles-inl.h\n```c++\ntemplate \u003ctypename T\u003e                                                           \nHandle\u003cT\u003e::Handle(T object, Isolate* isolate): HandleBase(object.ptr(), isolate) {}\n\nHandleBase::HandleBase(Address object, Isolate* isolate)                        \n    : location_(HandleScope::GetHandle(isolate, object)) {}\n```\nNotice that `object.ptr()` is used to pass the Address to HandleBase.\nAnd also notice that HandleBase sets its location_ to the result of HandleScope::GetHandle.\n\n```c++\nAddress* HandleScope::GetHandle(Isolate* isolate, Address value) {              \n  DCHECK(AllowHandleAllocation::IsAllowed());                                   \n  HandleScopeData* data = isolate-\u003ehandle_scope_data();                         \n  CanonicalHandleScope* canonical = data-\u003ecanonical_scope;                      \n  return canonical ? canonical-\u003eLookup(value) : CreateHandle(isolate, value);   \n}\n```\nWhich will call `CreateHandle` in this case and this function will retrieve the\ncurrent isolate's handle_scope_data:\n```c++\n  HandleScopeData* data = isolate-\u003ehandle_scope_data();                         \n  Address* result = data-\u003enext;                                                 \n  if (result == data-\u003elimit) {                                                  \n    result = Extend(isolate);                                                   \n  }     \n```\nIn this case both next and limit will be 0x0 so Extend will be called.\nExtend will also get the isolates handle_scope_data and check the current level\nand after that get the isolates HandleScopeImplementer:\n```c++\n  HandleScopeImplementer* impl = isolate-\u003ehandle_scope_implementer();           \n```\n`HandleScopeImplementer` is declared in `src/api/api.h`\n\nHandleScope:CreateHandle will get the handle_scope_data from the isolate:\n```c++\nAddress* HandleScope::CreateHandle(Isolate* isolate, Address value) {\n  HandleScopeData* data = isolate-\u003ehandle_scope_data();\n  if (result == data-\u003elimit) {\n    result = Extend(isolate);\n  }\n  // Update the current next field, set the value in the created handle,        \n  // and return the result.\n  data-\u003enext = reinterpret_cast\u003cAddress*\u003e(reinterpret_cast\u003cAddress\u003e(result) + sizeof(Address));\n  *result = value;\n  return result;\n}                         \n```\nNotice that `data-\u003enext` is set to the address passed in + the size of an\nAddress.\n\n\nThe destructor for HandleScope will call CloseScope.\nSee [handlescope_test.cc](./test/handlescope_test.cc) for an example.\n\n### EscapableHandleScope\nLocal handles are located on the stack and are deleted when the appropriate\ndestructor is called. If there is a local HandleScope then it will take care\nof this when the scope returns. When there are no references left to a handle\nit can be garbage collected. This means if a function has a HandleScope and\nwants to return a handle/local it will not be available after the function\nreturns. This is what EscapableHandleScope is for, it enable the value to be\nplaced in the enclosing handle scope to allow it to survive. When the enclosing\nHandleScope goes out of scope it will be cleaned up.\n\n```c++\nclass V8_EXPORT EscapableHandleScope : public HandleScope {                        \n public:                                                                           \n  explicit EscapableHandleScope(Isolate* isolate);\n  V8_INLINE ~EscapableHandleScope() = default;\n  template \u003cclass T\u003e\n  V8_INLINE Local\u003cT\u003e Escape(Local\u003cT\u003e value) {\n    internal::Address* slot = Escape(reinterpret_cast\u003cinternal::Address*\u003e(*value));\n    return Local\u003cT\u003e(reinterpret_cast\u003cT*\u003e(slot));\n  }\n\n  template \u003cclass T\u003e\n  V8_INLINE MaybeLocal\u003cT\u003e EscapeMaybe(MaybeLocal\u003cT\u003e value) {\n    return Escape(value.FromMaybe(Local\u003cT\u003e()));\n  }\n\n private:\n  ...\n  internal::Address* escape_slot_;\n};\n```\n\nFrom `api.cc`\n```c++\nEscapableHandleScope::EscapableHandleScope(Isolate* v8_isolate) {\n  i::Isolate* isolate = reinterpret_cast\u003ci::Isolate*\u003e(v8_isolate);\n  escape_slot_ = CreateHandle(isolate, i::ReadOnlyRoots(isolate).the_hole_value().ptr());\n  Initialize(v8_isolate);\n}\n```\nSo when an EscapableHandleScope is created it will create a handle with the\nhole value and store it in the `escape_slot_` which is of type Address. This\nHandle will be created in the current HandleScope, and EscapableHandleScope\ncan later set a value for that pointer/address which it want to be escaped.\nLater when that HandleScope goes out of scope it will be cleaned up.\nIt then calls Initialize just like a normal HandleScope would.\n\n```c++\ni::Address* HandleScope::CreateHandle(i::Isolate* isolate, i::Address value) {\n  return i::HandleScope::CreateHandle(isolate, value);\n}\n```\nFrom `handles-inl.h`:\n```c++\nAddress* HandleScope::CreateHandle(Isolate* isolate, Address value) {\n  DCHECK(AllowHandleAllocation::IsAllowed());\n  HandleScopeData* data = isolate-\u003ehandle_scope_data();\n  Address* result = data-\u003enext;\n  if (result == data-\u003elimit) {\n    result = Extend(isolate);\n  }\n  // Update the current next field, set the value in the created handle,\n  // and return the result.\n  DCHECK_LT(reinterpret_cast\u003cAddress\u003e(result),\n            reinterpret_cast\u003cAddress\u003e(data-\u003elimit));\n  data-\u003enext = reinterpret_cast\u003cAddress*\u003e(reinterpret_cast\u003cAddress\u003e(result) +\n                                          sizeof(Address));\n  *result = value;\n  return result;\n}\n```\n\nWhen Escape is called the following happens (v8.h):\n```c++\ntemplate \u003cclass T\u003e\n  V8_INLINE Local\u003cT\u003e Escape(Local\u003cT\u003e value) {\n    internal::Address* slot = Escape(reinterpret_cast\u003cinternal::Address*\u003e(*value));\n    return Local\u003cT\u003e(reinterpret_cast\u003cT*\u003e(slot));\n  }\n```\nAn the EscapeableHandleScope::Escape (api.cc):\n```c++\ni::Address* EscapableHandleScope::Escape(i::Address* escape_value) {\n  i::Heap* heap = reinterpret_cast\u003ci::Isolate*\u003e(GetIsolate())-\u003eheap();\n  Utils::ApiCheck(i::Object(*escape_slot_).IsTheHole(heap-\u003eisolate()),\n                  \"EscapableHandleScope::Escape\", \"Escape value set twice\");\n  if (escape_value == nullptr) {\n    *escape_slot_ = i::ReadOnlyRoots(heap).undefined_value().ptr();\n    return nullptr;\n  }\n  *escape_slot_ = *escape_value;\n  return escape_slot_;\n}\n```\nIf the escape_value is null, the `escape_slot` that is a pointer into the \nparent HandleScope is set to the undefined_value() instead of the hole value\nwhich is was previously, and nullptr will be returned. This returned \naddress/pointer will then be returned after being casted to T*.\nNext, we take a look at what happens when the EscapableHandleScope goes out of\nscope. This will call HandleScope::~HandleScope which makes sense as any other\nLocal handles should be cleaned up.\n\n`Escape` copies the value of its argument into the enclosing scope, deletes alli\nits local handles, and then gives back the new handle copy which can safely be\nreturned.\n\n### HeapObject\nTODO:\n\n### Local\nHas a single member `val_` which is of type pointer to `T`:\n```c++\ntemplate \u003cclass T\u003e class Local { \n...\n private:\n  T* val_\n}\n```\nNotice that this is a pointer to T. We could create a local using:\n```c++\n  v8::Local\u003cv8::Value\u003e empty_value;\n```\n\nSo a Local contains a pointer to type T. We can access this pointer using\n`operator-\u003e` and `operator*`.\n\nWe can cast from a subtype to a supertype using Local::Cast:\n```c++\nv8::Local\u003cv8::Number\u003e nr = v8::Local\u003cv8::Number\u003e(v8::Number::New(isolate_, 12));\nv8::Local\u003cv8::Value\u003e val = v8::Local\u003cv8::Value\u003e::Cast(nr);\n```\nAnd there is also the \n```c++\nv8::Local\u003cv8::Value\u003e val2 = nr.As\u003cv8::Value\u003e();\n```\n\nSee [local_test.cc](./test/local_test.cc) for an example.\n\n### PrintObject\nUsing _v8_internal_Print_Object from c++:\n```console\n$ nm -C libv8_monolith.a | grep Print_Object\n0000000000000000 T _v8_internal_Print_Object(void*)\n```\nNotice that this function does not have a namespace.\nWe can use this as:\n```c++\nextern void _v8_internal_Print_Object(void* object);\n\n_v8_internal_Print_Object(*((v8::internal::Object**)(*global)));\n```\nLets take a closer look at the above:\n```c++\n  v8::internal::Object** gl = ((v8::internal::Object**)(*global));\n```\nWe use the dereference operator to get the value of a Local (*global), which is\njust of type `T*`, a pointer to the type the Local:\n```c++\ntemplate \u003cclass T\u003e\nclass Local {\n  ...\n private:\n  T* val_;\n}\n```\n\nWe are then casting that to be of type pointer-to-pointer to Object.\n```\n  gl**        Object*         Object\n+-----+      +------+      +-------+\n|     |-----\u003e|      |-----\u003e|       |\n+-----+      +------+      +-------+\n```\nAn instance of `v8::internal::Object` only has a single data member which is a\nfield named `ptr_` of type `Address`:\n\n`src/objects/objects.h`:\n```c++\nclass Object : public TaggedImpl\u003cHeapObjectReferenceType::STRONG, Address\u003e {\n public:\n  constexpr Object() : TaggedImpl(kNullAddress) {}\n  explicit constexpr Object(Address ptr) : TaggedImpl(ptr) {}\n\n#define IS_TYPE_FUNCTION_DECL(Type) \\\n  V8_INLINE bool Is##Type() const;  \\\n  V8_INLINE bool Is##Type(const Isolate* isolate) const;\n  OBJECT_TYPE_LIST(IS_TYPE_FUNCTION_DECL)\n  HEAP_OBJECT_TYPE_LIST(IS_TYPE_FUNCTION_DECL)\n  IS_TYPE_FUNCTION_DECL(HashTableBase)\n  IS_TYPE_FUNCTION_DECL(SmallOrderedHashTable)\n#undef IS_TYPE_FUNCTION_DECL\n  V8_INLINE bool IsNumber(ReadOnlyRoots roots) const;\n}\n```\nLets take a look at one of these functions and see how it is implemented. For\nexample in the OBJECT_TYPE_LIST we have:\n```c++\n#define OBJECT_TYPE_LIST(V) \\\n  V(LayoutDescriptor)       \\\n  V(Primitive)              \\\n  V(Number)                 \\\n  V(Numeric)\n```\nSo the object class will have a function that looks like:\n```c++\ninline bool IsNumber() const;\ninline bool IsNumber(const Isolate* isolate) const;\n```\nAnd in src/objects/objects-inl.h we will have the implementations:\n```c++\nbool Object::IsNumber() const {\n  return IsHeapObject() \u0026\u0026 HeapObject::cast(*this).IsNumber();\n}\n```\n`IsHeapObject` is defined in TaggedImpl:\n```c++\n  constexpr inline bool IsHeapObject() const { return IsStrong(); }\n\n  constexpr inline bool IsStrong() const {\n#if V8_HAS_CXX14_CONSTEXPR\n    DCHECK_IMPLIES(!kCanBeWeak, !IsSmi() == HAS_STRONG_HEAP_OBJECT_TAG(ptr_));\n#endif\n    return kCanBeWeak ? HAS_STRONG_HEAP_OBJECT_TAG(ptr_) : !IsSmi();\n  }\n```\n\nThe macro can be found in src/common/globals.h:\n```c++\n#define HAS_STRONG_HEAP_OBJECT_TAG(value)                          \\\n  (((static_cast\u003ci::Tagged_t\u003e(value) \u0026 ::i::kHeapObjectTagMask) == \\\n    ::i::kHeapObjectTag))\n```\nSo we are casting `ptr_` which is of type Address into type `Tagged_t` which\nis defined in src/common/global.h and can be different depending on if compressed\npointers are used or not. If they are  not supported it is the same as Address:\n``` \nusing Tagged_t = Address;\n```\n\n`src/objects/tagged-impl.h`:\n```c++\ntemplate \u003cHeapObjectReferenceType kRefType, typename StorageType\u003e\nclass TaggedImpl {\n\n  StorageType ptr_;\n}\n```\nThe HeapObjectReferenceType can be either WEAK or STRONG. And the storage type\nis `Address` in this case. So Object itself only has one member that is inherited\nfrom its only super class and this is `ptr_`.\n\nSo the following is telling the compiler to treat the value of our Local,\n`*global`, as a pointer (which it already is) to a pointer that points to\na memory location that adhers to the layout of an `v8::internal::Object` type,\nwhich we know now has a `prt_` member. And we want to dereference it and pass\nit into the function.\n```c++\n_v8_internal_Print_Object(*((v8::internal::Object**)(*global)));\n```\n\n### ObjectTemplate\nBut I'm still missing the connection between ObjectTemplate and object.\nWhen we create it we use:\n```c++\nLocal\u003cObjectTemplate\u003e global = ObjectTemplate::New(isolate);\n```\nIn `src/api/api.cc` we have:\n```c++\nstatic Local\u003cObjectTemplate\u003e ObjectTemplateNew(\n    i::Isolate* isolate, v8::Local\u003cFunctionTemplate\u003e constructor,\n    bool do_not_cache) {\n  i::Handle\u003ci::Struct\u003e struct_obj = isolate-\u003efactory()-\u003eNewStruct(\n      i::OBJECT_TEMPLATE_INFO_TYPE, i::AllocationType::kOld);\n  i::Handle\u003ci::ObjectTemplateInfo\u003e obj = i::Handle\u003ci::ObjectTemplateInfo\u003e::cast(struct_obj);\n  InitializeTemplate(obj, Consts::OBJECT_TEMPLATE);\n  int next_serial_number = 0;\n  if (!constructor.IsEmpty())\n    obj-\u003eset_constructor(*Utils::OpenHandle(*constructor));\n  obj-\u003eset_data(i::Smi::zero());\n  return Utils::ToLocal(obj);\n}\n```\nWhat is a `Struct` in this context?  \n`src/objects/struct.h`\n```c++\n#include \"torque-generated/class-definitions-tq.h\"\n\nclass Struct : public TorqueGeneratedStruct\u003cStruct, HeapObject\u003e {\n public:\n  inline void InitializeBody(int object_size);\n  void BriefPrintDetails(std::ostream\u0026 os);\n  TQ_OBJECT_CONSTRUCTORS(Struct)\n```\nNotice that the include is specifying `torque-generated` include which can be\nfound `out/x64.release_gcc/gen/torque-generated/class-definitions-tq`. So, somewhere\nthere must be an call to the `torque` executable which generates the Code Stub\nAssembler C++ headers and sources before compiling the main source files. There is\nand there is a section about this in `Building V8`.\nThe macro `TQ_OBJECT_CONSTRUCTORS` can be found in `src/objects/object-macros.h`\nand expands to:\n```c++\n  constexpr Struct() = default;\n\n protected:\n  template \u003ctypename TFieldType, int kFieldOffset\u003e\n  friend class TaggedField;\n\n  inline explicit Struct(Address ptr);\n```\n\nSo what does the TorqueGeneratedStruct look like?\n```\ntemplate \u003cclass D, class P\u003e\nclass TorqueGeneratedStruct : public P {\n public:\n```\nWhere D is Struct and P is HeapObject in this case. But the above is the declartion\nof the type but what we have in the .h file is what was generated. \n\nThis type is defined in `src/objects/struct.tq`:\n```\n@abstract                                                                       \n@generatePrint                                                                  \n@generateCppClass                                                               \nextern class Struct extends HeapObject {                                        \n} \n```\n\n`NewStruct` can be found in `src/heap/factory-base.cc`\n```c++\ntemplate \u003ctypename Impl\u003e\nHandleFor\u003cImpl, Struct\u003e FactoryBase\u003cImpl\u003e::NewStruct(\n    InstanceType type, AllocationType allocation) {\n  Map map = Map::GetStructMap(read_only_roots(), type);\n  int size = map.instance_size();\n  HeapObject result = AllocateRawWithImmortalMap(size, allocation, map);\n  HandleFor\u003cImpl, Struct\u003e str = handle(Struct::cast(result), isolate());\n  str-\u003eInitializeBody(size);\n  return str;\n}\n```\nEvery object that is stored on the v8 heap has a Map (`src/objects/map.h`) that\ndescribes the structure of the object being stored.\n```c++\nclass Map : public HeapObject {\n```\n\n```console\n1725\t  return Utils::ToLocal(obj);\n(gdb) p obj\n$6 = {\u003cv8::internal::HandleBase\u003e = {location_ = 0x30b5160}, \u003cNo data fields\u003e}\n```\nSo this is the connection, what we see as a Local\u003cObjectTemplate\u003e is a HandleBase.\nTODO: dig into this some more when I have time.\n\n\n```console\n(lldb) expr gl\n(v8::internal::Object **) $0 = 0x00000000020ee160\n(lldb) memory read -f x -s 8 -c 1 gl\n0x020ee160: 0x00000aee081c0121\n\n(lldb) memory read -f x -s 8 -c 1 *gl\n0xaee081c0121: 0x0200000002080433\n```\n\n\nYou can reload `.lldbinit` using the following command:\n```console\n(lldb) command source ~/.lldbinit\n```\nThis can be useful when debugging a lldb command. You can set a breakpoint\nand break at that location and make updates to the command and reload without\nhaving to restart lldb.\n\nCurrently, the lldb-commands.py that ships with v8 contains an extra operation\nof the parameter pased to `ptr_arg_cmd`:\n```python\ndef ptr_arg_cmd(debugger, name, param, cmd):                                    \n  if not param:                                                                 \n    print(\"'{}' requires an argument\".format(name))                             \n    return                                                                      \n  param = '(void*)({})'.format(param)                                           \n  no_arg_cmd(debugger, cmd.format(param)) \n```\nNotice that `param` is the object that we want to print, for example lets say\nit is a local named obj:\n```\nparam = \"(void*)(obj)\"\n```\nThis will then be \"passed\"/formatted into the command string:\n```\n\"_v8_internal_Print_Object(*(v8::internal::Object**)(*(void*)(obj))\")\n```\n\n#### Threads\nV8 is single threaded (the execution of the functions of the stack) but there\nare supporting threads used for garbage collection, profiling (IC, and perhaps\nother things) (I think).\nLets see what threads there are:\n\n    $ LD_LIBRARY_PATH=../v8_src/v8/out/x64.release_gcc/ lldb ./hello-world \n    (lldb) br s -n main\n    (lldb) r\n    (lldb) thread list\n    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\n\nSo at startup there is only one thread which is what we expected. Lets skip ahead to where we create the platform:\n\n    Platform* platform = platform::CreateDefaultPlatform();\n    ...\n    DefaultPlatform* platform = new DefaultPlatform(idle_task_support, tracing_controller);\n    platform-\u003eSetThreadPoolSize(thread_pool_size);\n\n    (lldb) fr v thread_pool_size\n    (int) thread_pool_size = 0\n\nNext there is a check for 0 and the number of processors -1 is used as the size of the thread pool:\n\n    (lldb) fr v thread_pool_size\n    (int) thread_pool_size = 7\n\nThis is all that `SetThreadPoolSize` does. After this we have:\n\n    platform-\u003eEnsureInitialized();\n\n    for (int i = 0; i \u003c thread_pool_size_; ++i)\n      thread_pool_.push_back(new WorkerThread(\u0026queue_));\n\n`new WorkerThread` will create a new pthread (on my system which is MacOSX):\n\n    result = pthread_create(\u0026data_-\u003ethread_, \u0026attr, ThreadEntry, this);\n\nThreadEntry can be found in src/base/platform/platform-posix.\n\n\n### International Component for Unicode (ICU)\nInternational Components for Unicode (ICU) deals with internationalization (i18n).\nICU provides support locale-sensitve string comparisons, date/time/number/currency formatting\netc. \n\nThere is an optional API called ECMAScript 402 which V8 suppports and which is enabled by\ndefault. [i18n-support](https://github.com/v8/v8/wiki/i18n-support) says that even if your application does \nnot use ICU you still need to call InitializeICU :\n\n    V8::InitializeICU();\n\n### Local\n\n```c++\nLocal\u003cString\u003e script_name = ...;\n```\nSo what is script_name. Well it is an object reference that is managed by the v8 GC.\nThe GC needs to be able to move things (pointers around) and also track if\nthings should be GC'd. Local handles as opposed to persistent handles are light\nweight and mostly used local operations. These handles are managed by\nHandleScopes so you must have a handlescope on the stack and the local is only\nvalid as long as the handlescope is valid. This uses Resource Acquisition Is\nInitialization (RAII) so when the HandleScope instance goes out of scope it\nwill remove all the Local instances.\n\nThe `Local` class (in `include/v8.h`) only has one member which is of type\npointer to the type `T`. So for the above example it would be:\n```c++\n  String* val_;\n```\nYou can find the available operations for a Local in `include/v8.h`.\n\n```shell\n(lldb) p script_name.IsEmpty()\n(bool) $12 = false\n````\n\nA Local\u003cT\u003e has overloaded a number of operators, for example -\u003e:\n```shell\n(lldb) p script_name-\u003eLength()\n(int) $14 = 7\n````\nWhere Length is a method on the v8 String class.\n\nThe handle stack is not part of the C++ call stack, but the handle scopes are\nembedded in the C++ stack. Handle scopes can only be stack-allocated, not\nallocated with new.\n\n### Persistent\nhttps://v8.dev/docs/embed:\nPersistent handles provide a reference to a heap-allocated JavaScript Object, \njust like a local handle. There are two flavors, which differ in the lifetime\nmanagement of the reference they handle. Use a persistent handle when you need\nto keep a reference to an object for more than one function call, or when handle\nlifetimes do not correspond to C++ scopes. Google Chrome, for example, uses\npersistent handles to refer to Document Object Model (DOM) nodes.\n\nA persistent handle can be made weak, using PersistentBase::SetWeak, to trigger\na callback from the garbage collector when the only references to an object are\nfrom weak persistent handles.\n\n\nA UniquePersistent\u003cSomeType\u003e handle relies on C++ constructors and destructors\nto manage the lifetime of the underlying object.\nA Persistent\u003cSomeType\u003e can be constructed with its constructor, but must be\nexplicitly cleared with Persistent::Reset.\n\nSo how is a persistent object created?  \nLet's write a test and find out (`test/persistent-object_text.cc`):\n```console\n$ make test/persistent-object_test\n$ ./test/persistent-object_test --gtest_filter=PersistentTest.value\n```\nNow, to create an instance of Persistent we need a Local\u003cT\u003e instance or the\nPersistent instance will just be empty.\n```c++\nLocal\u003cObject\u003e o = Local\u003cObject\u003e::New(isolate_, Object::New(isolate_));\n```\n`Local\u003cObject\u003e::New` can be found in `src/api/api.cc`:\n```c++\nLocal\u003cv8::Object\u003e v8::Object::New(Isolate* isolate) {\n  i::Isolate* i_isolate = reinterpret_cast\u003ci::Isolate*\u003e(isolate);\n  LOG_API(i_isolate, Object, New);\n  ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate);\n  i::Handle\u003ci::JSObject\u003e obj =\n      i_isolate-\u003efactory()-\u003eNewJSObject(i_isolate-\u003eobject_function());\n  return Utils::ToLocal(obj);\n}\n```\nThe first thing that happens is that the public Isolate pointer is cast to an\npointer to the internal `Isolate` type.\n`LOG_API` is a macro in the same source file (src/api/api.cc):\n```c++\n#define LOG_API(isolate, class_name, function_name)                           \\\n  i::RuntimeCallTimerScope _runtime_timer(                                    \\\n      isolate, i::RuntimeCallCounterId::kAPI_##class_name##_##function_name); \\\n  LOG(isolate, ApiEntryCall(\"v8::\" #class_name \"::\" #function_name))\n```\nIf our case the preprocessor would expand that to:\n```c++\n  i::RuntimeCallTimerScope _runtime_timer(\n      isolate, i::RuntimeCallCounterId::kAPI_Object_New);\n  LOG(isolate, ApiEntryCall(\"v8::Object::New))\n```\n`LOG` is a macro that can be found in `src/log.h`:\n```c++\n#define LOG(isolate, Call)                              \\\n  do {                                                  \\\n    v8::internal::Logger* logger = (isolate)-\u003elogger(); \\\n    if (logger-\u003eis_logging()) logger-\u003eCall;             \\\n  } while (false)\n```\nAnd this would expand to:\n```c++\n  v8::internal::Logger* logger = isolate-\u003elogger();\n  if (logger-\u003eis_logging()) logger-\u003eApiEntryCall(\"v8::Object::New\");\n```\nSo with the LOG_API macro expanded we have:\n```c++\nLocal\u003cv8::Object\u003e v8::Object::New(Isolate* isolate) {\n  i::Isolate* i_isolate = reinterpret_cast\u003ci::Isolate*\u003e(isolate);\n  i::RuntimeCallTimerScope _runtime_timer( isolate, i::RuntimeCallCounterId::kAPI_Object_New);\n  v8::internal::Logger* logger = isolate-\u003elogger();\n  if (logger-\u003eis_logging()) logger-\u003eApiEntryCall(\"v8::Object::New\");\n\n  ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate);\n  i::Handle\u003ci::JSObject\u003e obj =\n      i_isolate-\u003efactory()-\u003eNewJSObject(i_isolate-\u003eobject_function());\n  return Utils::ToLocal(obj);\n}\n```\nNext we have `ENTER_V8_NO_SCRIPT_NO_EXCEPTION`:\n```c++\n#define ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate)                    \\\n  i::VMState\u003cv8::OTHER\u003e __state__((isolate));                       \\\n  i::DisallowJavascriptExecutionDebugOnly __no_script__((isolate)); \\\n  i::DisallowExceptions __no_exceptions__((isolate))\n```\nSo with the macros expanded we have:\n```c++\nLocal\u003cv8::Object\u003e v8::Object::New(Isolate* isolate) {\n  i::Isolate* i_isolate = reinterpret_cast\u003ci::Isolate*\u003e(isolate);\n  i::RuntimeCallTimerScope _runtime_timer( isolate, i::RuntimeCallCounterId::kAPI_Object_New);\n  v8::internal::Logger* logger = isolate-\u003elogger();\n  if (logger-\u003eis_logging()) logger-\u003eApiEntryCall(\"v8::Object::New\");\n\n  i::VMState\u003cv8::OTHER\u003e __state__(i_isolate));\n  i::DisallowJavascriptExecutionDebugOnly __no_script__(i_isolate);\n  i::DisallowExceptions __no_exceptions__(i_isolate));\n\n  i::Handle\u003ci::JSObject\u003e obj =\n      i_isolate-\u003efactory()-\u003eNewJSObject(i_isolate-\u003eobject_function());\n\n  return Utils::ToLocal(obj);\n}\n```\nTODO: Look closer at `VMState`.  \n\nFirst, `i_isolate-\u003eobject_function()` is called and the result passed to\n`NewJSObject`. `object_function` is generated by a macro named \n`NATIVE_CONTEXT_FIELDS`:\n```c++\n#define NATIVE_CONTEXT_FIELD_ACCESSOR(index, type, name)     \\\n  Handle\u003ctype\u003e Isolate::name() {                             \\\n    return Handle\u003ctype\u003e(raw_native_context()-\u003ename(), this); \\\n  }                                                          \\\n  bool Isolate::is_##name(type* value) {                     \\\n    return raw_native_context()-\u003eis_##name(value);           \\\n  }\nNATIVE_CONTEXT_FIELDS(NATIVE_CONTEXT_FIELD_ACCESSOR)\n```\n`NATIVE_CONTEXT_FIELDS` is a macro in `src/contexts` and it c\n```c++\n#define NATIVE_CONTEXT_FIELDS(V)                                               \\\n...                                                                            \\\n  V(OBJECT_FUNCTION_INDEX, JSFunction, object_function)                        \\\n```\n\n```c++\n  Handle\u003ctype\u003e Isolate::object_function() {\n    return Handle\u003cJSFunction\u003e(raw_native_context()-\u003eobject_function(), this);\n  }\n\n  bool Isolate::is_object_function(JSFunction* value) {\n    return raw_native_context()-\u003eis_object_function(value);\n  }\n```\nI'm not clear on the different types of context, there is a native context, a \"normal/public\" context.\nIn `src/contexts-inl.h` we have the native_context function:\n```c++\nContext* Context::native_context() const {\n  Object* result = get(NATIVE_CONTEXT_INDEX);\n  DCHECK(IsBootstrappingOrNativeContext(this-\u003eGetIsolate(), result));\n  return reinterpret_cast\u003cContext*\u003e(result);\n}\n```\n`Context` extends `FixedArray` so the get function is the get function of FixedArray and `NATIVE_CONTEXT_INDEX` \nis the index into the array where the native context is stored.\n\nNow, lets take a closer look at `NewJSObject`. If you search for NewJSObject in `src/heap/factory.cc`:\n```c++\nHandle\u003cJSObject\u003e Factory::NewJSObject(Handle\u003cJSFunction\u003e constructor, PretenureFlag pretenure) {\n  JSFunction::EnsureHasInitialMap(constructor);\n  Handle\u003cMap\u003e map(constructor-\u003einitial_map(), isolate());\n  return NewJSObjectFromMap(map, pretenure);\n}\n```\n`NewJSObjectFromMap` \n```c++\n...\n  HeapObject* obj = AllocateRawWithAllocationSite(map, pretenure, allocation_site);\n```\nSo we have created a new map\n\n### Map\nSo an HeapObject contains a pointer to a Map, or rather has a function that \nreturns a pointer to Map. I can't see any member map in the HeapObject class.\n\nLets take a look at when a map is created.\n```console\n(lldb) br s -f map_test.cc -l 63\n```\n\n```c++\nHandle\u003cMap\u003e Factory::NewMap(InstanceType type,\n                            int instance_size,\n                            ElementsKind elements_kind,\n                            int inobject_properties) {\n  HeapObject* result = isolate()-\u003eheap()-\u003eAllocateRawWithRetryOrFail(Map::kSize, MAP_SPACE);\n  result-\u003eset_map_after_allocation(*meta_map(), SKIP_WRITE_BARRIER);\n  return handle(InitializeMap(Map::cast(result), type, instance_size,\n                              elements_kind, inobject_properties),\n                isolate());\n}\n```\nWe can see that the above is calling `AllocateRawWithRetryOrFail` on the heap \ninstance passing a size of `88` and specifying the `MAP_SPACE`:\n```c++\nHeapObject* Heap::AllocateRawWithRetryOrFail(int size, AllocationSpace space,\n                                             AllocationAlignment alignment) {\n  AllocationResult alloc;\n  HeapObject* result = AllocateRawWithLigthRetry(size, space, alignment);\n  if (result) return result;\n\n  isolate()-\u003ecounters()-\u003egc_last_resort_from_handles()-\u003eIncrement();\n  CollectAllAvailableGarbage(GarbageCollectionReason::kLastResort);\n  {\n    AlwaysAllocateScope scope(isolate());\n    alloc = AllocateRaw(size, space, alignment);\n  }\n  if (alloc.To(\u0026result)) {\n    DCHECK(result != exception());\n    return result;\n  }\n  // TODO(1181417): Fix this.\n  FatalProcessOutOfMemory(\"CALL_AND_RETRY_LAST\");\n  return nullptr;\n}\n```\nThe default value for `alignment` is `kWordAligned`. Reading the docs in the header it says that this function\nwill try to perform an allocation of size `88` in the `MAP_SPACE` and if it fails a full GC will be performed\nand the allocation retried.\nLets take a look at `AllocateRawWithLigthRetry`:\n```c++\n  AllocationResult alloc = AllocateRaw(size, space, alignment);\n```\n`AllocateRaw` can be found in `src/heap/heap-inl.h`. There are different paths that will be taken depending on the\n`space` parameteter. Since it is `MAP_SPACE` in our case we will focus on that path:\n```c++\nAllocationResult Heap::AllocateRaw(int size_in_bytes, AllocationSpace space, AllocationAlignment alignment) {\n  ...\n  HeapObject* object = nullptr;\n  AllocationResult allocation;\n  if (OLD_SPACE == space) {\n  ...\n  } else if (MAP_SPACE == space) {\n    allocation = map_space_-\u003eAllocateRawUnaligned(size_in_bytes);\n  }\n  ...\n}\n```\n`map_space_` is a private member of Heap (src/heap/heap.h):\n```c++\nMapSpace* map_space_;\n```\n`AllocateRawUnaligned` can be found in `src/heap/spaces-inl.h`:\n```c++\nAllocationResult PagedSpace::AllocateRawUnaligned( int size_in_bytes, UpdateSkipList update_skip_list) {\n  if (!EnsureLinearAllocationArea(size_in_bytes)) {\n    return AllocationResult::Retry(identity());\n  }\n\n  HeapObject* object = AllocateLinearly(size_in_bytes);\n  MSAN_ALLOCATED_UNINITIALIZED_MEMORY(object-\u003eaddress(), size_in_bytes);\n  return object;\n}\n```\nThe default value for `update_skip_list` is `UPDATE_SKIP_LIST`.\nSo lets take a look at `AllocateLinearly`:\n```c++\nHeapObject* PagedSpace::AllocateLinearly(int size_in_bytes) {\n  Address current_top = allocation_info_.top();\n  Address new_top = current_top + size_in_bytes;\n  allocation_info_.set_top(new_top);\n  return HeapObject::FromAddress(current_top);\n}\n```\nRecall that `size_in_bytes` in our case is `88`.\n```console\n(lldb) expr current_top\n(v8::internal::Address) $5 = 24847457492680\n(lldb) expr new_top\n(v8::internal::Address) $6 = 24847457492768\n(lldb) expr new_top - current_top\n(unsigned long) $7 = 88\n```\nNotice that first the top is set to the new_top and then the current_top is returned and that will be a pointer\nto the start of the object in memory (which in this case is of v8::internal::Map which is also of type HeapObject).\nI've been wondering why Map (and other HeapObject) don't have any member fields and only/mostly \ngetters/setters for the various fields that make up an object. Well the answer is that pointers to instances of\nfor example Map point to the first memory location of the instance. And the getters/setter functions use indexed\nto read/write to memory locations. The indexes are mostly in the form of enum fields that define the memory layout\nof the type.\n\nNext, in `AllocateRawUnaligned` we have the `MSAN_ALLOCATED_UNINITIALIZED_MEMORY` macro:\n```c++\n  MSAN_ALLOCATED_UNINITIALIZED_MEMORY(object-\u003eaddress(), size_in_bytes);\n```\n`MSAN_ALLOCATED_UNINITIALIZED_MEMORY` can be found in `src/msan.h` and `ms` stands for `Memory Sanitizer` and \nwould only be used if `V8_US_MEMORY_SANITIZER` is defined.\nThe returned `object` will be used to construct an `AllocationResult` when returned.\nBack in `AllocateRaw` we have:\n```c++\nif (allocation.To(\u0026object)) {\n    ...\n    OnAllocationEvent(object, size_in_bytes);\n  }\n\n  return allocation;\n```\nThis will return us in `AllocateRawWithLightRetry`:\n```c++\nAllocationResult alloc = AllocateRaw(size, space, alignment);\nif (alloc.To(\u0026result)) {\n  DCHECK(result != exception());\n  return result;\n}\n```\nThis will return us back in `AllocateRawWithRetryOrFail`:\n```c++\n  HeapObject* result = AllocateRawWithLigthRetry(size, space, alignment);\n  if (result) return result;\n```\nAnd that return will return to `NewMap` in `src/heap/factory.cc`:\n```c++\n  result-\u003eset_map_after_allocation(*meta_map(), SKIP_WRITE_BARRIER);\n  return handle(InitializeMap(Map::cast(result), type, instance_size,\n                              elements_kind, inobject_properties),\n                isolate());\n```\n`InitializeMap`:\n```c++\n  map-\u003eset_instance_type(type);\n  map-\u003eset_prototype(*null_value(), SKIP_WRITE_BARRIER);\n  map-\u003eset_constructor_or_backpointer(*null_value(), SKIP_WRITE_BARRIER);\n  map-\u003eset_instance_size(instance_size);\n  if (map-\u003eIsJSObjectMap()) {\n    DCHECK(!isolate()-\u003eheap()-\u003eInReadOnlySpace(map));\n    map-\u003eSetInObjectPropertiesStartInWords(instance_size / kPointerSize - inobject_properties);\n    DCHECK_EQ(map-\u003eGetInObjectProperties(), inobject_properties);\n    map-\u003eset_prototype_validity_cell(*invalid_prototype_validity_cell());\n  } else {\n    DCHECK_EQ(inobject_properties, 0);\n    map-\u003eset_inobject_properties_start_or_constructor_function_index(0);\n    map-\u003eset_prototype_validity_cell(Smi::FromInt(Map::kPrototypeChainValid));\n  }\n  map-\u003eset_dependent_code(DependentCode::cast(*empty_fixed_array()), SKIP_WRITE_BARRIER);\n  map-\u003eset_weak_cell_cache(Smi::kZero);\n  map-\u003eset_raw_transitions(MaybeObject::FromSmi(Smi::kZero));\n  map-\u003eSetInObjectUnusedPropertyFields(inobject_properties);\n  map-\u003eset_instance_descriptors(*empty_descriptor_array());\n\n  map-\u003eset_visitor_id(Map::GetVisitorId(map));\n  map-\u003eset_bit_field(0);\n  map-\u003eset_bit_field2(Map::IsExtensibleBit::kMask);\n  int bit_field3 = Map::EnumLengthBits::encode(kInvalidEnumCacheSentinel) |\n                   Map::OwnsDescriptorsBit::encode(true) |\n                   Map::ConstructionCounterBits::encode(Map::kNoSlackTracking);\n  map-\u003eset_bit_field3(bit_field3);\n  map-\u003eset_elements_kind(elements_kind); //HOLEY_ELEMENTS\n  map-\u003eset_new_target_is_base(true);\n  isolate()-\u003ecounters()-\u003emaps_created()-\u003eIncrement();\n  if (FLAG_trace_maps) LOG(isolate(), MapCreate(map));\n  return map;\n```\n\nCreating a new map ([map_test.cc](./test/map_test.cc):\n```c++\n  i::Handle\u003ci::Map\u003e map = i::Map::Create(asInternal(isolate_), 10);\n  std::cout \u003c\u003c map-\u003einstance_type() \u003c\u003c '\\n';\n```\n`Map::Create` can be found in objects.cc:\n```c++\nHandle\u003cMap\u003e Map::Create(Isolate* isolate, int inobject_properties) {\n  Handle\u003cMap\u003e copy = Copy(handle(isolate-\u003eobject_function()-\u003einitial_map()), \"MapCreate\");\n```\nSo, the first thing that will happen is `isolate-\u003eobject_function()` will be called. This is function\nthat is generated by the preprocessor.\n\n```c++\n// from src/context.h\n#define NATIVE_CONTEXT_FIELDS(V)                                               \\\n  ...                                                                          \\\n  V(OBJECT_FUNCTION_INDEX, JSFunction, object_function)                        \\\n\n// from src/isolate.h\n#define NATIVE_CONTEXT_FIELD_ACCESSOR(index, type, name)     \\\n  Handle\u003ctype\u003e Isolate::name() {                             \\\n    return Handle\u003ctype\u003e(raw_native_context()-\u003ename(), this); \\\n  }                                                          \\\n  bool Isolate::is_##name(type* value) {                     \\\n    return raw_native_context()-\u003eis_##name(value);           \\\n  }\nNATIVE_CONTEXT_FIELDS(NATIVE_CONTEXT_FIELD_ACCESSOR)\n```\n`object_function()` will become:\n```c++\n  Handle\u003cJSFunction\u003e Isolate::object_function() {\n    return Handle\u003cJSFunction\u003e(raw_native_context()-\u003eobject_function(), this);\n  }\n```\n\nLets look closer at `JSFunction::initial_map()` in in object-inl.h:\n```c++\nMap* JSFunction::initial_map() {\n  return Map::cast(prototype_or_initial_map());\n}\n```\n\n`prototype_or_initial_map` is generated by a macro:\n```c++\nACCESSORS_CHECKED(JSFunction, prototype_or_initial_map, Object,\n                  kPrototypeOrInitialMapOffset, map()-\u003ehas_prototype_slot())\n```\n`ACCESSORS_CHECKED` can be found in `src/objects/object-macros.h`:\n```c++\n#define ACCESSORS_CHECKED(holder, name, type, offset, condition) \\\n  ACCESSORS_CHECKED2(holder, name, type, offset, condition, condition)\n\n#define ACCESSORS_CHECKED2(holder, name, type, offset, get_condition, \\\n                           set_condition)                             \\\n  type* holder::name() const {                                        \\\n    type* value = type::cast(READ_FIELD(this, offset));               \\\n    DCHECK(get_condition);                                            \\\n    return value;                                                     \\\n  }                                                                   \\\n  void holder::set_##name(type* value, WriteBarrierMode mode) {       \\\n    DCHECK(set_condition);                                            \\\n    WRITE_FIELD(this, offset, value);                                 \\\n    CONDITIONAL_WRITE_BARRIER(GetHeap(), this, offset, value, mode);  \\\n  }\n\n#define FIELD_ADDR(p, offset) \\\n  (reinterpret_cast\u003cAddress\u003e(p) + offset - kHeapObjectTag)\n\n#define READ_FIELD(p, offset) \\\n  (*reinterpret_cast\u003cObject* const*\u003e(FIELD_ADDR(p, offset)))\n```\nThe preprocessor will expand `prototype_or_initial_map` to:\n```c++\n  JSFunction* JSFunction::prototype_or_initial_map() const {\n    JSFunction* value = JSFunction::cast(\n        (*reinterpret_cast\u003cObject* const*\u003e(\n            (reinterpret_cast\u003cAddress\u003e(this) + kPrototypeOrInitialMapOffset - kHeapObjectTag))))\n    DCHECK(map()-\u003ehas_prototype_slot());\n    return value;\n  }\n```\nNotice that `map()-\u003ehas_prototype_slot())` will be called first which looks like this:\n```c++\nMap* HeapObject::map() const {\n  return map_word().ToMap();\n}\n```\n__TODO: Add notes about MapWord__  \n```c++\nMapWord HeapObject::map_word() const {\n  return MapWord(\n      reinterpret_cast\u003cuintptr_t\u003e(RELAXED_READ_FIELD(this, kMapOffset)));\n}\n```\nFirst thing that will happen is `RELAXED_READ_FIELD(this, kMapOffset)`\n```c++\n#define RELAXED_READ_FIELD(p, offset)           \\\n  reinterpret_cast\u003cObject*\u003e(base::Relaxed_Load( \\\n      reinterpret_cast\u003cconst base::AtomicWord*\u003e(FIELD_ADDR(p, offset))))\n\n#define FIELD_ADDR(p, offset) \\\n  (reinterpret_cast\u003cAddress\u003e(p) + offset - kHeapObjectTag)\n```\nThis will get expanded by the preprocessor to:\n```c++\n  reinterpret_cast\u003cObject*\u003e(base::Relaxed_Load(\n      reinterpret_cast\u003cconst base::AtomicWord*\u003e(\n          (reinterpret_cast\u003cAddress\u003e(this) + kMapOffset - kHeapObjectTag)))\n```\n`src/base/atomicops_internals_portable.h`:\n```c++\ninline Atomic8 Relaxed_Load(volatile const Atomic8* ptr) {\n  return __atomic_load_n(ptr, __ATOMIC_RELAXED);\n}\n```\nSo this will do an atomoic load of the ptr with the memory order of __ATOMIC_RELELAXED.\n\n\n`ACCESSORS_CHECKED` also generates a `set_prototyp_or_initial_map`:\n```c++\n  void JSFunction::set_prototype_or_initial_map(JSFunction* value, WriteBarrierMode mode) {\n    DCHECK(map()-\u003ehas_prototype_slot());\n    WRITE_FIELD(this, kPrototypeOrInitialMapOffset, value);\n    CONDITIONAL_WRITE_BARRIER(GetHeap(), this, kPrototypeOrInitialMapOffset, value, mode);\n  }\n```\nWhat does `WRITE_FIELD` do?  \n```c++\n#define WRITE_FIELD(p, offset, value)                             \\\n  base::Relaxed_Store(                                            \\\n      reinterpret_cast\u003cbase::AtomicWord*\u003e(FIELD_ADDR(p, offset)), \\\n      reinterpret_cast\u003cbase::AtomicWord\u003e(value));\n```\nWhich would expand into:\n```c++\n  base::Relaxed_Store(                                            \\\n      reinterpret_cast\u003cbase::AtomicWord*\u003e(\n          (reinterpret_cast\u003cAddress\u003e(this) + kPrototypeOrInitialMapOffset - kHeapObjectTag)\n      reinterpret_cast\u003cbase::AtomicWord\u003e(value));\n```\n\nLets take a look at what `instance_type` does:\n```c++\nInstanceType Map::instance_type() const {\n  return static_cast\u003cInstanceType\u003e(READ_UINT16_FIELD(this, kInstanceTypeOffset));\n}\n```\n\nTo see what the above is doing we can do the same thing in the debugger:\nNote that I got `11` below from `map-\u003ekInstanceTypeOffset - i::kHeapObjectTag`\n```console\n(lldb) memory read -f u -c 1 -s 8 `*map + 11`\n0x6d4e6609ed4: 585472345729139745\n(lldb) expr static_cast\u003cInstanceType\u003e(585472345729139745)\n(v8::internal::InstanceType) $34 = JS_OBJECT_TYPE\n```\n\nTake `map-\u003ehas_non_instance_prototype()`:\n```console\n(lldb) br s -n has_non_instance_prototype\n(lldb) expr -i 0 -- map-\u003ehas_non_instance_prototype()\n```\nThe above command will break in `src/objects/map-inl.h`:\n```c++\nBIT_FIELD_ACCESSORS(Map, bit_field, has_non_instance_prototype, Map::HasNonInstancePrototypeBit)\n\n// src/objects/object-macros.h\n#define BIT_FIELD_ACCESSORS(holder, field, name, BitField)      \\\n  typename BitField::FieldType holder::name() const {           \\\n    return BitField::decode(field());                           \\\n  }                                                             \\\n  void holder::set_##name(typename BitField::FieldType value) { \\\n    set_##field(BitField::update(field(), value));              \\\n  }\n```\nThe preprocessor will expand that to:\n```c++\n  typename Map::HasNonInstancePrototypeBit::FieldType Map::has_non_instance_prototype() const {\n    return Map::HasNonInstancePrototypeBit::decode(bit_field());\n  }                                                             \\\n  void holder::set_has_non_instance_prototype(typename BitField::FieldType value) { \\\n    set_bit_field(Map::HasNonInstancePrototypeBit::update(bit_field(), value));              \\\n  }\n```\nSo where can we find `Map::HasNonInstancePrototypeBit`?  \nIt is generated by a macro in `src/objects/map.h`:\n```c++\n// Bit positions for |bit_field|.\n#define MAP_BIT_FIELD_FIELDS(V, _)          \\\n  V(HasNonInstancePrototypeBit, bool, 1, _) \\\n  ...\n  DEFINE_BIT_FIELDS(MAP_BIT_FIELD_FIELDS)\n#undef MAP_BIT_FIELD_FIELDS\n\n#define DEFINE_BIT_FIELDS(LIST_MACRO) \\\n  DEFINE_BIT_RANGES(LIST_MACRO)       \\\n  LIST_MACRO(DEFINE_BIT_FIELD_TYPE, LIST_MACRO##_Ranges)\n\n#define DEFINE_BIT_RANGES(LIST_MACRO)                               \\\n  struct LIST_MACRO##_Ranges {                                      \\\n    enum { LIST_MACRO(DEFINE_BIT_FIELD_RANGE_TYPE, _) kBitsCount }; \\\n  };\n\n#define DEFINE_BIT_FIELD_RANGE_TYPE(Name, Type, Size, _) \\\n  k##Name##Start, k##Name##End = k##Name##Start + Size - 1,\n```\nAlright, lets see what preprocessor expands that to:\n```c++\n  struct MAP_BIT_FIELD_FIELDS_Ranges {\n    enum { \n      kHasNonInstancePrototypeBitStart, \n      kHasNonInstancePrototypeBitEnd = kHasNonInstancePrototypeBitStart + 1 - 1,\n      ... // not showing the rest of the entries.\n      kBitsCount \n    };\n  };\n\n```\nSo this would create a struct with an enum and it could be accessed using:\n`i::Map::MAP_BIT_FIELD_FIELDS_Ranges::kHasNonInstancePrototypeBitStart`\nThe next part of the macro is\n```c++\n  LIST_MACRO(DEFINE_BIT_FIELD_TYPE, LIST_MACRO##_Ranges)\n\n#define DEFINE_BIT_FIELD_TYPE(Name, Type, Size, RangesName) \\\n  typedef BitField\u003cType, RangesName::k##Name##Start, Size\u003e Name;\n```\nWhich will get expanded to:\n```c++\n  typedef BitField\u003cHasNonInstancePrototypeBit, MAP_BIT_FIELD_FIELDS_Ranges::kHasNonInstancePrototypeBitStart, 1\u003e HasNonInstancePrototypeBit;\n```\nSo this is how `HasNonInstancePrototypeBit` is declared and notice that it is of type `BitField` which can be\nfound in `src/utils.h`:\n```c++\ntemplate\u003cclass T, int shift, int size\u003e\nclass BitField : public BitFieldBase\u003cT, shift, size, uint32_t\u003e { };\n\ntemplate\u003cclass T, int shift, int size, class U\u003e\nclass BitFieldBase {\n public:\n  typedef T FieldType;\n```\n\nMap::HasNonInstancePrototypeBit::decode(bit_field());\nfirst bit_field is called:\n```c++\nbyte Map::bit_field() const { return READ_BYTE_FIELD(this, kBitFieldOffset); }\n\n```\nAnd the result of that is passed to `Map::HasNonInstancePrototypeBit::decode`:\n\n```console\n(lldb) br s -n bit_field\n(lldb) expr -i 0 --  map-\u003ebit_field()\n```\n\n```c++\nbyte Map::bit_field() const { return READ_BYTE_FIELD(this, kBitFieldOffset); }\n```\nSo, `this` is the current Map instance, and we are going to read from.\n```c++\n#define READ_BYTE_FIELD(p, offset) \\\n  (*reinterpret_cast\u003cconst byte*\u003e(FIELD_ADDR(p, offset)))\n\n#define FIELD_ADDR(p, offset) \\\n  (reinterpret_cast\u003cAddress\u003e(p) + offset - kHeapObjectTag)\n```\nWhich will get expanded to:\n```c++\nbyte Map::bit_field() const { \n  return *reinterpret_cast\u003cconst byte*\u003e(\n      reinterpret_cast\u003cAddress\u003e(this) + kBitFieldOffset - kHeapObjectTag)\n}\n```\n\nThe instance_size is the instance_size_in_words \u003c\u003c kPointerSizeLog2 (3 on my machine):\n```console\n(lldb) memory read -f x -s 1 -c 1 *map+8\n0x24d1cd509ed1: 0x03\n(lldb) expr 0x03 \u003c\u003c 3\n(int) $2 = 24\n(lldb) expr map-\u003einstance_size()\n(int) $3 = 24\n```\n`i::HeapObject::kHeaderSize` is 8 on my system  which is used in the `DEFINE_FIELD_OFFSET_CONSTANTS:\n```c++\n#define MAP_FIELDS(V)\nV(kInstanceSizeInWordsOffset, kUInt8Size)\nV(kInObjectPropertiesStartOrConstructorFunctionIndexOffset, kUInt8Size)\n...\nDEFINE_FIELD_OFFSET_CONSTANTS(HeapObject::kHeaderSize, MAP_FIELDS)\n```\n\nSo we can use this information to read the `inobject_properties_start_or_constructor_function_index` directly from memory using:\n```console\n(lldb) expr map-\u003einobject_properties_start_or_constructor_function_index()\n(lldb) memory read -f x -s 1 -c 1 map+9\nerror: invalid start address expression.\nerror: address expression \"map+9\" evaluation failed\n(lldb) memory read -f x -s 1 -c 1 *map+9\n0x17b027209ed2: 0x03\n```\n\nInspect the visitor_id (which is the last of the first byte):\n```console\nlldb) memory read -f x -s 1 -c 1 *map+10\n0x17b027209ed3: 0x15\n(lldb) expr (int) 0x15\n(int) $8 = 21\n(lldb) expr map-\u003evisitor_id()\n(v8::internal::VisitorId) $11 = kVisitJSObjectFast\n(lldb) expr (int) $11\n(int) $12 = 21\n```\n\nInspect the instance_type (which is part of the second byte):\n```console\n(lldb) expr map-\u003einstance_type()\n(v8::internal::InstanceType) $41 = JS_OBJECT_TYPE\n(lldb) expr v8::internal::InstanceType::JS_OBJECT_TYPE\n(uint16_t) $35 = 1057\n(lldb) memory read -f x -s 2 -c 1 *map+11\n0x17b027209ed4: 0x0421\n(lldb) expr (int)0x0421\n(int) $40 = 1057\n```\nNotice that `instance_type` is a short so that will take up 2 bytes\n```console\n(lldb) expr map-\u003ehas_non_instance_prototype()\n(bool) $60 = false\n(lldb) expr map-\u003eis_callable()\n(bool) $46 = false\n(lldb) expr map-\u003ehas_named_interceptor()\n(bool) $51 = false\n(lldb) expr map-\u003ehas_indexed_interceptor()\n(bool) $55 = false\n(lldb) expr map-\u003eis_undetectable()\n(bool) $56 = false\n(lldb) expr map-\u003eis_access_check_needed()\n(bool) $57 = false\n(lldb) expr map-\u003eis_constructor()\n(bool) $58 = false\n(lldb) expr map-\u003ehas_prototype_slot()\n(bool) $59 = false\n```\n\nVerify that the above is correct:\n```console\n(lldb) expr map-\u003ehas_non_instance_prototype()\n(bool) $44 = false\n(lldb) memory read -f x -s 1 -c 1 *map+13\n0x17b027209ed6: 0x00\n\n(lldb) expr map-\u003eset_has_non_instance_prototype(true)\n(lldb) memory read -f x -s 1 -c 1 *map+13\n0x17b027209ed6: 0x01\n\n(lldb) expr map-\u003eset_has_prototype_slot(true)\n(lldb) memory read -f x -s 1 -c 1 *map+13\n0x17b027209ed6: 0x81\n```\n\n\nInspect second int field (bit_field2):\n```console\n(lldb) memory read -f x -s 1 -c 1 *map+14\n0x17b027209ed7: 0x19\n(lldb) expr map-\u003eis_extensible()\n(bool) $78 = true\n(lldb) expr -- 0x19 \u0026 (1 \u003c\u003c 0)\n(bool) $90 = 1\n\n(lldb) expr map-\u003eis_prototype_map()\n(bool) $79 = false\n\n(lldb) expr map-\u003eis_in_retained_map_list()\n(bool) $80 = false\n\n(lldb) expr map-\u003eelements_kind()\n(v8::internal::ElementsKind) $81 = HOLEY_ELEMENTS\n(lldb) expr v8::internal::ElementsKind::HOLEY_ELEMENTS\n(int) $133 = 3\n(lldb) expr  0x19 \u003e\u003e 3\n(int) $134 = 3\n```\n\nInspect third int field (bit_field3):\n```console\n(lldb) memory read -f b -s 4 -c 1 *map+15\n0x17b027209ed8: 0b00001000001000000000001111111111\n(lldb) memory read -f x -s 4 -c 1 *map+15\n0x17b027209ed8: 0x082003ff\n```\n\nSo we know that a Map instance is a pointer allocated by the Heap and with a specific \nsize. Fields are accessed using indexes (remember there are no member fields in the Map class).\nWe also know that all HeapObject have a Map. The Map is sometimes referred to as the HiddenClass\nand sometimes the shape of an object. If two objects have the same properties they would share \nthe same Map. This makes sense and I've see blog post that show this but I'd like to verify\nthis to fully understand it.\nI'm going to try to match https://v8project.blogspot.com/2017/08/fast-properties.html with \nthe code.\n\nSo, lets take a look at adding a property to a JSObject. We start by creating a new Map and then \nuse it to create a new JSObject:\n```c++\n  i::Handle\u003ci::Map\u003e map = factory-\u003eNewMap(i::JS_OBJECT_TYPE, 32);\n  i::Handle\u003ci::JSObject\u003e js_object = factory-\u003eNewJSObjectFromMap(map);\n\n  i::Handle\u003ci::String\u003e prop_name = factory-\u003eInternalizeUtf8String(\"prop_name\");\n  i::Handle\u003ci::String\u003e prop_value = factory-\u003eInternalizeUtf8String(\"prop_value\");\n  i::JSObject::AddProperty(js_object, prop_name, prop_value, i::NONE);  \n```\nLets take a closer look at `AddProperty` and how it interacts with the Map. This function can be\nfound in `src/objects.cc`:\n```c++\nvoid JSObject::AddProperty(Handle\u003cJSObject\u003e object, Handle\u003cName\u003e name,\n                           Handle\u003cObject\u003e value,\n                           PropertyAttributes attributes) {\n  LookupIterator it(object, name, object, LookupIterator::OWN_SKIP_INTERCEPTOR);\n  CHECK_NE(LookupIterator::ACCESS_CHECK, it.state());\n```\nFirst we have the LookupIterator constructor (`src/lookup.h`) but since this is a new property which \nwe know does not exist it will not find any property.\n```c++\nCHECK(AddDataProperty(\u0026it, value, attributes, kThrowOnError,\n                        CERTAINLY_NOT_STORE_FROM_KEYED)\n            .IsJust());\n```\n```c++\n  Handle\u003cJSReceiver\u003e receiver = it-\u003eGetStoreTarget\u003cJSReceiver\u003e();\n  ...\n  it-\u003eUpdateProtector();\n  // Migrate to the most up-to-date map that will be able to store |value|\n  // under it-\u003ename() with |attributes|.\n  it-\u003ePrepareTransitionToDataProperty(receiver, value, attributes, store_mode);\n  DCHECK_EQ(LookupIterator::TRANSITION, it-\u003estate());\n  it-\u003eApplyTransitionToDataProperty(receiver);\n\n  // Write the property value.\n  it-\u003eWriteDataValue(value, true);\n```\n`PrepareTransitionToDataProperty`:\n```c++\n  Representation representation = value-\u003eOptimalRepresentation();\n  Handle\u003cFieldType\u003e type = value-\u003eOptimalType(isolate, representation);\n  maybe_map = Map::CopyWithField(map, name, type, attributes, constness,\n  representation, flag);\n```\n`Map::CopyWithField`:\n```c++\n  Descriptor d = Descriptor::DataField(name, index, attributes, constness, representation, wrapped_type);\n```\nLets take a closer look the Decriptor which can be found in `src/property.cc`:\n```c++\nDescriptor Descriptor::DataField(Handle\u003cName\u003e key, int field_index,\n                                 PropertyAttributes attributes,\n                                 PropertyConstness constness,\n                                 Representation representation,\n                                 MaybeObjectHandle wrapped_field_type) {\n  DCHECK(wrapped_field_type-\u003eIsSmi() || wrapped_field_type-\u003eIsWeakHeapObject());\n  PropertyDetails details(kData, attributes, kField, constness, representation,\n                          field_index);\n  return Descriptor(key, wrapped_field_type, details);\n}\n```\n`Descriptor` is declared in `src/property.h` and describes the elements in a instance-descriptor array. These\nare returned when calling `map-\u003einstance_descriptors()`. Let check some of the arguments:\n```console\n(lldb) job *key\n#prop_name\n(lldb) expr attributes\n(v8::internal::PropertyAttributes) $27 = NONE\n(lldb) expr constness\n(v8::internal::PropertyConstness) $28 = kMutable\n(lldb) expr representation\n(v8::internal::Representation) $29 = (kind_ = '\\b')\n```\nThe Descriptor class contains three members:\n```c++\n private:\n  Handle\u003cName\u003e key_;\n  MaybeObjectHandle value_;\n  PropertyDetails details_;\n```\nLets take a closer look `PropertyDetails` which only has a single member named `value_`\n```c++\n  uint32_t value_;\n```\nIt also declares a number of classes the extend BitField, for example:\n```c++\nclass KindField : public BitField\u003cPropertyKind, 0, 1\u003e {};\nclass LocationField : public BitField\u003cPropertyLocation, KindField::kNext, 1\u003e {};\nclass ConstnessField : public BitField\u003cPropertyConstness, LocationField::kNext, 1\u003e {};\nclass AttributesField : public BitField\u003cPropertyAttributes, ConstnessField::kNext, 3\u003e {};\nclass PropertyCellTypeField : public BitField\u003cPropertyCellType, AttributesField::kNext, 2\u003e {};\nclass DictionaryStorageField : public BitField\u003cuint32_t, PropertyCellTypeField::kNext, 23\u003e {};\n\n// Bit fields for fast objects.\nclass RepresentationField : public BitField\u003cuint32_t, AttributesField::kNext, 4\u003e {};\nclass DescriptorPointer : public BitField\u003cuint32_t, RepresentationField::kNext, kDescriptorIndexBitCount\u003e {};\nclass FieldIndexField : public BitField\u003cuint32_t, DescriptorPointer::kNext, kDescriptorIndexBitCount\u003e {\n\nenum PropertyKind { kData = 0, kAccessor = 1 };\nenum PropertyLocation { kField = 0, kDescriptor = 1 };\nenum class PropertyConstness { kMutable = 0, kConst = 1 };\nenum PropertyAttributes {\n  NONE = ::v8::None,\n  READ_ONLY = ::v8::ReadOnly,\n  DONT_ENUM = ::v8::DontEnum,\n  DONT_DELETE = ::v8::DontDelete,\n  ALL_ATTRIBUTES_MASK = READ_ONLY | DONT_ENUM | DONT_DELETE,\n  SEALED = DONT_DELETE,\n  FROZEN = SEALED | READ_ONLY,\n  ABSENT = 64,  // Used in runtime to indicate a property is absent.\n  // ABSENT can never be stored in or returned from a descriptor's attributes\n  // bitfield.  It is only used as a return value meaning the attributes of\n  // a non-existent property.\n};\nenum class PropertyCellType {\n  // Meaningful when a property cell does not contain the hole.\n  kUndefined,     // The PREMONOMORPHIC of property cells.\n  kConstant,      // Cell has been assigned only once.\n  kConstantType,  // Cell has been assigned only one type.\n  kMutable,       // Cell will no longer be tracked as constant.\n  // Meaningful when a property cell contains the hole.\n  kUninitialized = kUndefined,  // Cell has never been initialized.\n  kInvalidated = kConstant,     // Cell has been deleted, invalidated or never\n                                // existed.\n  // For dictionaries not holding cells.\n  kNoCell = kMutable,\n};\n\n\ntemplate\u003cclass T, int shift, int size\u003e\nclass BitField : public BitFieldBase\u003cT, shift, size, uint32_t\u003e { };\n```\nThe Type T of KindField will be `PropertyKind`, the `shift` will be 0 , and the `size` 1.\nNotice that `LocationField` is using `KindField::kNext` as its shift. This is a static class constant\nof type `uint32_t` and is defined as:\n```c++\nstatic const U kNext = kShift + kSize;\n```\nSo `LocationField` would get the value from KindField which should be:\n```c++\nclass LocationField : public BitField\u003cPropertyLocation, 1, 1\u003e {};\n```\n\nThe constructor for PropertyDetails looks like this:\n```c++\nPropertyDetails(PropertyKind kind, PropertyAttributes attributes, PropertyCellType cell_type, int dictionary_index = 0) {\n    value_ = KindField::encode(kind) | LocationField::encode(kField) |\n             AttributesField::encode(attributes) |\n             DictionaryStorageField::encode(dictionary_index) |\n             PropertyCellTypeField::encode(cell_type);\n  }\n```\nSo what does KindField::encode(kind) actualy do then?\n```console\n(lldb) expr static_cast\u003cuint32_t\u003e(kind())\n(uint32_t) $36 = 0\n(lldb) expr static_cast\u003cuint32_t\u003e(kind()) \u003c\u003c 0\n(uint32_t) $37 = 0\n```\nThis value is later returned by calling `kind()`:\n```c++\nPropertyKind kind() const { return KindField::decode(value_); }\n```\nSo we have all this information about this property, its type (Representation), constness, if it is \nread-only, enumerable, deletable, sealed, frozen. After that little detour we are back in `Descriptor::DataField`:\n```c++\n  return Descriptor(key, wrapped_field_type, details);\n```\nHere we are using the key (name of the property), the wrapped_field_type, and PropertyDetails we created.\nWhat is `wrapped_field_type` again?  \nIf we back up a few frames back into `Map::TransitionToDataProperty` we can see that the type passed in\nis taken from the following code:\n```c++\n  Representation representation = value-\u003eOptimalRepresentation();\n  Handle\u003cFieldType\u003e type = value-\u003eOptimalType(isolate, representation);\n```\nSo this is only taking the type of the field:\n```console\n(lldb) expr representation.kind()\n(v8::internal::Representation::Kind) $51 = kHeapObject\n```\nThis makes sense as the map only deals with the shape of the propery and not the value.\nNext in `Map::CopyWithField` we have:\n```c++\n  Handle\u003cMap\u003e new_map = Map::CopyAddDescriptor(map, \u0026d, flag);\n```\n`CopyAddDescriptor` does:\n```c++\n  Handle\u003cDescriptorArray\u003e descriptors(map-\u003einstance_descriptors());\n \n  int nof = map-\u003eNumberOfOwnDescriptors();\n  Handle\u003cDescriptorArray\u003e new_descriptors = DescriptorArray::CopyUpTo(descriptors, nof, 1);\n  new_descriptors-\u003eAppend(descriptor);\n  \n  Handle\u003cLayoutDescriptor\u003e new_layout_descriptor =\n      FLAG_unbox_double_fields\n          ? LayoutDescriptor::New(map, new_descriptors, nof + 1)\n          : handle(LayoutDescriptor::FastPointerLayout(), map-\u003eGetIsolate());\n\n  return CopyReplaceDescriptors(map, new_descriptors, new_layout_descriptor,\n                                flag, descriptor-\u003eGetKey(), \"CopyAddDescriptor\",\n                                SIMPLE_PROPERTY_TRANSITION);\n```\nLets take a closer look at `LayoutDescriptor`\n\n```console\n(lldb) expr new_layout_descriptor-\u003ePrint()\nLayout descriptor: \u003call tagged\u003e\n```\nTODO: Take a closer look at LayoutDescritpor\n\nLater when actually adding the value in `Object::AddDataProperty`:\n```c++\n  it-\u003eWriteDataValue(value, true);\n```\nThis call will end up in `src/lookup.cc` and in our case the path will be the following call:\n```c++\n  JSObject::cast(*holder)-\u003eWriteToField(descriptor_number(), property_details_, *value);\n```\nTODO: Take a closer look at LookupIterator.\n`WriteToField` can be found in `src/objects-inl.h`:\n```c++\n  FieldIndex index = FieldIndex::ForDescriptor(map(), descriptor);\n```\n`FieldIndex::ForDescriptor` can be found in `src/field-index-inl.h`:\n```c++\ninline FieldIndex FieldIndex::ForDescriptor(const Map* map, int descriptor_index) {\n  PropertyDetails details = map-\u003einstance_descriptors()-\u003eGetDetails(descriptor_index);\n  int field_index = details.field_index();\n  return ForPropertyIndex(map, field_index, details.representation());\n}\n```\nNotice that this is calling `instance_descriptors()` on the passed-in map. This as we recall from earlier returns\nand DescriptorArray (which is a type of WeakFixedArray). A Descriptor array \n\nOur DecsriptorArray only has one entry:\n```console\n(lldb) expr map-\u003einstance_descriptors()-\u003enumber_of_descriptors()\n(int) $6 = 1\n(lldb) expr map-\u003einstance_descriptors()-\u003eGetKey(0)-\u003ePrint()\n#prop_name\n(lldb) expr map-\u003einstance_descriptors()-\u003eGetFieldIndex(0)\n(int) $11 = 0\n```\nWe can also use `Print` on the DescriptorArray:\n```console\nlldb) expr map-\u003einstance_descriptors()-\u003ePrint()\n\n  [0]: #prop_name (data field 0:h, p: 0, attrs: [WEC]) @ Any\n```\nIn our case we are accessing the PropertyDetails and then getting the `field_index` which I think tells us\nwhere in the object the value for this property is stored.\nThe last call in `ForDescriptor` is `ForProperty:\n```c++\ninline FieldIndex FieldIndex::ForPropertyIndex(const Map* map,\n                                               int property_index,\n                                               Representation representation) {\n  int inobject_properties = map-\u003eGetInObjectProperties();\n  bool is_inobject = property_index \u003c inobject_properties;\n  int first_inobject_offset;\n  int offset;\n  if (is_inobject) {\n    first_inobject_offset = map-\u003eGetInObjectPropertyOffset(0);\n    offset = map-\u003eGetInObjectPropertyOffset(property_index);\n  } else {\n    first_inobject_offset = FixedArray::kHeaderSize;\n    property_index -= inobject_properties;\n    offset = FixedArray::kHeaderSize + property_index * kPointerSize;\n  }\n  Encoding encoding = FieldEncoding(representation);\n  return FieldIndex(is_inobject, offset, encoding, inobject_properties,\n                    first_inobject_offset);\n}\n```\nI was expecting `inobject_propertis` to be 1 here but it is 0:\n```console\n(lldb) expr inobject_properties\n(int) $14 = 0\n```\nWhy is that, what am I missing?  \nThese in-object properties are stored directly on the object instance and not do not use\nthe properties array. All get back to an example of this later to clarify this.\nTODO: Add in-object properties example.\n\nBack in `JSObject::WriteToField`:\n```c++\n  RawFastPropertyAtPut(index, value);\n```\n\n```c++\nvoid JSObject::RawFastPropertyAtPut(FieldIndex index, Object* value) {\n  if (index.is_inobject()) {\n    int offset = index.offset();\n    WRITE_FIELD(this, offset, value);\n    WRITE_BARRIER(GetHeap(), this, offset, value);\n  } else {\n    property_array()-\u003eset(index.outobject_array_index(), value);\n  }\n}\n```\nIn our case we know that the index is not inobject()\n```console\n(lldb) expr index.is_inobject()\n(bool) $18 = false\n```\nSo, `property_array()-\u003eset()` will be called.\n```console\n(lldb) expr this\n(v8::internal::JSObject *) $21 = 0x00002c31c6a88b59\n```\nJSObject inherits from JSReceiver which is where the property_array() function is declared.\n```c++\n  inline PropertyArray* property_array() const;\n```\n```console\n(lldb) expr property_array()-\u003ePrint()\n0x2c31c6a88bb1: [PropertyArray]\n - map: 0x2c31f5603e21 \u003cMap\u003e\n - length: 3\n - hash: 0\n           0: 0x2c31f56025a1 \u003cOdd Oddball: uninitialized\u003e\n         1-2: 0x2c31f56026f1 \u003cundefined\u003e\n(lldb) expr index.outobject_array_index()\n(int) $26 = 0\n(lldb) expr value-\u003ePrint()\n#prop_value\n```\nLooking at the above values printed we should see the property be written to entry 0.\n```console\n(lldb) expr property_array()-\u003eget(0)-\u003ePrint()\n#uninitialized\n// after call to set\n(lldb) expr property_array()-\u003eget(0)-\u003ePrint()\n#prop_value\n```\n\n```console\n(lldb) expr map-\u003einstance_descriptors()\n(v8::internal::DescriptorArray *) $4 = 0x000039a927082339\n```\nSo a map has an pointer array of instance of DescriptorArray\n\n```console\n(lldb) expr map-\u003eGetInObjectProperties()\n(int) $19 = 1\n```\nEach Map has int that tells us the number of properties it has. This is the number specified when creating\na new Map, for example:\n```console\ni::Handle\u003ci::Map\u003e map = i::Map::Create(asInternal(isolate_), 1);\n```\nBut at this stage we don't really have any properties. The value for a property is associated with the actual\ninstance of the Object. What the Map specifies is index of the value for a particualar property. \n\n#### Creating a Map instance\nLets take a look at when a map is created.\n```console\n(lldb) br s -f map_test.cc -l 63\n```\n\n```c++\nHandle\u003cMap\u003e Factory::NewMap(InstanceType type,\n                            int instance_size,\n                            ElementsKind elements_kind,\n                            int inobject_properties) {\n  HeapObject* result = isolate()-\u003eheap()-\u003eAllocateRawWithRetryOrFail(Map::kSize, MAP_SPACE);\n  result-\u003eset_map_after_allocation(*meta_map(), SKIP_WRITE_BARRIER);\n  return handle(InitializeMap(Map::cast(result), type, instance_size,\n                              elements_kind, inobject_properties),\n                isolate());\n}\n```\nWe can see that the above is calling `AllocateRawWithRetryOrFail` on the heap instance passing a size of `88` and\nspecifying the `MAP_SPACE`:\n```c++\nHeapObject* Heap::AllocateRawWithRetryOrFail(int size, AllocationSpace space,\n                                             AllocationAlignment alignment) {\n  AllocationResult alloc;\n  HeapObject* result = AllocateRawWithLigthRetry(size, space, alignment);\n  if (result) return result;\n\n  isolate()-\u003ecounters()-\u003egc_last_resort_from_handles()-\u003eIncrement();\n  CollectAllAvailableGarbage(GarbageCollectionReason::kLastResort);\n  {\n    AlwaysAllocateScope scope(isolate());\n    alloc = AllocateRaw(size, space, alignment);\n  }\n  if (alloc.To(\u0026result)) {\n    DCHECK(result != exception());\n    return result;\n  }\n  // TODO(1181417): Fix this.\n  FatalProcessOutOfMemory(\"CALL_AND_RETRY_LAST\");\n  return nullptr;\n}\n```\nThe default value for `alignment` is `kWordAligned`. Reading the docs in the header it says that this function\nwill try to perform an allocation of size `88` in the `MAP_SPACE` and if it fails a full GC will be performed\nand the allocation retried.\nLets take a look at `AllocateRawWithLigthRetry`:\n```c++\n  AllocationResult alloc = AllocateRaw(size, space, alignment);\n```\n`AllocateRaw` can be found in `src/heap/heap-inl.h`. There are different paths that will be taken depending on the\n`space` parameteter. Since it is `MAP_SPACE` in our case we will focus on that path:\n```c++\nAllocationResult Heap::AllocateRaw(int size_in_bytes, AllocationSpace space, AllocationAlignment alignment) {\n  ...\n  HeapObject* object = nullptr;\n  AllocationResult allocation;\n  if (OLD_SPACE == space) {\n  ...\n  } else if (MAP_SPACE == space) {\n    allocation = map_space_-\u003eAllocateRawUnaligned(size_in_bytes);\n  }\n  ...\n}\n```\n`map_space_` is a private member of Heap (src/heap/heap.h):\n```c++\nMapSpace* map_space_;\n```\n`AllocateRawUnaligned` can be found in `src/heap/spaces-inl.h`:\n```c++\nAllocationResult PagedSpace::AllocateRawUnaligned( int size_in_bytes, UpdateSkipList update_skip_list) {\n  if (!EnsureLinearAllocationArea(size_in_bytes)) {\n    return AllocationResult::Retry(identity());\n  }\n\n  HeapObject* object = AllocateLinearly(size_in_bytes);\n  MSAN_ALLOCATED_UNINITIALIZED_MEMORY(object-\u003eaddress(), size_in_bytes);\n  return object;\n}\n```\nThe default value for `update_skip_list` is `UPDATE_SKIP_LIST`.\nSo lets take a look at `AllocateLinearly`:\n```c++\nHeapObject* PagedSpace::AllocateLinearly(int size_in_bytes) {\n  Address current_top = allocation_info_.top();\n  Address new_top = current_top + size_in_bytes;\n  allocation_info_.set_top(new_top);\n  return HeapObject::FromAddress(current_top);\n}\n```\nRecall that `size_in_bytes` in our case is `88`.\n```console\n(lldb) expr current_top\n(v8::internal::Address) $5 = 24847457492680\n(lldb) expr new_top\n(v8::internal::Address) $6 = 24847457492768\n(lldb) expr new_top - current_top\n(unsigned long) $7 = 88\n```\nNotice that first the top is set to the new_top and then the current_top is returned and that will be a pointer\nto the start of the object in memory (which in this case is of v8::internal::Map which is also of type HeapObject).\nI've been wondering why Map (and other HeapObject) don't have any member fields and only/mostly \ngetters/setters for the various fields that make up an object. Well the answer is that pointers to instances of\nfor example Map point to the first memory location of the instance. And the getters/setter functions use indexed\nto read/write to memory locations. The indexes are mostly in the form of enum fields that define the memory layout\nof the type.\n\nNext, in `AllocateRawUnaligned` we have the `MSAN_ALLOCATED_UNINITIALIZED_MEMORY` macro:\n```c++\n  MSAN_ALLOCATED_UNINITIALIZED_MEMORY(object-\u003eaddress(), size_in_bytes);\n```\n`MSAN_ALLOCATED_UNINITIALIZED_MEMORY` can be found in `src/msan.h` and `ms` stands for `Memory Sanitizer` and \nwould only be used if `V8_US_MEMORY_SANITIZER` is defined.\nThe returned `object` will be used to construct an `AllocationResult` when returned.\nBack in `AllocateRaw` we have:\n```c++\nif (allocation.To(\u0026object)) {\n    ...\n    OnAllocationEvent(object, size_in_bytes);\n  }\n\n  return allocation;\n```\nThis will return us in `AllocateRawWithLightRetry`:\n```c++\nAllocationResult alloc = AllocateRaw(size, space, alignment);\nif (alloc.To(\u0026result)) {\n  DCHECK(result != exception());\n  return result;\n}\n```\nThis will return us back in `AllocateRawWithRetryOrFail`:\n```c++\n  HeapObject* result = AllocateRawWithLigthRetry(size, space, alignment);\n  if (result) return result;\n```\nAnd that return will return to `NewMap` in `src/heap/factory.cc`:\n```c++\n  result-\u003eset_map_after_allocation(*meta_map(), SKIP_WRITE_BARRIER);\n  return handle(InitializeMap(Map::cast(result), type, instance_size,\n                              elements_kind, inobject_properties),\n                isolate());\n```\n`InitializeMap`:\n```c++\n  map-\u003eset_instance_type(type);\n  map-\u003eset_prototype(*null_value(), SKIP_WRITE_BARRIER);\n  map-\u003eset_constructor_or_backpointer(*null_value(), SKIP_WRITE_BARRIER);\n  map-\u003eset_instance_size(instance_size);\n  if (map-\u003eIsJSObjectMap()) {\n    DCHECK(!isolate()-\u003eheap()-\u003eInReadOnlySpace(map));\n    map-\u003eSetInObjectPropertiesStartInWords(instance_size / kPointerSize - inobject_properties);\n    DCHECK_EQ(map-\u003eGetInObjectProperties(), inobject_properties);\n    map-\u003eset_prototype_validity_cell(*invalid_prototype_validity_cell());\n  } else {\n    DCHECK_EQ(inobject_properties, 0);\n    map-\u003eset_inobject_properties_start_or_constructor_function_index(0);\n    map-\u003eset_prototype_validity_cell(Smi::FromInt(Map::kPrototypeChainValid));\n  }\n  map-\u003eset_dependent_code(DependentCode::cast(*empty_fixed_array()), SKIP_WRITE_BARRIER);\n  map-\u003eset_weak_cell_cache(Smi::kZero);\n  map-\u003eset_raw_transitions(MaybeObject::FromSmi(Smi::kZero));\n  map-\u003eSetInObjectUnusedPropertyFields(inobject_properties);\n  map-\u003eset_instance_descriptors(*empty_descriptor_array());\n\n  map-\u003eset_visitor_id(Map::GetVisitorId(map));\n  map-\u003eset_bit_field(0);\n  map-\u003eset_bit_field2(Map::IsExtensibleBit::kMask);\n  int bit_field3 = Map::EnumLengthBits::encode(kInvalidEnumCacheSentinel) |\n                   Map::OwnsDescriptorsBit::encode(true) |\n                   Map::ConstructionCounterBits::encode(Map::kNoSlackTracking);\n  map-\u003eset_bit_field3(bit_field3);\n  map-\u003eset_elements_kind(elements_kind); //HOLEY_ELEMENTS\n  map-\u003eset_new_target_is_base(true);\n  isolate()-\u003ecounters()-\u003emaps_created()-\u003eIncrement();\n  if (FLAG_trace_maps) LOG(isolate(), MapCreate(map));\n  return map;\n```\n\n### Context\nContext extends `FixedArray` (`src/context.h`). So an instance of this Context is a FixedArray and we can \nuse Get(index) etc to get entries in the array.\n\n### V8_EXPORT\nThis can be found in quite a few places in v8 source code. For example:\n\n    class V8_EXPORT ArrayBuffer : public Object {\n\nWhat is this?  \nIt is a preprocessor macro which looks like this:\n\n    #if V8_HAS_ATTRIBUTE_VISIBILITY \u0026\u0026 defined(V8_SHARED)\n    # ifdef BUILDING_V8_SHARED\n    #  define V8_EXPORT __attribute__ ((visibility(\"default\")))\n    # else\n    #  define V8_EXPORT\n    # endif\n    #else\n    # define V8_EXPORT\n    #endif \n\nSo we can see that if `V8_HAS_ATTRIBUTE_VISIBILITY`, and `defined(V8_SHARED)`, and also \nif `BUILDING_V8_SHARED`, `V8_EXPORT` is set to `__attribute__ ((visibility(\"default\"))`.\nBut in all other cases `V8_EXPORT` is empty and the preprocessor does not insert \nanything (nothing will be there come compile time). \nBut what about the `__attribute__ ((visibility(\"default\"))` what is this?  \n\nIn the GNU compiler collection (GCC) environment, the term that is used for\nexporting is visibility. As it applies to functions and variables in a shared\nobject, visibility refers to the ability of other shared objects to call a\nC/C++ function. Functions with default visibility have a global scope and can\nbe called from other shared objects. Functions with hidden visibility have a\nlocal scope and cannot be called from other shared objects.\n\nVisibility can be controlled by using either compiler options or visibility attributes.\nIn your header files, wherever you want an interface or API made public outside\nthe current Dynamic Shared Object (DSO) , place\n`__attribute__ ((visibility (\"default\")))` in struct, class and function\ndeclarations you wish to make public.  With `-fvisibility=hidden`, you are\ntelling GCC that every declaration not explicitly marked with a visibility\nattribute has a hidden visibility. There is such a flag in build/common.gypi\n\n\n### ToLocalChecked()\nYou'll see a few of these calls in the hello_world example:\n```c++\n  Local\u003cString\u003e source = String::NewFromUtf8(isolate, js, NewStringType::kNormal).ToLocalChecked();\n```\n\nNewFromUtf8 actually returns a Local\u003cString\u003e wrapped in a MaybeLocal which forces a check to see if \nthe Local\u003c\u003e is empty before using it. \nNewStringType is an enum which can be kNormalString (k for constant) or kInternalized.\n\nThe following is after running the preprocessor (clang -E src/api.cc):\n\n    # 5961 \"src/api.cc\"\n    Local\u003cString\u003e String::NewFromUtf8(Isolate* isolate,\n                                  const char* data,\n                                  NewStringType type,\n                                  int length) {\n      MaybeLocal\u003cString\u003e result; \n      if (length == 0) { \n        result = String::Empty(isolate); \n      } else if (length \u003e i::String::kMaxLength) { \n        result = MaybeLocal\u003cString\u003e(); \n      } else { \n        i::Isolate* i_isolate = reinterpret_cast\u003cinternal::Isolate*\u003e(isolate); \n        i::VMState\u003cv8::OTHER\u003e __state__((i_isolate)); \n        i::RuntimeCallTimerScope _runtime_timer( i_isolate, \u0026i::RuntimeCallStats::API_String_NewFromUtf8); \n        LOG(i_isolate, ApiEntryCall(\"v8::\" \"String\" \"::\" \"NewFromUtf8\")); \n        if (length \u003c 0) length = StringLength(data); \n        i::Handle\u003ci::String\u003e handle_result = NewString(i_isolate-\u003efactory(), static_cast\u003cv8::NewStringType\u003e(type), i::Vector\u003cconst char\u003e(data, length)) .ToHandleChecked(); \n        result = Utils::ToLocal(handle_result); \n     };\n     return result.FromMaybe(Local\u003cString\u003e());;\n    }\n\nI was wondering where the Utils::ToLocal was defined but could not find it until I found:\n\n    MAKE_TO_LOCAL(ToLocal, String, String)\n\n    #define MAKE_TO_LOCAL(Name, From, To)                                       \\\n    Local\u003cv8::To\u003e Utils::Name(v8::internal::Handle\u003cv8::internal::From\u003e obj) {   \\\n      return Convert\u003cv8::internal::From, v8::To\u003e(obj);                          \\\n    }\n\nThe above can be found in `src/api.h`. The same goes for `Local\u003cObject\u003e,\nLocal\u003cString\u003e` etc.\n\n\n### Small Integers\nReading through v8.h I came accross `// Tag information for Smi`\nSmi stands for small integers.\n\nA pointer is really just a integer that is treated like a memory address. We can\nuse that memory address to get the start of the data located in that memory slot.\nBut we can also just store an normal value like 18 in it. There might be cases\nwhere it does not make sense to store a small integer somewhere in the heap and\nhave a pointer to it, but instead store the value directly in the pointer itself.\nBut that only works for small integers so there needs to be away to know if the\nvalue we want is stored in the pointer or if we should follow the value stored to\nthe heap to get the value.\n\nA word on a 64 bit machine is 8 bytes (64 bits) and all of the pointers need to\nbe aligned to multiples of 8. So a pointer could be:\n```\n1000       = 8\n10000      = 16\n11000      = 24\n100000     = 32\n1000000000 = 512\n```\nRemember that we are talking about the pointers and not the values store at\nthe memory location they point to. We can see that there are always three bits\nthat are zero in the pointers. So we can use them for something else and just\nmask them out when using them as pointers.\n\nTagging involves borrowing one bit of the 32-bit, making it 31-bit and having\nthe leftover bit represent a tag. If the tag is zero then this is a plain value,\nbut if tag is 1 then the pointer must be followed.\nThis does not only have to be for numbers it could also be used for object (I think)\n\nInstead the small integer is represented by the 32 bits plus a pointer to the\n64-bit number. V8 needs to know if a value stored in memory represents a 32-bit\ninteger, or if it is really a 64-bit number, in which case it has to follow the\npointer to get the complete value. This is where the concept of tagging comes in.\n\n\n\n### Properties/Elements\nTake the following object:\n\n    { firstname: \"Jon\", lastname: \"Doe' }\n\nThe above object has two named properties. Named properties differ from integer indexed \nwhich is what you have when you are working with arrays.\n\nMemory layout of JavaScript Object:\n```\nProperties                  JavaScript Object               Elements\n+-----------+              +-----------------+         +----------------+\n|property1  |\u003c------+      | HiddenClass     |  +-----\u003e|                |\n+-----------+       |      +-----------------+  |      +----------------+\n|...        |       +------| Properties      |  |      | element1       |\u003c------+\n+-----------+              +-----------------+  |      +----------------+       |\n|...        |              | Elements        |--+      | ...            |       |\n+-----------+              +-----------------+         +----------------+       |\n|propertyN  | \u003c---------------------+                  | elementN       |       |\n+-----------+                       |                  +----------------+       |\n                                    |                                           |\n                                    |                                           |\n                                    |                                           | \nNamed properties:    { firstname: \"Jon\", lastname: \"Doe' } Indexed Properties: {1: \"Jon\", 2: \"Doe\"}\n```\nWe can see that properies and elements are stored in different data structures.\nElements are usually implemented as a plain array and the indexes can be used for fast access\nto the elements. \nBut for the properties this is not the case. Instead there is a mapping between the property names\nand the index into the properties.\n\nIn `src/objects/objects.h` we can find JSObject:\n\n    class JSObject: public JSReceiver {\n    ...\n    DECL_ACCESSORS(elements, FixedArrayBase)\n\n\nAnd looking a the `DECL_ACCESSOR` macro:\n\n    #define DECL_ACCESSORS(name, type)    \\\n      inline type* name() const;          \\\n      inline void set_##name(type* value, \\\n                             WriteBarrierMode mode = UPDATE_WRITE_BARRIER);\n\n    inline FixedArrayBase* name() const;\n    inline void set_elements(FixedArrayBase* value, WriteBarrierMode = UPDATE_WRITE_BARRIER)\n\nNotice 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:\n\n    DECL_ACCESSORS(raw_properties_or_hash, Object)\n\nNow properties (named properties not elements) can be of different kinds internally. These work just\nlike simple dictionaries from the outside but a dictionary is only used in certain curcumstances\nat runtime.\n\n```\nProperties                  JSObject                    HiddenClass (Map)\n+-----------+              +-----------------+         +----------------+\n|property1  |\u003c------+      | HiddenClass     |--------\u003e| bit field1     |\n+-----------+       |      +-----------------+         +----------------+\n|...        |       +------| Properties      |         | bit field2     |\n+-----------+              +-----------------+         +----------------+\n|...        |              | Elements        |         | bit field3     |\n+-----------+              +-----------------+         +----------------+\n|propertyN  |              | property1       |         \n+-----------+              +-----------------+         \n                           | property2       |\n                           +-----------------+\n                           | ...             |\n                           +-----------------+\n\n```\n\n#### JSObject\nEach 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.\nAs mentioned earlier JSObject inherits from JSReceiver which inherits from HeapObject\n\nFor example,in [jsobject_test.cc](./test/jsobject_test.cc) we first create a new Map using the internal Isolate Factory:\n\n    v8::internal::Handle\u003cv8::internal::Map\u003e map = factory-\u003eNewMap(v8::internal::JS_OBJECT_TYPE, 24);\n    v8::internal::Handle\u003cv8::internal::JSObject\u003e js_object = factory-\u003eNewJSObjectFromMap(map);\n    EXPECT_TRUE(js_object-\u003eHasFastProperties());\n\nWhen we call `js_object-\u003eHasFastProperties()` this will delegate to the map instance:\n\n    return !map()-\u003eis_dictionary_map();\n\nHow do you add a property to a JSObject instance?\nTake a look at [jsobject_test.cc](./test/jsobject_test.cc) for an example.\n\n\n### Caching\nAre ways to optimize polymorphic function calls in dynamic languages, for example JavaScript.\n\n#### Lookup caches\nSending a message to a receiver requires the runtime to find the correct target method using\nthe runtime type of the receiver. A lookup cache maps the type of the receiver/message name\npair to methods and stores the most recently used lookup results. The cache is first consulted\nand if there is a cache miss a normal lookup is performed and the result stored in the cache.\n\n#### Inline caches\nUsing a lookup cache as described above still takes a considerable amount of time since the\ncache must be probed for each message. It can be observed that the type of the target does often\nnot vary. If a call to type A is done at a particular call site it is very likely that the next\ntime it is called the type will also be A.\nThe method address looked up by the system lookup routine can be cached and the call instruction\ncan be overwritten. Subsequent calls for the same type can jump directly to the cached method and\ncompletely avoid the lookup. The prolog of the called method must verify that the receivers\ntype has not changed and do the lookup if it has changed (the type if incorrect, no longer A for\nexample).\n\nThe target methods address is stored in the callers code, or \"inline\" with the callers code, \nhence the name \"inline cache\".\n\nIf V8 is able to make a good assumption about the type of object that will be passed to a method,\nit 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.\n\n#### Polymorfic Inline cache (PIC)\nA polymorfic call site is one where there are many equally likely receiver types (and thus\ncall targets).\n\n- Monomorfic means there is only one receiver type\n- Polymorfic a few receiver types\n- Megamorfic very many receiver types\n\nThis type of caching extends inline caching to not just cache the last lookup, but cache\nall lookup results for a given polymorfic call site using a specially generated stub.\nLets say we have a method that ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanbev%2Flearning-v8","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanbev%2Flearning-v8","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanbev%2Flearning-v8/lists"}