As I read more of the spec, I became more intrigued about is used by the keyword: [[[HomeObject]]](https://tc39.github.io/ecma262/#table-16). [[HomeObject]] super let parent = {x: 'parent'};let child = {x: 'child',papaX() { return super.x; }}; child.papaX(); // undefinedObject.setPrototypeOf(child, parent);child.papaX(); // 'parent' when used inside of one of 's methods is basically the equivalent of , whose value can change between call to call. However to do that a method (clarification: a method is a function like , declared with the shorthand notation) needs to know which object it was defined in. As we all know, that can be wildly different from a function’s value — can be altered with and friends alongside a myriad of other reasons. super child Object.getPrototypeOf(child) papaX this this call Enter . It’s a magical property assigned to methods at creation time which refers to the object they were created in. In the example above, inside of actually translates into: [[HomeObject]] super.x papaX Object.getPrototypeOf(papaX.[[HomeObject]]).x Except that, of course, accessing the of a function is outside the capabilities of regular JavaScript code. [[HomeObject]] That intrigued me. What if we have the power to access the of a function? If functions did have a constant binding to the object they were created in? I’m not saying it’s a good idea to have one, as a matter of fact I’m saying it’s a bad idea, but it is an one_._ did [[HomeObject]] interesting So to ask the question: How do we implement accessing in the various engines? [[HomeObject]] I’ll assume no prior knowledge on the JavaScript engines, how they’re implemented, C++, understanding of whales, breathing expertise, and most things in general. If you’ve ever wondered about engine internals I hope this will entertain and amaze! V8 Let’s begin by asking where the hell V8’s source is. You might be tempted to answer that it’s . While that’s (partly) true, let me introduce you to : on github the best website in the internet https://cs.chromium.org Let’s start out by optimistically searching for HomeObject Looks promising! We can continue the search to see all search results, but let’s look at what we have in front of us: doesn’t sound interesting to us right now, since it’ll probably only check if a function is a method. kNeedsHomeObject sounds better, it sounds like the place is set, so maybe we can go there and look for call-ees? HomeObjectSlot HomeObject Oh, ! Tests are always one of the best places for learning how a complex beast like v8 works, : TestHomeObject let’s open that Huh, that’s weird. What’s with the ? What’s that doing there, and why the heck are they calling a symbol? %HomeObjectSymbol() % HomeObject As it turns out, v8 is in part written in JavaScript (e.g. among many other functions!) which sometimes needs to use features which either can’t be written in plain js (e.g. run the GC, optimise this function, …), or which for performance reasons should be done in native code. are denoted with a prefix. We can actually use them with trusty ol’ node (which runs on v8) by passing a special flag: Array.prototype.reduce Those special functions % --allow-natives-syntax I should probably go to sleep! This is kind of cool! Hey, let’s get a list of all these functions. We happen to already have seen the name of one, , so let’s : HomeObjectSymbol search for that There’s only ten results so nothing popping out as obvious isn’t too bad, we can go through one by one. And what do you know…the first result looks pretty promising: Let’s see if they’re indeed all builtins or if they’re just there to tease us. Checking against two maybe-builtins from the above list: The 2nd one was to test what happens if we try to run a non-existing builtin, and it looks like the other two are indeed builtins, so there we have it, the list of all builtins! But wait wait wait, what led us down this rabbit hole? We were looking at how was accessed, and found out about . Let’s go back to our search to see where it’s implemented: HomeObject %HomeObjectSymbol The macro call looks promising, especially when the function was declared in . Let’s take a looksie! [RUNTIME_FUNCTION](https://cs.chromium.org/chromium/src/v8/src/runtime/runtime-classes.cc?l=96&rcl=06d36330e481d098c19fe047809d45955837ee46) inside of [runtime-classes.cc](https://cs.chromium.org/chromium/src/v8/src/runtime/runtime-classes.cc?l=96&rcl=06d36330e481d098c19fe047809d45955837ee46) runtime.h UNTIME_FUNCTION( ) { CHECK_EQ(0, . ());return -> ()-> ();} R Runtime_ HomeObjectSymbol D args length isolate heap home_object_symbol Well that doesn’t tell us much, let’s click on and : home_object_access see where it takes us #define (name) \Symbol* Heap::name() { return Symbol::cast(roots_[k##name##RootIndex]); } RIVATE_SYMBOL_LIST( )#undef SYMBOL_ACCESSOR P SYMBOL_ACCESSOR SYMBOL_ACCESSOR So I don’t know about you, but I’m not a C preprocessor so all of that looks like a bunch of gibberish to me. However, looking at that made me think: Zirak, you handsome creature, you spec of light in this dark universe, the reason I wake up in the morning, the pecan of my pie and gold nugget in my tooth, if it’s a symbol like any other symbol, what’ll it take to expose it so we could do ? func[Symbol.homeObject] To answer that question let’s look at how other symbols are declared and copy them. While we’re doing that remember the line of code we saw above: . We don’t have to understand what it does, just its overall feel. We’ll pick a less used symbol so we’ll have an easier time searching, , and : [isolate](https://cs.chromium.org/chromium/src/v8/src/runtime/runtime-classes.cc?l=96&ct=xref_jump_to_def&gsn=isolate)->[heap](https://cs.chromium.org/chromium/src/v8/src/isolate.h?l=856&ct=xref_jump_to_def&gsn=heap)()->[home_object_symbol](https://cs.chromium.org/chromium/src/v8/src/heap/heap-inl.h?l=139&ct=xref_jump_to_def&gsn=home_object_symbol)() [unscopables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/unscopables) narrow the search down to only search in v8, and exclude tests and javascript files Cool, only 6 results, very easy to go over all of them. Let’s take a looksie: doesn’t look related, we’re looking for implementation and ain’t it. api.cc GetUnscopables same, only declares . v8.h GetUnscopables All results in are related to . contexts.cc UnscopablesLookup ChangeLog. Fascinating indeed, but not for our purposes. looks just about right. Remember the line above? We got a reference to by getting some heap. Let’s open that in another tab and look at the final result. heap-symbols.h home_object_symbol What a weird name, but a call to inside a function called is enough to interest me any day of the week! Open that in yet another tab. bootstrapper.cc InstallConstant InitializeGlobal So let’s : see what that heap is about #define (V) \V(array_iteration_kind_symbol) \V(array_iterator_next_symbol) \// ...V(home_object_symbol) \// ...V(uninitialized_symbol) PRIVATE_SYMBOL_LIST #define (V) \V(async_iterator_symbol, Symbol.asyncIterator) \V(iterator_symbol, Symbol.iterator) \// ...V(to_primitive_symbol, Symbol.toPrimitive) \V( _symbol, Symbol. ) PUBLIC_SYMBOL_LIST unscopables unscopables Wanderin’ into our town are two lists, a and a symbol list. We see that the dear is in the list but our protégé is under the list! It’s time to shake its shy demeanour and expose him to the world in this episode of “Introvert to Extrovert: EXTREME Symbol Makeovers”: PRIVATE PUBLIC unscopables PUBLIC home_object PRIVATE Just moving the symbol down to the list and giving it that weird second argument, the list now looks like: PUBLIC PUBLIC #define (V) \V(async_iterator_symbol, Symbol.asyncIterator) \V(iterator_symbol, Symbol.iterator) \V(intl_fallback_symbol, IntlFallback) \V(match_symbol, Symbol.match) \V(replace_symbol, Symbol.replace) \V(search_symbol, Symbol.search) \V(species_symbol, Symbol.species) \V(split_symbol, Symbol.split) \V(to_primitive_symbol, Symbol.toPrimitive) \V(home_object_symbol, Symbol.homeObject) \V(unscopables_symbol, Symbol.unscopables) PUBLIC_SYMBOL_LIST Cool. However, we only made this symbol “public”, whatever that means, we still have to expose it the function somehow! Remember that result we saw above? Let’s look at that code. Symbol InstallConstant // Install well-known symbols.InstallConstant(isolate, symbol_fun, "hasInstance",factory->has_instance_symbol());InstallConstant(isolate, symbol_fun, "isConcatSpreadable",factory->is_concat_spreadable_symbol());// ...InstallConstant(isolate, symbol_fun, "unscopables",factory->unscopables_symbol()); okay…those are all the properties of . Let’s add our own! Symbol And we’re good to go! Let’s Play v8 Compiling v8 is one of those things at on the one hand isn’t difficult, but on the other is a bit of a drag at first, so let’s walk through those steps. I’ll be using Linux, the instructions for OSX should be pretty much the same (wooo shells!), and the documentation links provide install instructions for Windows. Anyway! Google has a lot of big, inter-connected projects and their own suite of tools to manage such projects called . It contains helpers to clone repos, keep 3rd-party dependencies up to date, create issues and patches, etc etc. They also contain the necessary build system ( ) and makefile generator ( ). Who said software development isn’t fun? depot_tools ninja gn We’ll begin by : installing depot_tools $ mkdir awesome-v8-playground && cd awesome-v8-playground$ git clone $ export PATH=”$(pwd)/depot_tools”:”$PATH”$ which gn/home/zirak/awesome-v8-playground/depot_tools/gn https://chromium.googlesource.com/chromium/tools/depot_tools.git Coolio. If you’re planning on working on v8 or chromium a lot, I recommend you put that in your favourite shell configuration file. Let’s look at what that brought us: export $ ls depot_tools | wc165 Yikes, that’s quite a lot. The ones we’ll use are: fetch: Used for downloading Chromium-family projects. gclient: Dependency manager, sort of a submodules wrapper/replacement ninja: Build system, a alternative make gn: makefile (rather, ninja-file) generator Now let’s : fetch the v8 source $ fetch v8Running: gclient rootRunning: gclient config --spec 'solutions = [{"url": " ","managed": False,"name": "v8","deps_file": "DEPS","custom_deps": {},},]'Running: gclient sync --with_branch_heads... take time to contemplate your existence ... https://chromium.googlesource.com/v8/v8.git ... did you contemplate? maybe it'll go faster if you do ... ... maybe reconsider software dev. pick up the viola ... ... the viola is a great instrument ... ... such a tender sound, and a greater amplitude than the violin ... ... hey remember that guy you saw at the gas station that one time who looked kinda weird and preoccupied? wonder what's up with him ... ... maybe he's a viola player wondering what's up with violottas ... ... oh look it finished ... Running: git submodule foreach 'git config -f $toplevel/.git/config submodule.$name.ignore all'Running: git config --add remote.origin.fetch '+refs/tags/*:refs/tags/*'Running: git config diff.ignoreSubmodules all Viola! Now let’s : do the build-tool dance $ gclient sync... maybe pmj are hiring...$ tools/dev/v8gen.py x64.release The next step’s the compilation. Let’s first do a clean build before modifying the source: (note to aficionados: you don’t need to pass , ninja is smart) make -j $ ninja -C out.gn/x64.release/ninja: Entering directory `out.gn/x64.release/'[88/1475] CXX obj/src/inspector/inspector/violas.o If you’re on a x86 system you should reconsider some life choices and replace in the above several commands with , i.e. x64.release ia32.release # Note: only for x86 compilations$ tools/dev/v8gen.py ia32.release$ ninja -C out.gn/ia32.release/ We can also see a list of possible compilation targets: $ tools/dev/v8gen.py listarm.debugarm.optdebugarm.releasearm64.debugarm64.optdebugarm64.releaseia32.debugia32.optdebugia32.releasemips64el.debugmips64el.optdebugmips64el.release... We’re compiling to release since we’re not going to debug v8’s native code. If you’re adventurous and want debug symbols, in addition to compiling to a target I suggest passing the flag to to speed up each re-compilation. Skip the step and : debug is_component_build gn v8gen.py run something like the following # Note: This is for debug builds. I'll get on with the regular# article after this!$ gn gen out.gn/awesome --args='is_debug=true target_cpu="x64" is_component_build=true'$ ninja -C out.gn/awesome/ It finished compiling! Hurray! Compiling v8 gives us some cool toys to play with, first and foremost , a basic javascript repl: d8 $ out.gn/x64.release/V8 version 5.9.0 (candidate)d8> 4 + 48d8> Sexy. Let’s apply our patches ( , ). Download them to a local directory and : bootsrapper.cc heap-symbols.h git apply $ git apply heap-symbols.h.diff$ git apply bootstrapper.cc.diff Note that is very very sensitive and omitting parts of the may offend it and cause it to storm out of the room in tears. Also, I expect that these diffs will break any day now so don’t rely on them: If throws a fit it’s okay, no reason to give up, you can do these edits manually. apply diff apply Let’s recompile and see what’s what: $ ninja -C out.gn/x64.release/ninja: Entering directory `out.gn/x64.release/’[12/751] CXX obj/v8_base/life-regret-analysis.o While it’s compiling, let’s take a look back . I found that it’s a nice play to look at occasionally and learn about new or obscure features. For instance did you know ? I certainly didn’t! Apparently it’s part of ES2015 though ( , ). Poke around this file in general, it’s fairly interesting. at [InitializeGlobal](https://cs.chromium.org/chromium/src/v8/src/bootstrapper.cc?l=1174&rcl=4e3e384275b567b0b518412e7c78cb25dd3f1782) in [bootstrapper.cc](https://cs.chromium.org/chromium/src/v8/src/bootstrapper.cc?l=1174&rcl=4e3e384275b567b0b518412e7c78cb25dd3f1782) about [Number.isSafeInteger](https://cs.chromium.org/chromium/src/v8/src/bootstrapper.cc?l=1539&rcl=4e3e384275b567b0b518412e7c78cb25dd3f1782) mdn spec Oh look, it finished compiling. Let’s take a quick looksie… $ out.gn/x64.release/d8V8 version 5.9.0 (candidate)d8> Symbol.homeObjectSymbol(Symbol.homeObject) SUCCESS! Time to try out this bad boy. Remember, is supposedly implemented as sort of super Object.getPrototypeOf(func.[[HomeObject]]) So should be a property on functions. HomeObject d8> var o = { f() {} };d8> o.f[Symbol.homeObject]undefined huh, that’s weird. Let’s play a bit more, maybe we missed something? Let’s try again. Quit d8, and run again with cheat codes: $ out.gn/x64.release/d8 --allow-natives-syntaxV8 version 5.9.0 (candidate)d8> %HomeObjectSymbol() === Symbol.homeObject // sanitytrued8> var o = { f() {} };undefinedd8> o.f[%HomeObjectSymbol()]undefined Looks like we have a deeper problem. Our patch works but it seems like doesn’t have a ! Take a few minutes to see if you can find out why. Symbol.homeObject o.f HomeObject I’ll wait. (。◕‿‿◕。) ༼ʘ̚ل͜ʘ̚༽ So! What could go wrong? I started out by writing a few lines which to use : have HomeObject d8> var parent = { x: 4 };d8> var child = { __proto__: parent, whatsX() { return super.x; } };d8> child.whatsX[Symbol.homeObject]{whatsX: whatsX() { return super.x; }} That works, so the difference seems to stem from using . Oh wait, remember our very first search for ? super HomeObject See that method? Sounds pretty important right now! Let’s : NeedsHomeObject check it out static ( ) {return :: ( );} bool Needs HomeObject Expression * expr FunctionLiteral Needs HomeObject expr uunnghh, indirections…click on and it for some reason brings up to : NeedsHomeObject almost the right place :: ( ) {if ( == nullptr || -> ()) return ; ( -> ()-> ());return -> ()-> ()-> ();} bool FunctionLiteral NeedsHomeObject Expression * expr expr IsFunctionLiteral false DCHECK_NOT_NULL expr AsFunctionLiteral scope expr AsFunctionLiteral scope NeedsHomeObject okay, : click on [NeedsHomeObject](https://cs.chromium.org/chromium/src/v8/src/ast/scopes.h?l=643&rcl=f8189977d2cf40ce3c0b1e1d10f0717c2e2a3e7a) again // src/scopes.h () const {return ||( && ( ( ()) || ( ()) || ( ())));} bool NeedsHomeObject scope_uses_super_property_ inner_scope_calls_eval_ IsConciseMethod function_kind IsAccessorFunction function_kind IsClassConstructor function_kind Jackpot! medium may have messed up some of the indentation but this looks promising. v8 being an optimisation-happy critter only wants to put on functions which need it. Sneaky critter. HomeObject An interesting tangent I won’t go into is discovering where is used and how it affects functions. If you want to go down that road, I personally suggest you go to and click on the method name. That should bring up the list of XRefs (cross-references) to the method: NeedsHomeObject the original callsite ooohhh, fancy (A thing to note about chromium’s source browser (besides how awesome it is) is that it works by indexing one compilation target, I suspect the x64 linux one. That means some things may not work so great, like checking XRefs to Windows-only functions or, in this case, XRefs to the codegen for non-x64 architectures like mips. We don’t necessarily care right now, just a thing to remember.) For all others who value their sanity and are oh-so-eager to continue this treacherous journey, delve onwards! Patching NeedsHomeObject It’s a simple diff, really: Save locally and do the patch/recompile dance: $ git apply scopes.h.diff$ ninja -C out.gn/x64.release/ninja: Entering directory `out.gn/x64.release/'[175/175] STAMP obj/going.postal.aarrgghhh$ out.gn/x64.release/d8V8 version 5.9.0 (candidate)d8> var o = { f() {} };undefinedd8> o.f[Symbol.homeObject]{f: f() {}}d8> ̿̿ ̿̿ ̿̿ ̿’̿’\̵͇̿̿\з= ( ▀ ͜͞ʖ▀) =ε/̵͇̿̿/’̿’̿ ̿ ̿̿ ̿̿ ̿̿ Dance What did we learn today? The entirety of Chromium’s source code is indexed and easily accessible via https://cs.chromium.org/ We can understand how specific sections of code work without knowing exactly or they work by using guesstimates and googling around how why Additionally, we can use the knowledge found inside the source code to modify existing pieces Tests are an amazing treasure trove of knowledge Compiling v8 isn’t that bad ASCII faces are fun Finally, the complete : git diff And we’ve got an exposed . I call that a win. homeObject
Share Your Thoughts