2

I was reading ECMA2019 (the same is true in ES6 too), where I found:

Each object in an ECMAScript engine is associated with a set of internal methods that defines its runtime behaviour. These internal methods are not part of the ECMAScript language. They are defined by this specification purely for expository purposes. However, each object within an implementation of ECMAScript must behave as specified by the internal methods associated with it. The exact manner in which this is accomplished is determined by the implementation.

I also found these Stack Overflow question1 and question2 and that their answers don't seem to give me the answer I am looking for.

My question is simple. If JavaScript engines decide not to implement some of them, then how would they ensure this statement of above spec -

However, each object within an implementation of ECMAScript must behave as specified by the internal methods associated with it.

Let us take an example:

[[GetPrototypeOf]] , [[Get]] , [[Set]] , [[GetOwnProperty]] etc are essential internal methods. If a JavaScript engine refuses to implement them, how does it achieve this functionality? Clearly they have to implement it, just that they can choose to have different method name and different method signature as it is not enforced by spec on them?

Where am I wrong?

Similarly for internal slots too? If they don't have internal variables storing that state, how on earth will they maintain the state of that object when asked?

EDIT : I will add more details to clarify my question. Let us take an example of Object.getPrototypeOf(). This is an API for internal behaviour [[GetPrototypeOf]] and there are possible algorithm for implementing it. The question is not possible ways to implement it a behaviour - its about having a behaviour or not ! and still satisfying the spec overall object behaviour.

Number945
  • 4,631
  • 8
  • 45
  • 83
  • The engine could just have straight copy/paste of the code that would behave *like* the function but it might never define that function. It could me something like a pre-processed macro in C/C++, for example. – VLAZ Feb 07 '20 at 18:45
  • @VLAZ . what about internal slots ? – Number945 Feb 07 '20 at 18:46
  • 1
    While most engines probably do implement something very similar, why is it so absurd to imagine something, that behaves identically to the outside, yet has a different internal implementation? – ASDFGerte Feb 07 '20 at 18:46
  • @ASDFGerte , Spec does not mention how to implement. I agree that each engine can come up with its own algorithm for it. My question is when spec says that engine can refuse to implement these functionality while ensuring behaviour. – Number945 Feb 07 '20 at 18:48
  • 1
    So it just has to come up with something, that guarantees the same behavior to the outside, but what it does on the inside, it totally up to the engine. It's similar to specifying on a sort function, that it has to return a sorted array (mathematically describing, what "sorted" means), yet not caring about whether it's mergesort, quicksort, or whatever the engine wants to use (maybe some new invention?!). – ASDFGerte Feb 07 '20 at 18:50
  • @ASDFGerte but you agree to still have [[Sort]] internal method :) – Number945 Feb 07 '20 at 18:51
  • 1
    Why do you *have* to have it? It might be possible to implement in a different way. Whether it's done or not is not really relevant, as long as it's possible. – VLAZ Feb 07 '20 at 18:55
  • Well, I disagree. It says quite clearly in that answer that a slots implementation is not required which is the answer. What part of that do you not understand or believe? This issue is discussed and answered there and we have nothing further to add because your question has the same roots. – jfriend00 Feb 07 '20 at 20:43
  • 1
    Find, I'll reopen, but that doesn't change the fact that the spec does not require a specific internal implementation and we have nothing further to add beyond what has already been covered. The slots concept in the spec is just how they choose to describe the behavior required from the outside world - that's all. It does not require ANYTHING of the internal implementation except a behavior to the outside world. – jfriend00 Feb 07 '20 at 20:46
  • 1
    Don't understand what you're asking. The internal implementation can be ANYTHING the implementer chooses as long as it satisfies the described interface to the outside world. It's kind of a duuuh that object-specific state has to be stored somewhere. But, how it's stored is up to the implementer. A slot is an abstract concept for us in writing the spec and describing the required behavior. And, it's a duhhh, that if you're going to implement a method that handles `Object.getPrototypeOf()` that there has to be some ***internal*** way to get a prototype out of an object reference. – jfriend00 Feb 07 '20 at 20:55
  • @jfriend00 , Thnx for reopening. Methods define behaviour. If an engine does not follow the internal methods interface (I am not concerned about what algorithm they follow) , how are they going to define that behaviour ? You can have different method names and interfaces , say for , [[Get]] , [[Set]] functionality, but you cannot choose to refuse to implement it and still expect to have same behaviour. How does engines go about it to satisfy the behaviour while still having the flexibility of refusing above internal methods interface ? – Number945 Feb 07 '20 at 21:00
  • @Number945 When people close a question, they are not generally in the habit of doing so after reading *only* the title. You can disagree with the closure, but don't accuse people of acting in bad faith. Sometimes people simply misunderstand what is being asked. That happened to me earlier today, where I [misread a comment](https://stackoverflow.com/questions/60115233/same-method-signature-confusion#comment106323116_60115233). –  Feb 07 '20 at 21:00
  • I could write an ECMAScript engine that, instead of using ones and zeroes in silicon-based memory, uses the presence and absence of (well-trained) monkeys in certain cubicles across the world. It would be vastly slower than even Internet Explorer, but it could *still* be an implementation of ECMAScript. Writing code for that engine would be difficult (and take a lot of bananas), but it would still meet the requirements. – Heretic Monkey Feb 07 '20 at 21:04
  • 1
    I could implement `Object.getPrototypeOf()` internally by looking up the object in question in an external hash table and getting it's prototype there instead of from instance data on the object. There are lots of possible ways to implement these this type of functionality that doesn't require the object to have a method that implements it. Or, it could be done with plain procedural functions where I call a function, pass it the object in question and that function examines the object and discerns its prototype with there being an actual method on the object. – jfriend00 Feb 07 '20 at 21:10
  • @jfriend00 `Object.getPrototypeOf()` is an API for internal behaviour `[[GetPrototypeOf]]` and what you are saying is possible algorithm for implementing it. Its not possible ways to implement it a behaviour - its about having a behaviour or not ! and still satisfying the spec overall object behaviour. I hope this gives some clarity. I think you are misunderstanding between - Public API , behaviour and their implementation. – Number945 Feb 07 '20 at 21:20
  • 1
    Yet again, you don't need a defined method or a function. If you need to use a specific internal method in multiple procedures you can literally just copy/paste the same code that implements the internal method behaviour as part of *different* methods. So, you've never defined an internal method, you've implemented the behaviour, though. So, I don't understand the insistence why an engine is required to define a method/function - it *isn't* as long as the behaviour is preserved. – VLAZ Feb 07 '20 at 21:20
  • @VLAZ that will be a bad design approach. Also read my comment above. May be that adds more clarity. – Number945 Feb 07 '20 at 21:22
  • 1
    Bad design assuming you do the copy/paste yourself in code. Yet again, in C/C++ you can just have a pre-process macro that does it for you automatically. So, your implementation has inlined scattered implementation of the same behaviour. It saves up on the extra call you would otherwise make, thus it's more optimised. It preserves the behaviour, thus it is up to the spec. – VLAZ Feb 07 '20 at 21:25

1 Answers1

3

V8 developer here. I think this question has mostly been answered already in the comments, so I'll just summarize.

Are internal slot and internal methods actually implemented by JavaScript engines?

Generally not; the engine simply behaves as if its internals were structured in this way. Some parts of an implementation might be very close to the spec's structure, if it's convenient.

One way to phrase it would be: you could implement a JavaScript engine by first faithfully translating the spec text to code (in whichever language you choose to use for your engine), and then you'd be allowed to refactor the invisible internals in any way you want (e.g.: inline functions, or split them up, or organize them as a helper class, or add a fast path or a cache, or generally turn the code inside out, etc). Which isn't surprising, really: as long as the observable behavior remains the same, any program is allowed to refactor its internals. What the ECMAScript is making clear at that point is simply that the "internal slots" really are guaranteed to always be internal and not observable.

[[[Get]] etc] are essential internal methods. If a JavaScript engine refuses to implement them, how does it achieve this functionality?

It's not about refusing to implement something. You can usually implement functionality in many different ways, i.e. with many different ways of structuring your code and your objects. Engines are free to structure their code and objects any way they want, as long as the resulting observable behavior is as specified.

Let us take an example of Object.getPrototypeOf(). This is an API for internal behaviour [[GetPrototypeOf]]

Not quite. Object.getPrototypeOf is a public function that's specified to behave in a certain way. The way the spec describes it is that it must *behave as if there were an internal slot [[GetPrototypeOf]].

You seem to have trouble imagining an alternative way. Well, in many cases, engines will probably choose to have an implementation that's very close to having those internal slots -- perhaps mapped to fields and methods in a C++ class. But it doesn't have to be that way; for example, instead of class methods, there could be free functions: GetPrototypeImpl(internal::Object object) rather than internal::Object::GetPrototypeImpl(). Or instead of an inheritance/hierarchy structure, the engine could use switch-statements over types.

One of the most common ways in which engines' implementations deviate from the structure defined by the spec's internal slots is by having additional fast paths. Typically, a fast path performs a few checks to see if it is applicable, and then does the simple, common case; if the applicability check fails, it falls back to a slower, more complete implementation, that might be much closer to the spec's structure. Or maybe neither function on its own contains the complete spec'ed behavior: you could have GetPrototypeFromRegularObject and GetPrototypeFromProxy plus a wrapper dispatching to the right one, and those all together behave like the spec's hypothetical system of having a [[GetPrototypeOf]] slot on both proxies and regular objects. All of that is perfectly okay because from the outside you can't see a difference in behavior -- all you can see is Object.getPrototypeOf.

One particular example of a fast path is a compiler. If you implemented object behaviors as (private) methods, and loaded and called those methods every time, then your implementation would be extremely slow. Modern engines compile JavaScript functions to bytecode or even machine code, and that code will behave as if you had loaded and called an internal function with the given behavior, but it (usually) will not actually call any such functions. For example, optimized code for an array[index] access should only be a few machine instructions (type check, bounds check, memory load), there should be no call to a [[Get]] involved.

Another very common example is object types. The spec typically uses wording like "if the object has a [[StringData]] internal slot, then ..."; an engine typically replaces that with "if the object's type is what I've chosen for representing strings internally, then ...". Again, the difference is not observable from the outside: Strings behave as if they had a [[StringData]] internal slot, but (in V8 at least) they don't have such a slot, they simply have an appropriate object type that identifies them as strings, and objects with string type know where their character payload is, they don't need any special slot for that.

Edit: forgot to mention: see also https://v8.dev/blog/understanding-ecmascript-part-1 for another way to explain it.

jmrk
  • 34,271
  • 7
  • 59
  • 74
  • Thnx for the answer. One correction in above answer `[[GetPrototypeOf]]` is internal method and not internal state as mentioned in answer. – Number945 Feb 08 '20 at 01:59
  • Also, one question : The ECMA spec tells us behaviour and states that every object in JS internally needs to have. But how does JS engine, say V8, decides which of these internal states/methods needs to be exposed to outside world. Ex : `[[GetPrototypeOf]]` is an internal method exposed to outside world by `Object.getPrototype()`. But what about other ? Who decides what JavaScript API will be exposed to outside world ? Are all internal states/methods(behaviour) exposed to outside world through some channel ?Is there any spec on that too ? – Number945 Feb 08 '20 at 02:02
  • @Number945 No, the spec *only* defines what exactly is exposed to the executed code. It defines exactly `Object.getPrototypeOf` to exist as a method on `Object`, and it uses its own spec framework with "internal slots and methods" to define the behaviour of those exposed parts. – Bergi Feb 08 '20 at 02:17
  • @Number945: Again, **internal slots and methods are not required to exist**, and they certainly don't whole-sale exist as some primordial law of nature. The spec says "`Object.getPrototypeof` must exist, and this is how it must behave: ...". How an engine chooses to implement that internally is completely up to the engine. So if you think "first there was the [[Whatever]] internal slot, and then it was exposed", then you got it backwards: first there was the requirement to have `Object.whatever`, and then engines decided how to implement that internally; there is no spec for the latter. – jmrk Feb 08 '20 at 15:38