CINXE.COM

JavaScript engine fundamentals: Shapes and Inline Caches · Mathias Bynens

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JavaScript engine fundamentals: Shapes and Inline Caches · Mathias Bynens</title> <meta name="viewport" content="width=device-width"> <link rel="stylesheet" href="/css"> <link rel="manifest" href="/.webmanifest"> <link rel="alternate" href="/notes/shapes-ics.atom" type="application/atom+xml" title="Atom feed for comments on this entry"> </head> <body> <header> <h1><a href="/">Mathias Bynens</a></h1> <nav> <h1>Navigation</h1> <ul> <li><a href="/">Home</a></li> <li><a href="/notes">Archive</a></li> </ul> </nav> </header> <section id="content"> <nav><a href="/notes/async-stack-traces" rel="prev">← Asynchronous stack traces: why <code>await</code> beats <code>Promise#then()</code></a><a href="/notes/prototypes" rel="next">JavaScript engine fundamentals: optimizing prototypes →</a></nav> <article itemscope itemtype="http://schema.org/BlogPosting"> <header> <h1 itemprop="headline">JavaScript engine fundamentals: Shapes and Inline Caches</h1> <p class="meta">Published <time datetime="2018-06-14" itemprop="datePublished">14th June 2018</time> · tagged with <a href="/notes#javascript">JavaScript</a>, <a href="/notes#performance">performance</a></p> </header> <div itemprop="articleBody"> <p>This article describes some key fundamentals that are common to all JavaScript engines — and not just <a href="https://twitter.com/v8js">V8</a>, the engine the authors (<a href="https://twitter.com/bmeurer">Benedikt</a> and <a href="https://twitter.com/mathias">Mathias</a>) work on. As a JavaScript developer, having a deeper understanding of how JavaScript engines work helps you reason about the performance characteristics of your code.</p> <p class="note"><strong>Note:</strong> If you prefer watching a presentation over reading articles, then enjoy the video below! If not, skip the video and read on.</p> <iframe class="youtube" width="600" height="337" src="https://www.youtube-nocookie.com/embed/5nmpokoRaZI" frameborder="0" allowfullscreen loading="lazy"></iframe> <h2 id="pipeline">The JavaScript engine pipeline</h2> <p>It all starts with the JavaScript code you write. The JavaScript engine parses the source code and turns it into an Abstract Syntax Tree (AST). Based on that AST, the interpreter can start to do its thing and produce bytecode. Great! At that point the engine is actually running the JavaScript code.</p> <p class="figure"><img src="/_img/js-engines/js-engine-pipeline.svg" loading="lazy" decoding="async" alt=""></p> <p>To make it run faster, the bytecode can be sent to the optimizing compiler along with profiling data. The optimizing compiler makes certain assumptions based on the profiling data it has, and then produces highly-optimized machine code.</p> <p>If at some point one of the assumptions turns out to be incorrect, the optimizing compiler deoptimizes and goes back to the interpreter.</p> <h3 id="pipeline-comparison">Interpreter/compiler pipelines in JavaScript engines</h3> <p>Now, let’s zoom in on the parts of this pipeline that actually run your JavaScript code, i.e. where code gets interpreted and optimized, and go over some of the differences between major JavaScript engines.</p> <p>Generally speaking, there’s a pipeline containing an interpreter and an optimizing compiler. The interpreter generates unoptimized bytecode quickly, and the optimizing compiler takes a little longer but eventually produces highly-optimized machine code.</p> <p class="figure"><img src="/_img/js-engines/interpreter-optimizing-compiler.svg" loading="lazy" decoding="async" alt=""></p> <p>This generic pipeline is pretty much exactly how V8, the JavaScript engine used in Chrome and Node.js, works:</p> <p class="figure"><img src="/_img/js-engines/interpreter-optimizing-compiler-v8.svg" loading="lazy" decoding="async" alt=""></p> <p>The interpreter in V8 is called Ignition, and is responsible for generating and executing bytecode. While it runs the bytecode, it collects profiling data, which can be used to speed up the execution later. When a function becomes <em>hot</em>, for example when it’s run often, the generated bytecode <strong>and</strong> the profiling data are passed on to TurboFan, our optimizing compiler, to generate highly-optimized machine code based on the profiling data.</p> <p class="figure"><img src="/_img/js-engines/interpreter-optimizing-compiler-spidermonkey.svg" loading="lazy" decoding="async" alt=""></p> <p>SpiderMonkey, Mozilla’s JavaScript engine as used in Firefox and in <a href="https://github.com/mozilla/spidernode">SpiderNode</a>, does it a little differently. They have not one but two optimizing compilers. The interpreter optimizes into the Baseline compiler, which produces somewhat optimized code. Combined with profiling data gathered while running the code, the IonMonkey compiler can produce heavily-optimized code. If the speculative optimization fails, IonMonkey falls back to the Baseline code.</p> <p class="figure"><img src="/_img/js-engines/interpreter-optimizing-compiler-chakra.svg" loading="lazy" decoding="async" alt=""></p> <p>Chakra, Microsoft’s JavaScript engine as used in Edge and <a href="https://github.com/nodejs/node-chakracore">Node-ChakraCore</a>, has a very similar setup with two optimizing compilers. The interpreter optimizes into SimpleJIT — where JIT stands for Just-In-Time compiler — which produces somewhat optimized code. Combined with profiling data, the FullJIT can produce more-heavily-optimized code.</p> <p class="figure"><img src="/_img/js-engines/interpreter-optimizing-compiler-jsc.svg" loading="lazy" decoding="async" alt=""></p> <p>JavaScriptCore (abbreviated as JSC), Apple’s JavaScript engine as used in Safari and React Native, takes it to the extreme with three different optimizing compilers. LLInt, the Low-Level Interpreter, optimizes into the Baseline compiler, which can then optimize into the DFG (Data Flow Graph) compiler, which can in turn optimize into the FTL (Faster Than Light) compiler.</p> <p>Why do some engines have more optimizing compilers than others? It’s all about trade-offs. An interpreter can produce bytecode quickly, but bytecode is generally not very efficient. An optimizing compiler on the other hand takes a little longer, but eventually produces much more efficient machine code. There is a trade-off between quickly getting code to run (interpreter) or taking some more time, but eventually running the code with optimal performance (optimizing compiler). Some engines choose to add multiple optimizing compilers with different time/efficiency characteristics, allowing for more fine-grained control over these trade-offs at the cost of additional complexity. Another trade-off relates to memory usage; see <a href="/notes/prototypes#tradeoffs">our follow-up article</a> for details on that.</p> <p>We’ve just highlighted the main differences in the interpreter and optimizing compiler pipelines for each JavaScript engine. But besides these differences, at a high level, <strong>all JavaScript engines have the same architecture</strong>: there’s a parser and some kind of interpreter/compiler pipeline.</p> <h2 id="object-model">JavaScript’s object model</h2> <p>Let’s look at what else JavaScript engines have in common by zooming in on how some aspects are implemented.</p> <p>For example, how do JavaScript engines implement the JavaScript object model, and which tricks do they use to speed up accessing properties on JavaScript objects? As it turns out, all major engines implement this very similarly.</p> <p>The ECMAScript specification essentially defines all objects as dictionaries, with string keys mapping to <em><a href="https://tc39.es/ecma262/#sec-property-attributes">property attributes</a></em>.</p> <p class="figure"><img src="/_img/js-engines/object-model.svg" loading="lazy" decoding="async" alt=""></p> <p>Other than the <code>[[Value]]</code> itself, the spec defines these properties:</p> <ul> <li><code>[[Writable]]</code> which determines whether the property can be reassigned to,</li> <li><code>[[Enumerable]]</code> which determines whether the property shows up in <code>for</code>-<code>in</code> loops,</li> <li>and <code>[[Configurable]]</code> which determines whether the property can be deleted.</li> </ul> <p>The <code>[[double square brackets]]</code> notation looks funky, but that’s just how the spec represents properties that aren’t directly exposed to JavaScript. You can still get to these property attributes for any given object and property in JavaScript by using the <code>Object.getOwnPropertyDescriptor</code> API:</p> <pre><code class="language-javascript">const object = { foo: 42 };<br>Object.getOwnPropertyDescriptor(object, 'foo');<br>// → { value: 42, writable: true, enumerable: true, configurable: true }</code></pre> <p>Ok, so that’s how JavaScript defines objects. What about arrays?</p> <p>You can think of arrays as a special case of objects. One difference is that arrays have special handling of array indices. Here <em>array index</em> is a special term in the ECMAScript specification. Arrays are limited to 2³²−1 items in JavaScript. An array index is any valid index within that limit, i.e. any integer number from 0 to 2³²−2.</p> <p>Another difference is that arrays also have a magical <code>length</code> property.</p> <pre><code class="language-javascript">const array = ['a', 'b'];<br>array.length; // → 2<br>array[2] = 'c';<br>array.length; // → 3</code></pre> <p>In this example, the array has a <code>length</code> of <code>2</code> when it’s created. Then we assign another element to index <code>2</code>, and the <code>length</code> automatically updates.</p> <p>JavaScript defines arrays similarly to objects. For example, all the keys including array indices are represented as strings explicitly. The first element in the array is stored under the key <code>'0'</code>.</p> <p class="figure"><img src="/_img/js-engines/array-1.svg" loading="lazy" decoding="async" alt=""></p> <p>The <code>'length'</code> property is just another property that happens to be non-enumerable and non-configurable.</p> <p>Once an element is added to the array, JavaScript automatically updates the <code>[[Value]]</code> property attribute of the <code>'length'</code> property.</p> <p class="figure"><img src="/_img/js-engines/array-2.svg" loading="lazy" decoding="async" alt=""></p> <p>Generally speaking, arrays behave pretty similarly to objects.</p> <h2 id="optimizing-property-access">Optimizing property access</h2> <p>Now that we know how objects are defined in JavaScript, let’s dive into how JavaScript engines enable working with objects efficiently.</p> <p>Looking at JavaScript programs in the wild, accessing properties is by far the most common operation. It’s crucial for JavaScript engines to make property access fast.</p> <pre><code class="language-javascript">const object = {<br> foo: 'bar',<br> baz: 'qux',<br>};<br><br>// Here, we’re accessing the property `foo` on `object`:<br>doSomething(object.foo);<br>// ^^^^^^^^^^</code></pre> <h3 id="shapes">Shapes</h3> <p>In JavaScript programs, it’s common to have multiple objects with the same property keys. Such objects have the same <em>shape</em>.</p> <pre><code class="language-javascript">const object1 = { x: 1, y: 2 };<br>const object2 = { x: 3, y: 4 };<br>// `object1` and `object2` have the same shape.</code></pre> <p>It’s also very common to access the same property on objects with the same shape:</p> <pre><code class="language-javascript">function logX(object) {<br> console.log(object.x);<br> // ^^^^^^^^<br>}<br><br>const object1 = { x: 1, y: 2 };<br>const object2 = { x: 3, y: 4 };<br><br>logX(object1);<br>logX(object2);</code></pre> <p>With that in mind, JavaScript engines can optimize object property access based on the object’s shape. Here’s how that works.</p> <p>Let’s assume we have an object with the properties <code>x</code> and <code>y</code>, and it uses the dictionary data structure we discussed earlier: it contains the keys as strings, and those point to their respective property attributes.</p> <p class="figure"><img src="/_img/js-engines/object-model.svg" loading="lazy" decoding="async" alt=""></p> <p>If you access a property, e.g. <code>object.y</code>, the JavaScript engine looks in the <code>JSObject</code> for the key <code>'y'</code>, then loads the corresponding property attributes, and finally returns the <code>[[Value]]</code>.</p> <p>But where are these property attributes stored in memory? Should we store them as part of the <code>JSObject</code>? If we assume that we’ll be seeing more objects with this shape later, then it’s wasteful to store the full dictionary containing the property names and attributes on the <code>JSObject</code> itself, as the property names are repeated for all objects with the same shape. That’s a lot of duplication and unnecessarily memory usage. As an optimization, engines store the <code>Shape</code> of the object separately.</p> <p class="figure"><img src="/_img/js-engines/shape-1.svg" loading="lazy" decoding="async" alt=""></p> <p>This <code>Shape</code> contains all the property names and the attributes, except for their <code>[[Value]]</code>s. Instead the <code>Shape</code> contains the offset of the values inside of the <code>JSObject</code>, so that the JavaScript engine knows where to find the values. Every <code>JSObject</code> with this same shape points to exactly this <code>Shape</code> instance. Now every <code>JSObject</code> only has to store the values that are unique to this object.</p> <p class="figure"><img src="/_img/js-engines/shape-2.svg" loading="lazy" decoding="async" alt=""></p> <p>The benefit becomes clear when we have multiple objects. No matter how many objects there are, as long as they have the same shape, we only have to store the shape and property information once!</p> <p>All JavaScript engines use shapes as an optimization, but they don’t all call them shapes:</p> <ul> <li>Academic papers call them <em>Hidden Classes</em> (confusing w.r.t. JavaScript classes)</li> <li>V8 calls them <em>Maps</em> (confusing w.r.t. JavaScript <code>Map</code>s)</li> <li>Chakra calls them <em>Types</em> (confusing w.r.t. JavaScript’s dynamic types and <code>typeof</code>)</li> <li>JavaScriptCore calls them <em>Structures</em></li> <li>SpiderMonkey calls them <em>Shapes</em></li> </ul> <p>Throughout this article, we’ll continue to use the term <em>shapes</em>.</p> <h3 id="transitions">Transition chains and trees</h3> <p>What happens if you have an object with a certain shape, but then you add a property to it? How does the JavaScript engine find the new shape?</p> <pre><code class="language-javascript">const object = {};<br>object.x = 5;<br>object.y = 6;</code></pre> <p>The shapes form so-called <em>transition chains</em> in the JavaScript engine. Here’s an example:</p> <p class="figure"><img src="/_img/js-engines/shape-chain-1.svg" loading="lazy" decoding="async" alt=""></p> <p>The object starts out without any properties, so it points to the empty shape. The next statement adds a property <code>'x'</code> with a value <code>5</code> to this object, so the JavaScript engine transitions to a shape that contains the property <code>'x'</code> and a value <code>5</code> is added to the <code>JSObject</code> at the first offset <code>0</code>. The next line adds a property <code>'y'</code>, so the engine transitions to yet another shape that contains both <code>'x'</code> and <code>'y'</code>, and appends the value <code>6</code> to the <code>JSObject</code> (at offset <code>1</code>).</p> <p class="note"><strong>Note:</strong> The order in which properties are added impacts the shape. For example, <code>{ x: 4, y: 5 }</code> results in a different shape than <code>{ y: 5, x: 4 }</code>.</p> <p>We don’t even need to store the full table of properties for each <code>Shape</code>. Instead, every <code>Shape</code> only needs to know about the new property it introduces. For example, in this case we don’t have to store the information about <code>'x'</code> in that last shape, because it can be found earlier in the chain. To make this work, every <code>Shape</code> links back to its previous shape:</p> <p class="figure"><img src="/_img/js-engines/shape-chain-2.svg" loading="lazy" decoding="async" alt=""></p> <p>If you write <code>o.x</code> in your JavaScript code, the JavaScript engine looks up the property <code>'x'</code> by walking up the transition chain until it finds the <code>Shape</code> that introduced property <code>'x'</code>.</p> <p>But what happens if there’s no way to create a transition chain? For example, what if you have two empty objects, and you add a different property to each of them?</p> <pre><code class="language-javascript">const object1 = {};<br>object1.x = 5;<br>const object2 = {};<br>object2.y = 6;</code></pre> <p>In that case we have to branch, and instead of a chain, we end up with a <em>transition tree</em>:</p> <p class="figure"><img src="/_img/js-engines/shape-tree.svg" loading="lazy" decoding="async" alt=""></p> <p>Here, we create an empty object <code>a</code>, and then add a property <code>'x'</code> to it. We end up with a <code>JSObject</code> containing a single value, and two <code>Shapes</code>: the empty shape, and the shape with only a property <code>x</code>.</p> <p>The second example starts with an empty object <code>b</code> too, but then adds a different property <code>'y'</code>. We end up with two shape chains, and a total of three shapes.</p> <p>Does that mean we always start at the empty shape? Not necessarily. Engines apply some optimizations for object literals that already contain properties. Let’s say we either add <code>x</code> starting from the empty object literal, or have an object literal that already contains <code>x</code>:</p> <pre><code class="language-javascript">const object1 = {};<br>object1.x = 5;<br>const object2 = { x: 6 };</code></pre> <p>In the first example, we start at the empty shape and transition to the shape that also contains <code>x</code>, just as we saw before.</p> <p>In the case of <code>object2</code>, it makes sense to directly produce objects that already have <code>x</code> from the beginning instead of starting from an empty object and transitioning.</p> <p class="figure"><img src="/_img/js-engines/empty-shape-bypass.svg" loading="lazy" decoding="async" alt=""></p> <p>The object literal that contains the property <code>'x'</code> starts at a shape that contains <code>'x'</code> from the beginning, effectively skipping the empty shape. This is what (at least) V8 and SpiderMonkey do. This optimization shortens the transition chains and makes it more efficient to construct objects from literals.</p> <p>Benedikt’s blog post on <a href="https://medium.com/@bmeurer/surprising-polymorphism-in-react-applications-63015b50abc">surprising polymorphism in React applications</a> discusses how these subtleties can affect real-world performance.</p> <p>Here’s an example of a 3D point object with properties <code>'x'</code>, <code>'y'</code>, and <code>'z'</code>.</p> <pre><code class="language-javascript">const point = {};<br>point.x = 4;<br>point.y = 5;<br>point.z = 6;</code></pre> <p>As we learned before, this creates an object with 3 shapes in memory (not counting the empty shape). To access the property <code>'x'</code> on that object, e.g. if you write <code>point.x</code> in your program, the JavaScript engine needs to follow the linked list: it starts at the <code>Shape</code> at the bottom, and then works its way up to the <code>Shape</code> that introduced <code>'x'</code> at the top.</p> <p class="figure"><img src="/_img/js-engines/shapetable-1.svg" loading="lazy" decoding="async" alt=""></p> <p>That’s going to be really slow if we do this more often, especially when the objects have lots of properties. The time to find the property is <code>O(n)</code>, i.e. linear in the number of properties on the object. To speed up searching for properties, JavaScript engines add a <code>ShapeTable</code> data structure. This <code>ShapeTable</code> is a dictionary, mapping property keys to the respective <code>Shape</code>s that introduce the given property.</p> <p class="figure"><img src="/_img/js-engines/shapetable-2.svg" loading="lazy" decoding="async" alt=""></p> <p>Wait a minute, now we’re back to dictionary lookups… That’s where we were before we started adding <code>Shape</code>s in the first place! So why do we bother with shapes at all?</p> <p>The reason is that shapes enable another optimization called <em>Inline Caches</em>.</p> <h3 id="ics">Inline Caches (ICs)</h3> <p>The main motivation behind shapes is the concept of Inline Caches or ICs. ICs are the key ingredient to making JavaScript run fast! JavaScript engines use ICs to memorize information on where to find properties on objects, to reduce the number of expensive lookups.</p> <p>Here’s a function <code>getX</code> that takes an object and loads the property <code>x</code> from it:</p> <pre><code class="language-javascript">function getX(o) {<br> return o.x;<br>}</code></pre> <p>If we run this function in JSC, it generates the following bytecode:</p> <p class="figure"><img src="/_img/js-engines/ic-1.svg" loading="lazy" decoding="async" alt="The generated bytecode is `get_by_id loc0, arg1, x`."></p> <p>The first <code>get_by_id</code> instruction loads the property <code>'x'</code> from the first argument (<code>arg1</code>) and stores the result into <code>loc0</code>. The second instruction returns what we stored to <code>loc0</code>.</p> <p>JSC also embeds an Inline Cache into the <code>get_by_id</code> instruction, which consists of two uninitialized slots.</p> <p class="figure"><img src="/_img/js-engines/ic-2.svg" loading="lazy" decoding="async" alt=""></p> <p>Now let’s assume we call <code>getX</code> with an object <code>{ x: 'a' }</code>. As we learned, this object has a shape with property <code>'x'</code> and the <code>Shape</code> stores the offset and attributes for that property <code>x</code>. When you execute the function for the first time, the <code>get_by_id</code> instruction looks up the property <code>'x'</code> and finds that the value is stored at offset <code>0</code>.</p> <p class="figure"><img src="/_img/js-engines/ic-3.svg" loading="lazy" decoding="async" alt=""></p> <p>The IC embedded into the <code>get_by_id</code> instruction memorizes the shape and the offset at which the property was found:</p> <p class="figure"><img src="/_img/js-engines/ic-4.svg" loading="lazy" decoding="async" alt=""></p> <p>For subsequent runs, the IC only needs to compare the shape, and if it’s the same as before, just load the value from the memorized offset. Specifically, if the JavaScript engine sees objects with a shape that the IC recorded before, it no longer needs to reach out to the property information at all — instead, the expensive property information lookup can be skipped entirely. That’s significantly faster than looking up the property each time.</p> <h2 id="arrays">Storing arrays efficiently</h2> <p>For arrays, it’s common to store properties that are <em>array indices</em>. The values for such properties are called array elements. It would be wasteful memory-wise to store property attributes for each and every array element in every single array. Instead, JavaScript engines use the fact that array-indexed properties are writable, enumerable, and configurable by default, and store array elements separately from other named properties.</p> <p>Consider this array:</p> <pre><code class="language-javascript">const array = [<br> '#jsconfeu',<br>];</code></pre> <p>The engine stores the array length (<code>1</code>), and points to the <code>Shape</code> that contains the offset and the attributes for the <code>'length'</code> property.</p> <p class="figure"><img src="/_img/js-engines/array-shape.svg" loading="lazy" decoding="async" alt=""></p> <p>This is similar to what we’ve seen before… but where are the array values stored?</p> <p class="figure"><img src="/_img/js-engines/array-elements.svg" loading="lazy" decoding="async" alt=""></p> <p>Every array has a separate <em>elements backing store</em> that contains all the array-indexed property values. The JavaScript engine doesn’t have to store any property attributes for array elements, because usually they are all writable, enumerable, and configurable.</p> <p>What happens in the unusual case, though? What if you change the property attributes of array elements?</p> <pre><code class="language-javascript">// Please don’t ever do this!<br>const array = Object.defineProperty(<br> [],<br> '0',<br> {<br> value: 'Oh noes!!1',<br> writable: false,<br> enumerable: false,<br> configurable: false,<br> }<br>);</code></pre> <p>The above snippet defines a property named <code>'0'</code> (which happens to be an array index), but sets it attributes to non-default values.</p> <p>In such edge cases, the JavaScript engine represents the <em>entire</em> elements backing store as a dictionary that maps array indices to property attributes.</p> <p class="figure"><img src="/_img/js-engines/array-dictionary-elements.svg" loading="lazy" decoding="async" alt=""></p> <p>Even when just a single array element has non-default attributes, the entire array’s backing store goes into this slow and inefficient mode. <strong>Avoid <code>Object.defineProperty</code> on array indices!</strong> (I’m not sure why you would even want to do this. It seems like a weird, non-useful thing to do.)</p> <h2 id="takeaways">Take-aways</h2> <p>We’ve learned how JavaScript engines store objects and arrays, and how Shapes and ICs help to optimize common operations on them. Based on this knowledge, we identified some practical JavaScript coding tips that can help boost performance:</p> <ul> <li>Always initialize your objects in the same way, so they don’t end up having different shapes.</li> <li>Don’t mess with property attributes of array elements, so they can be stored and operated on efficiently.</li> </ul> <h2 id="translations">Translations</h2> <ul> <li><a href="https://hijiangtao.github.io/2018/06/17/Shapes-ICs/">Chinese</a></li> <li><a href="https://shlrur.github.io/javascripts/javascript-engine-fundamentals-shapes-and-Inline-caches/">Korean</a></li> </ul> <p class="note"><strong>Note:</strong> This article was co-authored by <a href="https://twitter.com/bmeurer">Benedikt Meurer</a>.</p> <p class="note"><strong>Looking for more?</strong> If you’re interested in how JavaScript engines speed up accesses to prototype properties, read the next article in this series, <a href="/notes/prototypes">“JavaScript engine fundamentals: optimizing prototypes”</a>.</p> </div> <footer id="about"> <img src="https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=112" alt="" width="112" height="112" srcset="https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=224 2x"> <h2>About me</h2> <p>Hi there! I’m Mathias. I work on Chrome at Google. HTML, CSS, JavaScript, Unicode, performance, and security get me excited. <a href="https://twitter.com/mathias" rel="me nofollow">Follow me on Twitter</a>, <a href="https://bsky.app/profile/mths.be" rel="me nofollow">Bluesky</a>, and <a href="https://github.com/mathiasbynens" rel="me nofollow">GitHub</a>.</p> </footer> <section id="comments"> <h1>Comments</h1> <p>No comments yet.</p> </section> <section> <h1>Leave a comment</h1> <form method="post" action="/notes/shapes-ics"> <fieldset> <legend>Comment on “JavaScript engine fundamentals: Shapes and Inline Caches”</legend> <p> <label for="comment-name">Name <em>*</em></label> <input type="text" name="comment-name" id="comment-name" required> </p> <p> <label for="comment-mail">Email <em>*</em></label> <input type="email" name="comment-mail" id="comment-mail" required> </p> <p> <label for="comment-site">Website</label> <input type="url" name="comment-site" id="comment-site"> </p> <p><textarea rows="5" cols="50" name="comment-text" id="comment-text" required></textarea></p> <p>Your input will be parsed as Markdown.</p> <p> <label for="comment-spam">Spammer? (Enter ‘no’) <em>*</em></label> <input type="text" name="comment-spam" id="comment-spam" required> </p> <p class="submit"> <input type="submit" name="submit" value="Add your comment"> </p> </fieldset> </form> </section> </article> </section> <footer>© 1988–2025 Mathias Bynens</footer> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js" integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk" crossorigin></script> <script src="/js"></script> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10