/
README.md
8044 lines (7080 loc) · 329 KB
/
README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
## Learning Google V8
The sole purpose of this project is to aid me in leaning Google's V8 JavaScript engine.
### Contents
1. [Introduction](./notes/intro.md)
1. [Address](#address)
1. [TaggedImpl](#taggedimpl)
1. [Object](#object)
1. [Handle](#handle)
1. [FunctionTemplate](#functiontemplate)
1. [ObjectTemplate](#objecttemplate)
1. [Small Integers](#small-integers)
1. [String types](./notes/string.md)
1. [Roots](#roots)
1. [Heap](./notes/heap.md)
1. [Builtins](#builtins)
1. [Compiler pipeline](#compiler-pipeline)
1. [CodeStubAssembler](#codestubassembler)
1. [Torque](#torque)
1. [WebAssembly](#webassembly)
1. [Promises](./notes/promises.md)
1. [Snapshots](./notes/snapshots.md)
1. [V8 Build artifacts](#v8-build-artifacts)
1. [V8 Startup walkthrough](#startup-walk-through)
1. [Building V8](#building-v8)
1. [Contributing a change](#contributing-a-change)
1. [Debugging](#debugging)
1. [Building chromium](#building-chromium)
1. [Goma chromium](#goma)
1. [EcmaScript notes](./notes/ecmaspec.md)
1. [GN notes](./notes/gn.md)
### Isolate
An Isolate is an independant copy of the V8 runtime which includes its own heap.
Two different Isolates can run in parallel and can be seen as entirely different
sandboxed instances of a V8 runtime.
### Context
To allow separate JavaScript applications to run in the same isolate a context
must be specified for each one. This is to avoid them interfering with each
other, for example by changing the builtin objects provided.
### Template
This is the super class of both ObjecTemplate and FunctionTemplate. Remember
that in JavaScript a function can have fields just like objects.
```c++
class V8_EXPORT Template : public Data {
public:
void Set(Local<Name> name, Local<Data> value,
PropertyAttribute attributes = None);
void SetPrivate(Local<Private> name, Local<Data> value,
PropertyAttribute attributes = None);
V8_INLINE void Set(Isolate* isolate, const char* name, Local<Data> value);
void SetAccessorProperty(
Local<Name> name,
Local<FunctionTemplate> getter = Local<FunctionTemplate>(),
Local<FunctionTemplate> setter = Local<FunctionTemplate>(),
PropertyAttribute attribute = None,
AccessControl settings = DEFAULT);
```
The `Set` function can be used to have an name and a value set on an instance
created from this template.
The `SetAccessorProperty` is for properties that are get/set using functions.
```c++
enum PropertyAttribute {
/** None. **/
None = 0,
/** ReadOnly, i.e., not writable. **/
ReadOnly = 1 << 0,
/** DontEnum, i.e., not enumerable. **/
DontEnum = 1 << 1,
/** DontDelete, i.e., not configurable. **/
DontDelete = 1 << 2
};
enum AccessControl {
DEFAULT = 0,
ALL_CAN_READ = 1,
ALL_CAN_WRITE = 1 << 1,
PROHIBITS_OVERWRITING = 1 << 2
};
```
### ObjectTemplate
These allow you to create JavaScript objects without a dedicated constructor.
When an instance is created using an ObjectTemplate the new instance will have
the properties and functions configured on the ObjectTemplate.
This would be something like:
```js
const obj = {};
```
This class is declared in include/v8.h and extends Template:
```c++
class V8_EXPORT ObjectTemplate : public Template {
...
}
class V8_EXPORT Template : public Data {
...
}
class V8_EXPORT Data {
private:
Data();
};
```
We create an instance of ObjectTemplate and we can add properties to it that
all instance created using this ObjectTemplate instance will have. This is done
by calling `Set` which is member of the `Template` class. You specify a
Local<Name> for the property. `Name` is a superclass for `Symbol` and `String`
which can be both be used as names for a property.
The implementation for `Set` can be found in `src/api/api.cc`:
```c++
void Template::Set(v8::Local<Name> name, v8::Local<Data> value, v8::PropertyAttribute attribute) {
...
i::ApiNatives::AddDataProperty(isolate, templ, Utils::OpenHandle(*name),
value_obj,
static_cast<i::PropertyAttributes>(attribute));
}
```
There is an example in [objecttemplate_test.cc](./test/objecttemplate_test.cc)
### FunctionTemplate
Is a template that is used to create functions and like ObjectTemplate it inherits
from Template:
```c++
class V8_EXPORT FunctionTemplate : public Template {
}
```
Rememeber that a function in javascript can have properties just like object.
There is an example in [functiontemplate_test.cc](./test/functiontemplate_test.cc)
An instance of a function template can be created using:
```c++
Local<FunctionTemplate> ft = FunctionTemplate::New(isolate_, function_callback, data);
Local<Function> function = ft->GetFunction(context).ToLocalChecked();
```
And the function can be called using:
```c++
MaybeLocal<Value> ret = function->Call(context, recv, 0, nullptr);
```
Function::Call can be found in `src/api/api.cc`:
```c++
bool has_pending_exception = false;
auto self = Utils::OpenHandle(this);
i::Handle<i::Object> recv_obj = Utils::OpenHandle(*recv);
i::Handle<i::Object>* args = reinterpret_cast<i::Handle<i::Object>*>(argv);
Local<Value> result;
has_pending_exception = !ToLocal<Value>(
i::Execution::Call(isolate, self, recv_obj, argc, args), &result);
```
Notice that the return value of `Call` which is a `MaybeHandle<Object>` will be
passed to ToLocal<Value> which is defined in `api.h`:
```c++
template <class T>
inline bool ToLocal(v8::internal::MaybeHandle<v8::internal::Object> maybe,
Local<T>* local) {
v8::internal::Handle<v8::internal::Object> handle;
if (maybe.ToHandle(&handle)) {
*local = Utils::Convert<v8::internal::Object, T>(handle);
return true;
}
return false;
```
So lets take a look at `Execution::Call` which can be found in `execution/execution.cc`
and it calls:
```c++
return Invoke(isolate, InvokeParams::SetUpForCall(isolate, callable, receiver, argc, argv));
```
`SetUpForCall` will return an `InvokeParams`.
TODO: Take a closer look at InvokeParams.
```c++
V8_WARN_UNUSED_RESULT MaybeHandle<Object> Invoke(Isolate* isolate,
const InvokeParams& params) {
```
```c++
Handle<Object> receiver = params.is_construct
? isolate->factory()->the_hole_value()
: params.receiver;
```
In our case `is_construct` is false as we are not using `new` and the receiver,
the `this` in the function should be set to the receiver that we passed in. After
that we have `Builtins::InvokeApiFunction`
```c++
auto value = Builtins::InvokeApiFunction(
isolate, params.is_construct, function, receiver, params.argc,
params.argv, Handle<HeapObject>::cast(params.new_target));
```
```c++
result = HandleApiCallHelper<false>(isolate, function, new_target,
fun_data, receiver, arguments);
```
`api-arguments-inl.h` has:
```c++
FunctionCallbackArguments::Call(CallHandlerInfo handler) {
...
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
FunctionCallbackInfo<v8::Value> info(values_, argv_, argc_);
f(info);
return GetReturnValue<Object>(isolate);
}
```
The call to f(info) is what invokes the callback, which is just a normal
function call.
Back in `HandleApiCallHelper` we have:
```c++
Handle<Object> result = custom.Call(call_data);
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object);
```
`RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION` expands to:
```c++
Handle<Object> result = custom.Call(call_data);
do {
Isolate* __isolate__ = (isolate);
((void) 0);
if (__isolate__->has_scheduled_exception()) {
__isolate__->PromoteScheduledException();
return MaybeHandle<Object>();
}
} while (false);
```
Notice that if there was an exception an empty object is returned.
Later in `Invoke` in `execution.cc`a:
```c++
auto value = Builtins::InvokeApiFunction(
isolate, params.is_construct, function, receiver, params.argc,
params.argv, Handle<HeapObject>::cast(params.new_target));
bool has_exception = value.is_null();
if (has_exception) {
if (params.message_handling == Execution::MessageHandling::kReport) {
isolate->ReportPendingMessages();
}
return MaybeHandle<Object>();
} else {
isolate->clear_pending_message();
}
return value;
```
Looking at this is looks like passing back an empty object will cause an
exception to be triggered?
### Address
`Address` can be found in `include/v8-internal.h`:
```c++
typedef uintptr_t Address;
```
`uintptr_t` is an optional type specified in `cstdint` and is capable of storing
a data pointer. It is an unsigned integer type that any valid pointer to void
can be converted to this type (and back).
### TaggedImpl
This class is declared in `src/objects/tagged-impl.h and has a single private
member which is declared as:
```c++
public
constexpr StorageType ptr() const { return ptr_; }
private:
StorageType ptr_;
```
An instance can be created using:
```c++
i::TaggedImpl<i::HeapObjectReferenceType::STRONG, i::Address> tagged{};
```
Storage type can also be `Tagged_t` which is defined in globals.h:
```c++
using Tagged_t = uint32_t;
```
It looks like it can be a different value when using pointer compression.
See [tagged_test.cc](./test/tagged_test.cc) for an example.
### Object
This class extends TaggedImpl:
```c++
class Object : public TaggedImpl<HeapObjectReferenceType::STRONG, Address> {
```
An Object can be created using the default constructor, or by passing in an
Address which will delegate to TaggedImpl constructors. Object itself does
not have any members (apart from `ptr_` which is inherited from TaggedImpl that is).
So if we create an Object on the stack this is like a pointer/reference to
an object:
```
+------+
|Object|
|------|
|ptr_ |---->
+------+
```
Now, `ptr_` is a StorageType so it could be a `Smi` in which case it would just
contains the value directly, for example a small integer:
```
+------+
|Object|
|------|
| 18 |
+------+
```
See [object_test.cc](./test/object_test.cc) for an example.
### ObjectSlot
```c++
i::Object obj{18};
i::FullObjectSlot slot{&obj};
```
```
+----------+ +---------+
|ObjectSlot| | Object |
|----------| |---------|
| address | ---> | 18 |
+----------+ +---------+
```
See [objectslot_test.cc](./test/objectslot_test.cc) for an example.
### Maybe
A Maybe is like an optional which can either hold a value or nothing.
```c++
template <class T>
class Maybe {
public:
V8_INLINE bool IsNothing() const { return !has_value_; }
V8_INLINE bool IsJust() const { return has_value_; }
...
private:
bool has_value_;
T value_;
}
```
I first thought that name `Just` was a little confusing but if you read this
like:
```c++
bool cond = true;
Maybe<int> maybe = cond ? Just<int>(10) : Nothing<int>();
```
I think it makes more sense. There are functions that check if the Maybe is
nothing and crash the process if so. You can also check and return the value
by using `FromJust`.
The usage of Maybe is where api calls can fail and returning Nothing is a way
of signaling this.
See [maybe_test.cc](./test/maybe_test.cc) for an example.
### MaybeLocal
```c++
template <class T>
class MaybeLocal {
public:
V8_INLINE MaybeLocal() : val_(nullptr) {}
V8_INLINE Local<T> ToLocalChecked();
V8_INLINE bool IsEmpty() const { return val_ == nullptr; }
template <class S>
V8_WARN_UNUSED_RESULT V8_INLINE bool ToLocal(Local<S>* out) const {
out->val_ = IsEmpty() ? nullptr : this->val_;
return !IsEmpty();
}
private:
T* val_;
```
`ToLocalChecked` will crash the process if `val_` is a nullptr. If you want to
avoid a crash one can use `ToLocal`.
See [maybelocal_test.cc](./test/maybelocal_test.cc) for an example.
### Data
Is the super class of all objects that can exist the V8 heap:
```c++
class V8_EXPORT Data {
private:
Data();
};
```
### Value
Value extends `Data` and adds a number of methods that check if a Value
is of a certain type, like `IsUndefined()`, `IsNull`, `IsNumber` etc.
It also has useful methods to convert to a Local<T>, for example:
```c++
V8_WARN_UNUSED_RESULT MaybeLocal<Number> ToNumber(Local<Context> context) const;
V8_WARN_UNUSED_RESULT MaybeLocal<String> ToNumber(Local<String> context) const;
...
```
### Handle
A Handle is similar to a Object and ObjectSlot in that it also contains
an Address member (called `location_` and declared in `HandleBase`), but with the
difference is that Handles acts as a layer of abstraction and can be relocated
by the garbage collector.
Can be found in `src/handles/handles.h`.
```c++
class HandleBase {
...
protected:
Address* location_;
}
template <typename T>
class Handle final : public HandleBase {
...
}
```
```
+----------+ +--------+ +---------+
| Handle | | Object | | int |
|----------| +-----+ |--------| |---------|
|*location_| ---> |&ptr_| --> | ptr_ | -----> | 5 |
+----------+ +-----+ +--------+ +---------+
```
```console
(gdb) p handle
$8 = {<v8::internal::HandleBase> = {location_ = 0x7ffdf81d60c0}, <No data fields>}
```
Notice that `location_` contains a pointer:
```console
(gdb) p /x *(int*)0x7ffdf81d60c0
$9 = 0xa9d330
```
And this is the same as the value in obj:
```console
(gdb) p /x obj.ptr_
$14 = 0xa9d330
```
And we can access the int using any of the pointers:
```console
(gdb) p /x *value
$16 = 0x5
(gdb) p /x *obj.ptr_
$17 = 0x5
(gdb) p /x *(int*)0x7ffdf81d60c0
$18 = 0xa9d330
(gdb) p /x *(*(int*)0x7ffdf81d60c0)
$19 = 0x5
```
See [handle_test.cc](./test/handle_test.cc) for an example.
### HandleScope
Contains a number of Local/Handle's (think pointers to objects but is managed
by V8) and will take care of deleting the Local/Handles for us. HandleScopes
are stack allocated
When ~HandleScope is called all handles created within that scope are removed
from the stack maintained by the HandleScope which makes objects to which the
handles point being eligible for deletion from the heap by the GC.
A HandleScope only has three members:
```c++
internal::Isolate* isolate_;
internal::Address* prev_next_;
internal::Address* prev_limit_;
```
Lets take a closer look at what happens when we construct a HandleScope:
```c++
v8::HandleScope handle_scope{isolate_};
```
The constructor call will end up in `src/api/api.cc` and the constructor simply
delegates to `Initialize`:
```c++
HandleScope::HandleScope(Isolate* isolate) { Initialize(isolate); }
void HandleScope::Initialize(Isolate* isolate) {
i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate);
...
i::HandleScopeData* current = internal_isolate->handle_scope_data();
isolate_ = internal_isolate;
prev_next_ = current->next;
prev_limit_ = current->limit;
current->level++;
}
```
Every `v8::internal::Isolate` has member of type HandleScopeData:
```c++
HandleScopeData* handle_scope_data() { return &handle_scope_data_; }
HandleScopeData handle_scope_data_;
```
HandleScopeData is a struct defined in `src/handles/handles.h`:
```c++
struct HandleScopeData final {
Address* next;
Address* limit;
int level;
int sealed_level;
CanonicalHandleScope* canonical_scope;
void Initialize() {
next = limit = nullptr;
sealed_level = level = 0;
canonical_scope = nullptr;
}
};
```
Notice that there are two pointers (Address*) to next and a limit. When a
HandleScope is Initialized the current handle_scope_data will be retrieved
from the internal isolate. The HandleScope instance that is getting created
stores the next/limit pointers of the current isolate so that they can be restored
when this HandleScope is closed (see CloseScope).
So with a HandleScope created, how does a Local<T> interact with this instance?
When a Local<T> is created this will/might go through FactoryBase::NewStruct
which will allocate a new Map and then create a Handle for the InstanceType
being created:
```c++
Handle<Struct> str = handle(Struct::cast(result), isolate());
```
This will land in the constructor Handle<T>src/handles/handles-inl.h
```c++
template <typename T>
Handle<T>::Handle(T object, Isolate* isolate): HandleBase(object.ptr(), isolate) {}
HandleBase::HandleBase(Address object, Isolate* isolate)
: location_(HandleScope::GetHandle(isolate, object)) {}
```
Notice that `object.ptr()` is used to pass the Address to HandleBase.
And also notice that HandleBase sets its location_ to the result of HandleScope::GetHandle.
```c++
Address* HandleScope::GetHandle(Isolate* isolate, Address value) {
DCHECK(AllowHandleAllocation::IsAllowed());
HandleScopeData* data = isolate->handle_scope_data();
CanonicalHandleScope* canonical = data->canonical_scope;
return canonical ? canonical->Lookup(value) : CreateHandle(isolate, value);
}
```
Which will call `CreateHandle` in this case and this function will retrieve the
current isolate's handle_scope_data:
```c++
HandleScopeData* data = isolate->handle_scope_data();
Address* result = data->next;
if (result == data->limit) {
result = Extend(isolate);
}
```
In this case both next and limit will be 0x0 so Extend will be called.
Extend will also get the isolates handle_scope_data and check the current level
and after that get the isolates HandleScopeImplementer:
```c++
HandleScopeImplementer* impl = isolate->handle_scope_implementer();
```
`HandleScopeImplementer` is declared in `src/api/api.h`
HandleScope:CreateHandle will get the handle_scope_data from the isolate:
```c++
Address* HandleScope::CreateHandle(Isolate* isolate, Address value) {
HandleScopeData* data = isolate->handle_scope_data();
if (result == data->limit) {
result = Extend(isolate);
}
// Update the current next field, set the value in the created handle,
// and return the result.
data->next = reinterpret_cast<Address*>(reinterpret_cast<Address>(result) + sizeof(Address));
*result = value;
return result;
}
```
Notice that `data->next` is set to the address passed in + the size of an
Address.
The destructor for HandleScope will call CloseScope.
See [handlescope_test.cc](./test/handlescope_test.cc) for an example.
### EscapableHandleScope
Local handles are located on the stack and are deleted when the appropriate
destructor is called. If there is a local HandleScope then it will take care
of this when the scope returns. When there are no references left to a handle
it can be garbage collected. This means if a function has a HandleScope and
wants to return a handle/local it will not be available after the function
returns. This is what EscapableHandleScope is for, it enable the value to be
placed in the enclosing handle scope to allow it to survive. When the enclosing
HandleScope goes out of scope it will be cleaned up.
```c++
class V8_EXPORT EscapableHandleScope : public HandleScope {
public:
explicit EscapableHandleScope(Isolate* isolate);
V8_INLINE ~EscapableHandleScope() = default;
template <class T>
V8_INLINE Local<T> Escape(Local<T> value) {
internal::Address* slot = Escape(reinterpret_cast<internal::Address*>(*value));
return Local<T>(reinterpret_cast<T*>(slot));
}
template <class T>
V8_INLINE MaybeLocal<T> EscapeMaybe(MaybeLocal<T> value) {
return Escape(value.FromMaybe(Local<T>()));
}
private:
...
internal::Address* escape_slot_;
};
```
From `api.cc`
```c++
EscapableHandleScope::EscapableHandleScope(Isolate* v8_isolate) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
escape_slot_ = CreateHandle(isolate, i::ReadOnlyRoots(isolate).the_hole_value().ptr());
Initialize(v8_isolate);
}
```
So when an EscapableHandleScope is created it will create a handle with the
hole value and store it in the `escape_slot_` which is of type Address. This
Handle will be created in the current HandleScope, and EscapableHandleScope
can later set a value for that pointer/address which it want to be escaped.
Later when that HandleScope goes out of scope it will be cleaned up.
It then calls Initialize just like a normal HandleScope would.
```c++
i::Address* HandleScope::CreateHandle(i::Isolate* isolate, i::Address value) {
return i::HandleScope::CreateHandle(isolate, value);
}
```
From `handles-inl.h`:
```c++
Address* HandleScope::CreateHandle(Isolate* isolate, Address value) {
DCHECK(AllowHandleAllocation::IsAllowed());
HandleScopeData* data = isolate->handle_scope_data();
Address* result = data->next;
if (result == data->limit) {
result = Extend(isolate);
}
// Update the current next field, set the value in the created handle,
// and return the result.
DCHECK_LT(reinterpret_cast<Address>(result),
reinterpret_cast<Address>(data->limit));
data->next = reinterpret_cast<Address*>(reinterpret_cast<Address>(result) +
sizeof(Address));
*result = value;
return result;
}
```
When Escape is called the following happens (v8.h):
```c++
template <class T>
V8_INLINE Local<T> Escape(Local<T> value) {
internal::Address* slot = Escape(reinterpret_cast<internal::Address*>(*value));
return Local<T>(reinterpret_cast<T*>(slot));
}
```
An the EscapeableHandleScope::Escape (api.cc):
```c++
i::Address* EscapableHandleScope::Escape(i::Address* escape_value) {
i::Heap* heap = reinterpret_cast<i::Isolate*>(GetIsolate())->heap();
Utils::ApiCheck(i::Object(*escape_slot_).IsTheHole(heap->isolate()),
"EscapableHandleScope::Escape", "Escape value set twice");
if (escape_value == nullptr) {
*escape_slot_ = i::ReadOnlyRoots(heap).undefined_value().ptr();
return nullptr;
}
*escape_slot_ = *escape_value;
return escape_slot_;
}
```
If the escape_value is null, the `escape_slot` that is a pointer into the
parent HandleScope is set to the undefined_value() instead of the hole value
which is was previously, and nullptr will be returned. This returned
address/pointer will then be returned after being casted to T*.
Next, we take a look at what happens when the EscapableHandleScope goes out of
scope. This will call HandleScope::~HandleScope which makes sense as any other
Local handles should be cleaned up.
`Escape` copies the value of its argument into the enclosing scope, deletes alli
its local handles, and then gives back the new handle copy which can safely be
returned.
### HeapObject
TODO:
### Local
Has a single member `val_` which is of type pointer to `T`:
```c++
template <class T> class Local {
...
private:
T* val_
}
```
Notice that this is a pointer to T. We could create a local using:
```c++
v8::Local<v8::Value> empty_value;
```
So a Local contains a pointer to type T. We can access this pointer using
`operator->` and `operator*`.
We can cast from a subtype to a supertype using Local::Cast:
```c++
v8::Local<v8::Number> nr = v8::Local<v8::Number>(v8::Number::New(isolate_, 12));
v8::Local<v8::Value> val = v8::Local<v8::Value>::Cast(nr);
```
And there is also the
```c++
v8::Local<v8::Value> val2 = nr.As<v8::Value>();
```
See [local_test.cc](./test/local_test.cc) for an example.
### PrintObject
Using _v8_internal_Print_Object from c++:
```console
$ nm -C libv8_monolith.a | grep Print_Object
0000000000000000 T _v8_internal_Print_Object(void*)
```
Notice that this function does not have a namespace.
We can use this as:
```c++
extern void _v8_internal_Print_Object(void* object);
_v8_internal_Print_Object(*((v8::internal::Object**)(*global)));
```
Lets take a closer look at the above:
```c++
v8::internal::Object** gl = ((v8::internal::Object**)(*global));
```
We use the dereference operator to get the value of a Local (*global), which is
just of type `T*`, a pointer to the type the Local:
```c++
template <class T>
class Local {
...
private:
T* val_;
}
```
We are then casting that to be of type pointer-to-pointer to Object.
```
gl** Object* Object
+-----+ +------+ +-------+
| |----->| |----->| |
+-----+ +------+ +-------+
```
An instance of `v8::internal::Object` only has a single data member which is a
field named `ptr_` of type `Address`:
`src/objects/objects.h`:
```c++
class Object : public TaggedImpl<HeapObjectReferenceType::STRONG, Address> {
public:
constexpr Object() : TaggedImpl(kNullAddress) {}
explicit constexpr Object(Address ptr) : TaggedImpl(ptr) {}
#define IS_TYPE_FUNCTION_DECL(Type) \
V8_INLINE bool Is##Type() const; \
V8_INLINE bool Is##Type(const Isolate* isolate) const;
OBJECT_TYPE_LIST(IS_TYPE_FUNCTION_DECL)
HEAP_OBJECT_TYPE_LIST(IS_TYPE_FUNCTION_DECL)
IS_TYPE_FUNCTION_DECL(HashTableBase)
IS_TYPE_FUNCTION_DECL(SmallOrderedHashTable)
#undef IS_TYPE_FUNCTION_DECL
V8_INLINE bool IsNumber(ReadOnlyRoots roots) const;
}
```
Lets take a look at one of these functions and see how it is implemented. For
example in the OBJECT_TYPE_LIST we have:
```c++
#define OBJECT_TYPE_LIST(V) \
V(LayoutDescriptor) \
V(Primitive) \
V(Number) \
V(Numeric)
```
So the object class will have a function that looks like:
```c++
inline bool IsNumber() const;
inline bool IsNumber(const Isolate* isolate) const;
```
And in src/objects/objects-inl.h we will have the implementations:
```c++
bool Object::IsNumber() const {
return IsHeapObject() && HeapObject::cast(*this).IsNumber();
}
```
`IsHeapObject` is defined in TaggedImpl:
```c++
constexpr inline bool IsHeapObject() const { return IsStrong(); }
constexpr inline bool IsStrong() const {
#if V8_HAS_CXX14_CONSTEXPR
DCHECK_IMPLIES(!kCanBeWeak, !IsSmi() == HAS_STRONG_HEAP_OBJECT_TAG(ptr_));
#endif
return kCanBeWeak ? HAS_STRONG_HEAP_OBJECT_TAG(ptr_) : !IsSmi();
}
```
The macro can be found in src/common/globals.h:
```c++
#define HAS_STRONG_HEAP_OBJECT_TAG(value) \
(((static_cast<i::Tagged_t>(value) & ::i::kHeapObjectTagMask) == \
::i::kHeapObjectTag))
```
So we are casting `ptr_` which is of type Address into type `Tagged_t` which
is defined in src/common/global.h and can be different depending on if compressed
pointers are used or not. If they are not supported it is the same as Address:
```
using Tagged_t = Address;
```
`src/objects/tagged-impl.h`:
```c++
template <HeapObjectReferenceType kRefType, typename StorageType>
class TaggedImpl {
StorageType ptr_;
}
```
The HeapObjectReferenceType can be either WEAK or STRONG. And the storage type
is `Address` in this case. So Object itself only has one member that is inherited
from its only super class and this is `ptr_`.
So the following is telling the compiler to treat the value of our Local,
`*global`, as a pointer (which it already is) to a pointer that points to
a memory location that adhers to the layout of an `v8::internal::Object` type,
which we know now has a `prt_` member. And we want to dereference it and pass
it into the function.
```c++
_v8_internal_Print_Object(*((v8::internal::Object**)(*global)));
```
### ObjectTemplate
But I'm still missing the connection between ObjectTemplate and object.
When we create it we use:
```c++
Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
```
In `src/api/api.cc` we have:
```c++
static Local<ObjectTemplate> ObjectTemplateNew(
i::Isolate* isolate, v8::Local<FunctionTemplate> constructor,
bool do_not_cache) {
i::Handle<i::Struct> struct_obj = isolate->factory()->NewStruct(
i::OBJECT_TEMPLATE_INFO_TYPE, i::AllocationType::kOld);
i::Handle<i::ObjectTemplateInfo> obj = i::Handle<i::ObjectTemplateInfo>::cast(struct_obj);
InitializeTemplate(obj, Consts::OBJECT_TEMPLATE);
int next_serial_number = 0;
if (!constructor.IsEmpty())
obj->set_constructor(*Utils::OpenHandle(*constructor));
obj->set_data(i::Smi::zero());
return Utils::ToLocal(obj);
}
```
What is a `Struct` in this context?
`src/objects/struct.h`
```c++
#include "torque-generated/class-definitions-tq.h"
class Struct : public TorqueGeneratedStruct<Struct, HeapObject> {
public:
inline void InitializeBody(int object_size);
void BriefPrintDetails(std::ostream& os);
TQ_OBJECT_CONSTRUCTORS(Struct)
```
Notice that the include is specifying `torque-generated` include which can be
found `out/x64.release_gcc/gen/torque-generated/class-definitions-tq`. So, somewhere
there must be an call to the `torque` executable which generates the Code Stub
Assembler C++ headers and sources before compiling the main source files. There is
and there is a section about this in `Building V8`.
The macro `TQ_OBJECT_CONSTRUCTORS` can be found in `src/objects/object-macros.h`
and expands to:
```c++
constexpr Struct() = default;
protected:
template <typename TFieldType, int kFieldOffset>
friend class TaggedField;
inline explicit Struct(Address ptr);
```
So what does the TorqueGeneratedStruct look like?
```
template <class D, class P>
class TorqueGeneratedStruct : public P {
public:
```
Where D is Struct and P is HeapObject in this case. But the above is the declartion
of the type but what we have in the .h file is what was generated.
This type is defined in `src/objects/struct.tq`:
```
@abstract
@generatePrint
@generateCppClass
extern class Struct extends HeapObject {
}
```
`NewStruct` can be found in `src/heap/factory-base.cc`
```c++
template <typename Impl>
HandleFor<Impl, Struct> FactoryBase<Impl>::NewStruct(
InstanceType type, AllocationType allocation) {
Map map = Map::GetStructMap(read_only_roots(), type);
int size = map.instance_size();
HeapObject result = AllocateRawWithImmortalMap(size, allocation, map);
HandleFor<Impl, Struct> str = handle(Struct::cast(result), isolate());
str->InitializeBody(size);
return str;
}
```
Every object that is stored on the v8 heap has a Map (`src/objects/map.h`) that
describes the structure of the object being stored.
```c++
class Map : public HeapObject {
```
```console
1725 return Utils::ToLocal(obj);
(gdb) p obj
$6 = {<v8::internal::HandleBase> = {location_ = 0x30b5160}, <No data fields>}
```
So this is the connection, what we see as a Local<ObjectTemplate> is a HandleBase.
TODO: dig into this some more when I have time.
```console
(lldb) expr gl
(v8::internal::Object **) $0 = 0x00000000020ee160
(lldb) memory read -f x -s 8 -c 1 gl
0x020ee160: 0x00000aee081c0121
(lldb) memory read -f x -s 8 -c 1 *gl
0xaee081c0121: 0x0200000002080433
```
You can reload `.lldbinit` using the following command:
```console
(lldb) command source ~/.lldbinit
```
This can be useful when debugging a lldb command. You can set a breakpoint
and break at that location and make updates to the command and reload without
having to restart lldb.
Currently, the lldb-commands.py that ships with v8 contains an extra operation
of the parameter pased to `ptr_arg_cmd`:
```python
def ptr_arg_cmd(debugger, name, param, cmd):
if not param:
print("'{}' requires an argument".format(name))
return
param = '(void*)({})'.format(param)
no_arg_cmd(debugger, cmd.format(param))
```
Notice that `param` is the object that we want to print, for example lets say
it is a local named obj:
```
param = "(void*)(obj)"
```
This will then be "passed"/formatted into the command string:
```
"_v8_internal_Print_Object(*(v8::internal::Object**)(*(void*)(obj))")
```
#### Threads
V8 is single threaded (the execution of the functions of the stack) but there
are supporting threads used for garbage collection, profiling (IC, and perhaps
other things) (I think).
Lets see what threads there are:
$ LD_LIBRARY_PATH=../v8_src/v8/out/x64.release_gcc/ lldb ./hello-world
(lldb) br s -n main
(lldb) r
(lldb) thread list
thread #1: tid = 0x2efca6, 0x0000000100001e16 hello-world`main(argc=1, argv=0x00007fff5fbfee98) + 38 at hello-world.cc:40, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
So at startup there is only one thread which is what we expected. Lets skip ahead to where we create the platform:
Platform* platform = platform::CreateDefaultPlatform();
...
DefaultPlatform* platform = new DefaultPlatform(idle_task_support, tracing_controller);
platform->SetThreadPoolSize(thread_pool_size);
(lldb) fr v thread_pool_size
(int) thread_pool_size = 0
Next there is a check for 0 and the number of processors -1 is used as the size of the thread pool:
(lldb) fr v thread_pool_size