Extend the SaneChain optimization to Contiguous arrays
authorfpizlo@apple.com <fpizlo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 9 May 2015 00:18:43 +0000 (00:18 +0000)
committerfpizlo@apple.com <fpizlo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 9 May 2015 00:18:43 +0000 (00:18 +0000)
https://bugs.webkit.org/show_bug.cgi?id=144664

Reviewed by Mark Lam.

Previously if you loaded from a hole, you'd either have to take slow path for the array
load (which means C++ calls and prototype chain walks) or you'd exit (if you hadn't
gathered the necessary profiling yet). But that's unnecessary if we know that the
prototype chain is sane - i.e. has no indexed properties. Then we can just return
Undefined for the hole.

Making this change requires setting more watchpoints on the array prototype chain. But
that hit a horrible bug: ArrayPrototype still uses the static lookup tables and builds
itself up lazily. This means that this increased the number of recompilations we'd get
due to the array prototype chain being built up.

So, this change also removes the laziness and static tables from ArrayPrototype.

But to make that change, I also had to add a helper for eagerly building up a prototype
that has builtin functions.

* CMakeLists.txt:
* DerivedSources.make:
* dfg/DFGArrayMode.h:
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLLowerDFGToLLVM.cpp:
(JSC::FTL::LowerDFGToLLVM::compileGetByVal):
* runtime/ArrayPrototype.cpp:
(JSC::ArrayPrototype::finishCreation):
(JSC::ArrayPrototype::getOwnPropertySlot): Deleted.
* runtime/ArrayPrototype.h:
* runtime/JSObject.h:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@184032 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/CMakeLists.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/DerivedSources.make
Source/JavaScriptCore/dfg/DFGArrayMode.h
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp
Source/JavaScriptCore/runtime/ArrayPrototype.cpp
Source/JavaScriptCore/runtime/ArrayPrototype.h
Source/JavaScriptCore/runtime/JSObject.h

index 88c997b..7ff0601 100644 (file)
@@ -605,7 +605,6 @@ list(APPEND JavaScriptCore_SOURCES
 set(JavaScriptCore_LUT_FILES
     runtime/ArrayConstructor.cpp
     runtime/ArrayIteratorPrototype.cpp
-    runtime/ArrayPrototype.cpp
     runtime/BooleanPrototype.cpp
     runtime/DateConstructor.cpp
     runtime/DatePrototype.cpp
index 8e3e69b..97d8dfa 100644 (file)
@@ -1,3 +1,43 @@
+2015-05-08  Filip Pizlo  <fpizlo@apple.com>
+
+        Extend the SaneChain optimization to Contiguous arrays
+        https://bugs.webkit.org/show_bug.cgi?id=144664
+
+        Reviewed by Mark Lam.
+        
+        Previously if you loaded from a hole, you'd either have to take slow path for the array
+        load (which means C++ calls and prototype chain walks) or you'd exit (if you hadn't
+        gathered the necessary profiling yet). But that's unnecessary if we know that the
+        prototype chain is sane - i.e. has no indexed properties. Then we can just return
+        Undefined for the hole.
+        
+        Making this change requires setting more watchpoints on the array prototype chain. But
+        that hit a horrible bug: ArrayPrototype still uses the static lookup tables and builds
+        itself up lazily. This means that this increased the number of recompilations we'd get
+        due to the array prototype chain being built up.
+        
+        So, this change also removes the laziness and static tables from ArrayPrototype.
+        
+        But to make that change, I also had to add a helper for eagerly building up a prototype
+        that has builtin functions.
+
+        * CMakeLists.txt:
+        * DerivedSources.make:
+        * dfg/DFGArrayMode.h:
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * ftl/FTLLowerDFGToLLVM.cpp:
+        (JSC::FTL::LowerDFGToLLVM::compileGetByVal):
+        * runtime/ArrayPrototype.cpp:
+        (JSC::ArrayPrototype::finishCreation):
+        (JSC::ArrayPrototype::getOwnPropertySlot): Deleted.
+        * runtime/ArrayPrototype.h:
+        * runtime/JSObject.h:
+
 2015-05-08  Michael Saboff  <msaboff@apple.com>
 
         Creating a large MarkedBlock sometimes results in more than one cell in the block
index b8bc25c..fba9106 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013 Apple Inc. All rights reserved.
+# Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013, 2015 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
@@ -37,7 +37,6 @@ VPATH = \
 all : \
     ArrayConstructor.lut.h \
     ArrayIteratorPrototype.lut.h \
-    ArrayPrototype.lut.h \
     BooleanPrototype.lut.h \
     DateConstructor.lut.h \
     DatePrototype.lut.h \
index e804bd7..a77c888 100644 (file)
@@ -89,6 +89,7 @@ enum Class {
 
 enum Speculation {
     SaneChain, // In bounds and the array prototype chain is still intact, i.e. loading a hole doesn't require special treatment.
+    
     InBounds, // In bounds and not loading a hole.
     ToHole, // Potentially storing to a hole.
     OutOfBounds // Out-of-bounds access and anything can happen.
index eead3fa..a3e0a02 100644 (file)
@@ -538,17 +538,61 @@ private:
             
             ArrayMode arrayMode = node->arrayMode();
             switch (arrayMode.type()) {
+            case Array::Contiguous:
             case Array::Double:
                 if (arrayMode.arrayClass() == Array::OriginalArray
                     && arrayMode.speculation() == Array::InBounds) {
                     JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic);
-                    if (globalObject->arrayPrototypeChainIsSane()
-                        && !(node->flags() & NodeBytecodeUsesAsOther)) {
-                        m_graph.watchpoints().addLazily(
-                            globalObject->arrayPrototype()->structure()->transitionWatchpointSet());
-                        m_graph.watchpoints().addLazily(
-                            globalObject->objectPrototype()->structure()->transitionWatchpointSet());
-                        node->setArrayMode(arrayMode.withSpeculation(Array::SaneChain));
+                    if (globalObject->arrayPrototypeChainIsSane()) {
+                        // Check if SaneChain will work on a per-type basis. Note that:
+                        //
+                        // 1) We don't want double arrays to sometimes return undefined, since
+                        // that would require a change to the return type and it would pessimise
+                        // things a lot. So, we'd only want to do that if we actually had
+                        // evidence that we could read from a hole. That's pretty annoying.
+                        // Likely the best way to handle that case is with an equivalent of
+                        // SaneChain for OutOfBounds. For now we just detect when Undefined and
+                        // NaN are indistinguishable according to backwards propagation, and just
+                        // use SaneChain in that case. This happens to catch a lot of cases.
+                        //
+                        // 2) We don't want int32 array loads to have to do a hole check just to
+                        // coerce to Undefined, since that would mean twice the checks.
+                        //
+                        // This has two implications. First, we have to do more checks than we'd
+                        // like. It's unfortunate that we have to do the hole check. Second,
+                        // some accesses that hit a hole will now need to take the full-blown
+                        // out-of-bounds slow path. We can fix that with:
+                        // https://bugs.webkit.org/show_bug.cgi?id=144668
+                        
+                        bool canDoSaneChain = false;
+                        switch (arrayMode.type()) {
+                        case Array::Contiguous:
+                            // This is happens to be entirely natural. We already would have
+                            // returned any JSValue, and now we'll return Undefined. We still do
+                            // the check but it doesn't require taking any kind of slow path.
+                            canDoSaneChain = true;
+                            break;
+                            
+                        case Array::Double:
+                            if (!(node->flags() & NodeBytecodeUsesAsOther)) {
+                                // Holes look like NaN already, so if the user doesn't care
+                                // about the difference between Undefined and NaN then we can
+                                // do this.
+                                canDoSaneChain = true;
+                            }
+                            break;
+                            
+                        default:
+                            break;
+                        }
+                        
+                        if (canDoSaneChain) {
+                            m_graph.watchpoints().addLazily(
+                                globalObject->arrayPrototype()->structure()->transitionWatchpointSet());
+                            m_graph.watchpoints().addLazily(
+                                globalObject->objectPrototype()->structure()->transitionWatchpointSet());
+                            node->setArrayMode(arrayMode.withSpeculation(Array::SaneChain));
+                        }
                     }
                 }
                 break;
index 1d0dda7..66785b1 100644 (file)
@@ -2309,21 +2309,46 @@ void SpeculativeJIT::compile(Node* node)
             
                 GPRTemporary resultPayload(this);
                 if (node->arrayMode().type() == Array::Int32) {
+                    ASSERT(!node->arrayMode().isSaneChain());
+                    
                     speculationCheck(
                         OutOfBounds, JSValueRegs(), 0,
                         m_jit.branch32(
                             MacroAssembler::Equal,
-                            MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesEight, OBJECT_OFFSETOF(JSValue, u.asBits.tag)),
+                            MacroAssembler::BaseIndex(
+                                storageReg, propertyReg, MacroAssembler::TimesEight, TagOffset),
                             TrustedImm32(JSValue::EmptyValueTag)));
-                    m_jit.load32(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesEight, OBJECT_OFFSETOF(JSValue, u.asBits.payload)), resultPayload.gpr());
+                    m_jit.load32(
+                        MacroAssembler::BaseIndex(
+                            storageReg, propertyReg, MacroAssembler::TimesEight, PayloadOffset),
+                        resultPayload.gpr());
                     int32Result(resultPayload.gpr(), node);
                     break;
                 }
                 
                 GPRTemporary resultTag(this);
-                m_jit.load32(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesEight, OBJECT_OFFSETOF(JSValue, u.asBits.tag)), resultTag.gpr());
-                speculationCheck(LoadFromHole, JSValueRegs(), 0, m_jit.branch32(MacroAssembler::Equal, resultTag.gpr(), TrustedImm32(JSValue::EmptyValueTag)));
-                m_jit.load32(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesEight, OBJECT_OFFSETOF(JSValue, u.asBits.payload)), resultPayload.gpr());
+                m_jit.load32(
+                    MacroAssembler::BaseIndex(
+                        storageReg, propertyReg, MacroAssembler::TimesEight, TagOffset),
+                    resultTag.gpr());
+                m_jit.load32(
+                    MacroAssembler::BaseIndex(
+                        storageReg, propertyReg, MacroAssembler::TimesEight, PayloadOffset),
+                    resultPayload.gpr());
+                if (node->arrayMode().isSaneChain()) {
+                    JITCompiler::Jump notHole = m_jit.branch32(
+                        MacroAssembler::NotEqual, resultTag.gpr(),
+                        TrustedImm32(JSValue::EmptyValueTag));
+                    m_jit.move(TrustedImm32(JSValue::UndefinedTag), resultTag.gpr());
+                    m_jit.move(TrustedImm32(0), resultPayload.gpr());
+                    notHole.link(&m_jit);
+                } else {
+                    speculationCheck(
+                        LoadFromHole, JSValueRegs(), 0,
+                        m_jit.branch32(
+                            MacroAssembler::Equal, resultTag.gpr(),
+                            TrustedImm32(JSValue::EmptyValueTag)));
+                }
                 jsValueResult(resultTag.gpr(), resultPayload.gpr(), node);
                 break;
             }
index b770b50..2a736be 100644 (file)
@@ -2447,7 +2447,17 @@ void SpeculativeJIT::compile(Node* node)
                 
                 GPRTemporary result(this);
                 m_jit.load64(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesEight), result.gpr());
-                speculationCheck(LoadFromHole, JSValueRegs(), 0, m_jit.branchTest64(MacroAssembler::Zero, result.gpr()));
+                if (node->arrayMode().isSaneChain()) {
+                    ASSERT(node->arrayMode().type() == Array::Contiguous);
+                    JITCompiler::Jump notHole = m_jit.branchTest64(
+                        MacroAssembler::NonZero, result.gpr());
+                    m_jit.move(TrustedImm64(JSValue::encode(jsUndefined())), result.gpr());
+                    notHole.link(&m_jit);
+                } else {
+                    speculationCheck(
+                        LoadFromHole, JSValueRegs(), 0,
+                        m_jit.branchTest64(MacroAssembler::Zero, result.gpr()));
+                }
                 jsValueResult(result.gpr(), node, node->arrayMode().type() == Array::Int32 ? DataFormatJSInt32 : DataFormatJS);
                 break;
             }
index b91e9c1..4a837c7 100644 (file)
@@ -2263,7 +2263,14 @@ private:
             
             if (m_node->arrayMode().isInBounds()) {
                 LValue result = m_out.load64(baseIndex(heap, storage, index, m_node->child2()));
-                speculate(LoadFromHole, noValue(), 0, m_out.isZero64(result));
+                LValue isHole = m_out.isZero64(result);
+                if (m_node->arrayMode().isSaneChain()) {
+                    DFG_ASSERT(
+                        m_graph, m_node, m_node->arrayMode().type() == Array::Contiguous);
+                    result = m_out.select(
+                        isHole, m_out.constInt64(JSValue::encode(jsUndefined())), result);
+                } else
+                    speculate(LoadFromHole, noValue(), 0, isHole);
                 setJSValue(result);
                 return;
             }
index 4077743..07be2a9 100644 (file)
 #include "Interpreter.h"
 #include "JIT.h"
 #include "JSArrayIterator.h"
+#include "JSCBuiltins.h"
+#include "JSCInlines.h"
 #include "JSStringBuilder.h"
 #include "JSStringJoiner.h"
 #include "Lookup.h"
 #include "ObjectConstructor.h"
 #include "ObjectPrototype.h"
-#include "JSCInlines.h"
 #include "StringRecursionChecker.h"
 #include <algorithm>
 #include <wtf/Assertions.h>
@@ -63,47 +64,9 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncLastIndexOf(ExecState*);
 EncodedJSValue JSC_HOST_CALL arrayProtoFuncKeys(ExecState*);
 EncodedJSValue JSC_HOST_CALL arrayProtoFuncEntries(ExecState*);
 
-}
-
-#include "ArrayPrototype.lut.h"
-
-namespace JSC {
-
 // ------------------------------ ArrayPrototype ----------------------------
 
-const ClassInfo ArrayPrototype::s_info = {"Array", &JSArray::s_info, &arrayPrototypeTable, CREATE_METHOD_TABLE(ArrayPrototype)};
-
-/* Source for ArrayPrototype.lut.h
-@begin arrayPrototypeTable 16
-  toString       arrayProtoFuncToString       DontEnum|Function 0
-  toLocaleString arrayProtoFuncToLocaleString DontEnum|Function 0
-  concat         arrayProtoFuncConcat         DontEnum|Function 1
-  fill           arrayProtoFuncFill           DontEnum|Function 1
-  join           arrayProtoFuncJoin           DontEnum|Function 1
-  pop            arrayProtoFuncPop            DontEnum|Function 0
-  push           arrayProtoFuncPush           DontEnum|Function 1
-  reverse        arrayProtoFuncReverse        DontEnum|Function 0
-  shift          arrayProtoFuncShift          DontEnum|Function 0
-  slice          arrayProtoFuncSlice          DontEnum|Function 2
-  sort           arrayProtoFuncSort           DontEnum|Function 1
-  splice         arrayProtoFuncSplice         DontEnum|Function 2
-  unshift        arrayProtoFuncUnShift        DontEnum|Function 1
-  every          arrayProtoFuncEvery          DontEnum|Function 1
-  forEach        arrayProtoFuncForEach        DontEnum|Function 1
-  some           arrayProtoFuncSome           DontEnum|Function 1
-  indexOf        arrayProtoFuncIndexOf        DontEnum|Function 1
-  lastIndexOf    arrayProtoFuncLastIndexOf    DontEnum|Function 1
-  filter         arrayProtoFuncFilter         DontEnum|Function 1
-  reduce         arrayProtoFuncReduce         DontEnum|Function 1
-  reduceRight    arrayProtoFuncReduceRight    DontEnum|Function 1
-  map            arrayProtoFuncMap            DontEnum|Function 1
-  entries        arrayProtoFuncEntries        DontEnum|Function 0
-  keys           arrayProtoFuncKeys           DontEnum|Function 0
-  find           arrayProtoFuncFind           DontEnum|Function 1
-  findIndex      arrayProtoFuncFindIndex      DontEnum|Function 1
-  includes       arrayProtoFuncIncludes       DontEnum|Function 1
-@end
-*/
+const ClassInfo ArrayPrototype::s_info = {"Array", &JSArray::s_info, nullptr, CREATE_METHOD_TABLE(ArrayPrototype)};
 
 ArrayPrototype* ArrayPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
 {
@@ -126,7 +89,35 @@ void ArrayPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
 
     putDirectWithoutTransition(vm, vm.propertyNames->values, globalObject->arrayProtoValuesFunction(), DontEnum);
     putDirectWithoutTransition(vm, vm.propertyNames->iteratorSymbol, globalObject->arrayProtoValuesFunction(), DontEnum);
-
+    
+    JSC_NATIVE_FUNCTION(vm.propertyNames->toString, arrayProtoFuncToString, DontEnum, 0);
+    JSC_NATIVE_FUNCTION(vm.propertyNames->toLocaleString, arrayProtoFuncToLocaleString, DontEnum, 0);
+    JSC_NATIVE_FUNCTION("concat", arrayProtoFuncConcat, DontEnum, 1);
+    JSC_BUILTIN_FUNCTION("fill", arrayPrototypeFillCodeGenerator, DontEnum);
+    JSC_NATIVE_FUNCTION(vm.propertyNames->join, arrayProtoFuncJoin, DontEnum, 1);
+    JSC_NATIVE_INTRINSIC_FUNCTION("pop", arrayProtoFuncPop, DontEnum, 0, ArrayPopIntrinsic);
+    JSC_NATIVE_INTRINSIC_FUNCTION("push", arrayProtoFuncPush, DontEnum, 1, ArrayPushIntrinsic);
+    JSC_NATIVE_FUNCTION("reverse", arrayProtoFuncReverse, DontEnum, 0);
+    JSC_NATIVE_FUNCTION("shift", arrayProtoFuncShift, DontEnum, 0);
+    JSC_NATIVE_FUNCTION(vm.propertyNames->slice, arrayProtoFuncSlice, DontEnum, 2);
+    JSC_BUILTIN_FUNCTION("sort", arrayPrototypeSortCodeGenerator, DontEnum);
+    JSC_NATIVE_FUNCTION("splice", arrayProtoFuncSplice, DontEnum, 2);
+    JSC_NATIVE_FUNCTION("unshift", arrayProtoFuncUnShift, DontEnum, 1);
+    JSC_BUILTIN_FUNCTION("every", arrayPrototypeEveryCodeGenerator, DontEnum);
+    JSC_BUILTIN_FUNCTION("forEach", arrayPrototypeForEachCodeGenerator, DontEnum);
+    JSC_BUILTIN_FUNCTION("some", arrayPrototypeSomeCodeGenerator, DontEnum);
+    JSC_NATIVE_FUNCTION("indexOf", arrayProtoFuncIndexOf, DontEnum, 1);
+    JSC_NATIVE_FUNCTION("lastIndexOf", arrayProtoFuncLastIndexOf, DontEnum, 1);
+    JSC_BUILTIN_FUNCTION("filter", arrayPrototypeFilterCodeGenerator, DontEnum);
+    JSC_NATIVE_FUNCTION("reduce", arrayProtoFuncReduce, DontEnum, 1);
+    JSC_NATIVE_FUNCTION("reduceRight", arrayProtoFuncReduceRight, DontEnum, 1);
+    JSC_BUILTIN_FUNCTION("map", arrayPrototypeMapCodeGenerator, DontEnum);
+    JSC_NATIVE_FUNCTION(vm.propertyNames->entries, arrayProtoFuncEntries, DontEnum, 0);
+    JSC_NATIVE_FUNCTION(vm.propertyNames->keys, arrayProtoFuncKeys, DontEnum, 0);
+    JSC_BUILTIN_FUNCTION("find", arrayPrototypeFindCodeGenerator, DontEnum);
+    JSC_BUILTIN_FUNCTION("findIndex", arrayPrototypeFindIndexCodeGenerator, DontEnum);
+    JSC_BUILTIN_FUNCTION("includes", arrayPrototypeIncludesCodeGenerator, DontEnum);
+    
     if (!globalObject->runtimeFlags().isSymbolDisabled()) {
         JSObject* unscopables = constructEmptyObject(globalObject->globalExec(), globalObject->nullPrototypeObjectStructure());
         const char* unscopableNames[] = {
@@ -144,11 +135,6 @@ void ArrayPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
     }
 }
 
-bool ArrayPrototype::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
-{
-    return getStaticFunctionSlot<JSArray>(exec, arrayPrototypeTable, jsCast<ArrayPrototype*>(object), propertyName, slot);
-}
-
 // ------------------------------ Array Functions ----------------------------
 
 // Helper function
index 3fde64d..39412c2 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
- *  Copyright (C) 2007, 2011 Apple Inc. All rights reserved.
+ *  Copyright (C) 2007, 2011, 2015 Apple Inc. All rights reserved.
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public
@@ -35,8 +35,6 @@ public:
 
     static ArrayPrototype* create(VM&, JSGlobalObject*, Structure*);
         
-    static bool getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot&);
-
     DECLARE_INFO;
 
     static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
index 651f045..445bb52 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
- *  Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2012, 2013, 2014 Apple Inc. All rights reserved.
+ *  Copyright (C) 2003-2009, 2012-2015 Apple Inc. All rights reserved.
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Library General Public
@@ -1557,6 +1557,12 @@ ALWAYS_INLINE Identifier makeIdentifier(VM&, const Identifier& name)
 #define JSC_NATIVE_FUNCTION(jsName, cppName, attributes, length) \
     JSC_NATIVE_INTRINSIC_FUNCTION(jsName, cppName, (attributes), (length), NoIntrinsic)
 
+// Identical helpers but for builtins. Note that currently, we don't support builtins that are
+// also intrinsics, but we probably will do that eventually.
+#define JSC_BUILTIN_FUNCTION(jsName, generatorName, attributes) \
+    putDirectBuiltinFunction(\
+        vm, globalObject, makeIdentifier(vm, (jsName)), (generatorName)(vm), (attributes))
+
 } // namespace JSC
 
 #endif // JSObject_h