Ignore:
Timestamp:
Jan 31, 2014, 1:34:38 PM (12 years ago)
Author:
[email protected]
Message:

Make it possible to implement JS builtins in JS
https://bugs.webkit.org/show_bug.cgi?id=127887

Reviewed by Michael Saboff.

.:

CMake updates

  • Source/cmake/gtest/CMakeLists.txt:

Source/JavaScriptCore:

This patch makes it possible to write builtin functions in JS.
The bindings, generators, and definitions are all created automatically
based on js files in the builtins/ directory. This patch includes one
such case: Array.prototype.js with an implementation of every().

There's a lot of refactoring to make it possible for CommonIdentifiers
to include the output of the generated files (DerivedSources/JSCBuiltins.{h,cpp})
without breaking the offset extractor. The result of this refactoring
is that CommonIdentifiers, and a few other miscellaneous headers now
need to be included directly as they were formerly captured through other
paths.

In addition this adds a flag to the Lookup table's hashentry to indicate
that a static function is actually backed by JS. There is then a lot of
logic to thread the special nature of the functon to where it matters.
This allows toString(), .caller, etc to mimic the behaviour of a host
function.

Notes on writing builtins:

  • Each function is compiled independently of the others, and those implementations cannot currently capture all global properties (as that could be potentially unsafe). If a function does capture a global we will deliberately crash.
  • For those "global" properties that we do want access to, we use the @ prefix, e.g. Object(this) becomes @Object(this). The @ identifiers are private names, and behave just like regular properties, only without the risk of adulteration. Again, in the @Object case, we explicitly duplicate the ObjectConstructor reference on the GlobalObject so that we have guaranteed access to the original version of the constructor.
  • call, apply, eval, and Function are all rejected identifiers, again to prevent anything from accidentally using an adulterated object. Instead @call and @apply are available, and happily they completely drop the neq_ptr instruction as they're defined as always being the original call/apply functions.

These restrictions are just intended to make it harder to accidentally
make changes that are incorrect (for instance calling whatever has been
assigned to global.Object, instead of the original constructor function).
However, making a mistake like this should result in a purely semantic
error as fundamentally these functions are treated as though they were
regular JS code in the host global, and have no more privileges than
any other JS.

The initial proof of concept is Array.prototype.every, this shows a 65%
performance improvement, and that improvement is significantly hurt by
our poor optimisation of op_in.

As this is such a limited function, we have not yet exported all symbols
that we could possibly need, but as we implement more, the likelihood
of encountering missing features will reduce.

This did require breaking out a JSStringInlines header, and required
fixing a few objects that were trying to using PropertyName::publicName
rather than PropertyName::uid.

  • API/JSCallbackObjectFunctions.h:

(JSC::JSCallbackObject<Parent>::getOwnPropertySlot):
(JSC::JSCallbackObject<Parent>::put):
(JSC::JSCallbackObject<Parent>::deleteProperty):
(JSC::JSCallbackObject<Parent>::getStaticValue):
(JSC::JSCallbackObject<Parent>::staticFunctionGetter):
(JSC::JSCallbackObject<Parent>::callbackGetter):

  • CMakeLists.txt:
  • DerivedSources.make:
  • GNUmakefile.list.am:
  • JavaScriptCore.vcxproj/JavaScriptCore.vcxproj:
  • JavaScriptCore.vcxproj/JavaScriptCore.vcxproj.filters:
  • JavaScriptCore.xcodeproj/project.pbxproj:
  • builtins/Array.prototype.js:

(every):

  • builtins/BuiltinExecutables.cpp: Added.

(JSC::BuiltinExecutables::BuiltinExecutables):
(JSC::BuiltinExecutables::createBuiltinExecutable):

  • builtins/BuiltinExecutables.h:

(JSC::BuiltinExecutables::create):

  • bytecode/CodeBlock.cpp:

(JSC::CodeBlock::CodeBlock):

  • bytecode/CodeBlock.h:
  • bytecode/ProfiledCodeBlockJettisoningWatchpoint.cpp:
  • bytecode/UnlinkedCodeBlock.cpp:

(JSC::generateFunctionCodeBlock):
(JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable):
(JSC::UnlinkedFunctionExecutable::codeBlockFor):
(JSC::UnlinkedCodeBlock::UnlinkedCodeBlock):

  • bytecode/UnlinkedCodeBlock.h:

(JSC::ExecutableInfo::ExecutableInfo):
(JSC::UnlinkedFunctionExecutable::create):
(JSC::UnlinkedFunctionExecutable::toStrictness):
(JSC::UnlinkedFunctionExecutable::isBuiltinFunction):
(JSC::UnlinkedCodeBlock::isBuiltinFunction):

  • bytecompiler/BytecodeGenerator.cpp:

(JSC::BytecodeGenerator::BytecodeGenerator):

  • bytecompiler/BytecodeGenerator.h:

(JSC::BytecodeGenerator::isBuiltinFunction):
(JSC::BytecodeGenerator::makeFunction):

  • bytecompiler/NodesCodegen.cpp:

(JSC::CallFunctionCallDotNode::emitBytecode):
(JSC::ApplyFunctionCallDotNode::emitBytecode):

  • create_hash_table:
  • dfg/DFGOperations.cpp:
  • generate-js-builtins: Added.

(getCopyright):
(getFunctions):
(generateCode):
(mangleName):
(FunctionExecutable):
(Identifier):
(JSGlobalObject):
(SourceCode):
(UnlinkedFunctionExecutable):
(VM):

  • interpreter/Interpreter.cpp:
  • interpreter/ProtoCallFrame.cpp:
  • jit/JITOpcodes.cpp:
  • jit/JITOpcodes32_64.cpp:
  • jit/JITOperations.cpp:
  • jit/JITPropertyAccess.cpp:
  • jit/JITPropertyAccess32_64.cpp:
  • jsc.cpp:
  • llint/LLIntSlowPaths.cpp:
  • parser/ASTBuilder.h:

(JSC::ASTBuilder::makeFunctionCallNode):

  • parser/Lexer.cpp:

(JSC::Lexer<T>::Lexer):
(JSC::isSafeIdentifier):
(JSC::Lexer<LChar>::parseIdentifier):
(JSC::Lexer<UChar>::parseIdentifier):
(JSC::Lexer<T>::lex):

  • parser/Lexer.h:

(JSC::isSafeIdentifier):
(JSC::Lexer<T>::lexExpectIdentifier):

  • parser/Nodes.cpp:

(JSC::ProgramNode::setClosedVariables):

  • parser/Nodes.h:

(JSC::ScopeNode::capturedVariables):
(JSC::ScopeNode::setClosedVariables):
(JSC::ProgramNode::closedVariables):

  • parser/Parser.cpp:

(JSC::Parser<LexerType>::Parser):
(JSC::Parser<LexerType>::parseInner):
(JSC::Parser<LexerType>::didFinishParsing):
(JSC::Parser<LexerType>::printUnexpectedTokenText):

  • parser/Parser.h:

(JSC::Scope::getUsedVariables):
(JSC::Parser::closedVariables):
(JSC::parse):

  • parser/ParserModes.h:
  • parser/ParserTokens.h:
  • runtime/ArgList.cpp:
  • runtime/Arguments.cpp:
  • runtime/Arguments.h:
  • runtime/ArgumentsIteratorConstructor.cpp:
  • runtime/ArgumentsIteratorPrototype.cpp:
  • runtime/ArrayPrototype.cpp:
  • runtime/CodeCache.cpp:

(JSC::CodeCache::getFunctionExecutableFromGlobalCode):

  • runtime/CommonIdentifiers.cpp:

(JSC::CommonIdentifiers::CommonIdentifiers):
(JSC::CommonIdentifiers::getPrivateName):
(JSC::CommonIdentifiers::getPublicName):

  • runtime/CommonIdentifiers.h:
  • runtime/CommonSlowPaths.cpp:
  • runtime/CommonSlowPathsExceptions.cpp:
  • runtime/ExceptionHelpers.cpp:

(JSC::createUndefinedVariableError):

  • runtime/Executable.h:

(JSC::EvalExecutable::executableInfo):
(JSC::ProgramExecutable::executableInfo):
(JSC::FunctionExecutable::isBuiltinFunction):

  • runtime/FunctionPrototype.cpp:

(JSC::functionProtoFuncToString):

  • runtime/JSActivation.cpp:

(JSC::JSActivation::symbolTableGet):
(JSC::JSActivation::symbolTablePut):
(JSC::JSActivation::symbolTablePutWithAttributes):

  • runtime/JSArgumentsIterator.cpp:
  • runtime/JSArray.cpp:
  • runtime/JSArrayIterator.cpp:
  • runtime/JSCJSValue.cpp:
  • runtime/JSCellInlines.h:
  • runtime/JSFunction.cpp:

(JSC::JSFunction::createBuiltinFunction):
(JSC::JSFunction::calculatedDisplayName):
(JSC::JSFunction::sourceCode):
(JSC::JSFunction::isHostOrBuiltinFunction):
(JSC::JSFunction::isBuiltinFunction):
(JSC::JSFunction::callerGetter):
(JSC::JSFunction::getOwnPropertySlot):
(JSC::JSFunction::getOwnNonIndexPropertyNames):
(JSC::JSFunction::put):
(JSC::JSFunction::defineOwnProperty):

  • runtime/JSFunction.h:
  • runtime/JSGenericTypedArrayViewConstructorInlines.h:
  • runtime/JSGenericTypedArrayViewInlines.h:
  • runtime/JSGenericTypedArrayViewPrototypeInlines.h:
  • runtime/JSGlobalObject.cpp:

(JSC::JSGlobalObject::reset):
(JSC::JSGlobalObject::visitChildren):

  • runtime/JSGlobalObject.h:

(JSC::JSGlobalObject::objectConstructor):
(JSC::JSGlobalObject::symbolTableHasProperty):

  • runtime/JSObject.cpp:

(JSC::getClassPropertyNames):
(JSC::JSObject::reifyStaticFunctionsForDelete):
(JSC::JSObject::putDirectBuiltinFunction):

  • runtime/JSObject.h:
  • runtime/JSPropertyNameIterator.cpp:
  • runtime/JSPropertyNameIterator.h:
  • runtime/JSString.h:
  • runtime/JSStringInlines.h: Added.

(JSC::JSString::getStringPropertySlot):
(JSC::inlineJSValueNotStringtoString):
(JSC::JSValue::toWTFStringInline):

  • runtime/JSSymbolTableObject.cpp:

(JSC::JSSymbolTableObject::getOwnNonIndexPropertyNames):

Don't report private names.

  • runtime/JSSymbolTableObject.h:

(JSC::symbolTableGet):
(JSC::symbolTablePut):
(JSC::symbolTablePutWithAttributes):

  • runtime/Lookup.cpp:

(JSC::setUpStaticFunctionSlot):

  • runtime/Lookup.h:

(JSC::HashEntry::builtinGenerator):
(JSC::HashEntry::propertyGetter):
(JSC::HashEntry::propertyPutter):
(JSC::HashTable::entry):
(JSC::getStaticPropertySlot):
(JSC::getStaticValueSlot):
(JSC::putEntry):

  • runtime/NativeErrorConstructor.cpp:

(JSC::NativeErrorConstructor::finishCreation):

  • runtime/NativeErrorConstructor.h:
  • runtime/PropertySlot.h:
  • runtime/RegExpPrototype.cpp:
  • runtime/SetConstructor.cpp:
  • runtime/StringObject.cpp:
  • runtime/Structure.cpp:
  • runtime/VM.cpp:

(JSC::VM::VM):

  • runtime/VM.h:

(JSC::VM::builtinExecutables):

Source/WebCore:

Updating for the newly required headers.

Test: js/regress/array-prototype-every.html

  • ForwardingHeaders/runtime/JSStringInlines.h: Added.
  • Modules/plugins/QuickTimePluginReplacement.cpp:
  • bindings/js/JSIDBAnyCustom.cpp:
  • bindings/js/JSIDBDatabaseCustom.cpp:
  • bindings/js/JSIDBObjectStoreCustom.cpp:

Source/WebKit:

CMake updates

  • CMakeLists.txt:

Source/WebKit2:

CMake updates

  • CMakeLists.txt:

Tools:

CMake updates

  • DumpRenderTree/CMakeLists.txt:
  • WebKitTestRunner/CMakeLists.txt:
  • WinCELauncher/CMakeLists.txt:

LayoutTests:

Updated the test results for new error messages (now that they're
actually helpful), and added a js-regress test to track performance.

  • js/array-every-expected.txt:
  • js/dom/array-prototype-properties-expected.txt:
  • js/regress/array-prototype-every-expected.txt: Added.
  • js/regress/array-prototype-every.html: Added.
  • js/regress/script-tests/array-prototype-every.js: Added.

(test1):
(test2):
(test3):

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/parser/Lexer.cpp

    r162906 r163195  
    2626#include "Lexer.h"
    2727
     28#include "CommonIdentifiers.h"
     29#include "Identifier.h"
    2830#include "JSFunctionInlines.h"
    29 
    3031#include "JSGlobalObjectFunctions.h"
    31 #include "Identifier.h"
    3232#include "NodeInfo.h"
    3333#include "Nodes.h"
     
    9191    // Other types (only one so far)
    9292    CharacterWhiteSpace,
     93    CharacterPrivateIdentifierStart
    9394};
    9495
     
    159160/*  62 - >                  */ CharacterGreater,
    160161/*  63 - ?                  */ CharacterQuestion,
    161 /*  64 - @                  */ CharacterInvalid,
     162/*  64 - @                  */ CharacterPrivateIdentifierStart,
    162163/*  65 - A                  */ CharacterIdentifierStart,
    163164/*  66 - B                  */ CharacterIdentifierStart,
     
    487488
    488489template <typename T>
    489 Lexer<T>::Lexer(VM* vm)
     490Lexer<T>::Lexer(VM* vm, JSParserStrictness strictness)
    490491    : m_isReparsing(false)
    491492    , m_vm(vm)
     493    , m_parsingBuiltinFunction(strictness == JSParseBuiltin)
    492494{
    493495}
     
    754756    m_buffer16.append(static_cast<UChar>(c));
    755757}
    756 
     758   
     759#if !ASSERT_DISABLED
     760bool isSafeIdentifier(VM& vm, const Identifier* ident)
     761{
     762    if (!ident)
     763        return true;
     764    /* Just block any use of suspicious identifiers.  This is intended to
     765     * be used as a safety net while implementing builtins.
     766     */
     767    if (*ident == vm.propertyNames->call)
     768        return false;
     769    if (*ident == vm.propertyNames->apply)
     770        return false;
     771    if (*ident == vm.propertyNames->eval)
     772        return false;
     773    if (*ident == vm.propertyNames->Function)
     774        return false;
     775    return true;
     776}
     777#endif
     778   
    757779template <>
    758780template <bool shouldCreateIdentifier> ALWAYS_INLINE JSTokenType Lexer<LChar>::parseIdentifier(JSTokenData* tokenData, unsigned lexerFlags, bool strictMode)
     
    766788        }
    767789    }
    768 
     790   
     791    bool isPrivateName = m_current == '@' && m_parsingBuiltinFunction;
     792    if (isPrivateName)
     793        shift();
     794   
    769795    const LChar* identifierStart = currentSourcePtr();
    770796    unsigned identifierLineStart = currentLineStartOffset();
     
    780806    const Identifier* ident = 0;
    781807   
    782     if (shouldCreateIdentifier) {
     808    if (shouldCreateIdentifier || m_parsingBuiltinFunction) {
    783809        int identifierLength = currentSourcePtr() - identifierStart;
    784810        ident = makeIdentifier(identifierStart, identifierLength);
    785 
     811        if (m_parsingBuiltinFunction) {
     812            if (!isSafeIdentifier(*m_vm, ident) && !isPrivateName) {
     813                m_lexErrorMessage = makeString("The use of '", ident->string(), "' is disallowed in builtin functions.");
     814                return ERRORTOK;
     815            }
     816            if (isPrivateName)
     817                ident = m_vm->propertyNames->getPrivateName(*ident);
     818            else if (*ident == m_vm->propertyNames->undefinedKeyword)
     819                tokenData->ident = &m_vm->propertyNames->undefinedKeywordPrivateName;
     820            if (!ident)
     821                return INVALID_PRIVATE_NAME_ERRORTOK;
     822        }
    786823        tokenData->ident = ident;
    787824    } else
    788825        tokenData->ident = 0;
    789826
    790     if (UNLIKELY((remaining < maxTokenLength) && !(lexerFlags & LexerFlagsIgnoreReservedWords))) {
     827    if (UNLIKELY((remaining < maxTokenLength) && !(lexerFlags & LexerFlagsIgnoreReservedWords)) && !isPrivateName) {
    791828        ASSERT(shouldCreateIdentifier);
    792829        if (remaining < maxTokenLength) {
     
    815852        }
    816853    }
     854   
     855    bool isPrivateName = m_current == '@' && m_parsingBuiltinFunction;
     856    if (isPrivateName)
     857        shift();
    817858
    818859    const UChar* identifierStart = currentSourcePtr();
     
    827868   
    828869    if (UNLIKELY(m_current == '\\')) {
     870        ASSERT(!isPrivateName);
    829871        setOffsetFromSourcePtr(identifierStart, identifierLineStart);
    830872        return parseIdentifierSlowCase<shouldCreateIdentifier>(tokenData, lexerFlags, strictMode);
     
    838880    const Identifier* ident = 0;
    839881   
    840     if (shouldCreateIdentifier) {
     882    if (shouldCreateIdentifier || m_parsingBuiltinFunction) {
    841883        int identifierLength = currentSourcePtr() - identifierStart;
    842884        if (isAll8Bit)
     
    844886        else
    845887            ident = makeIdentifier(identifierStart, identifierLength);
    846        
     888        if (m_parsingBuiltinFunction) {
     889            if (!isSafeIdentifier(*m_vm, ident) && !isPrivateName) {
     890                m_lexErrorMessage = makeString("The use of '", ident->string(), "' is disallowed in builtin functions.");
     891                return ERRORTOK;
     892            }
     893            if (isPrivateName)
     894                ident = m_vm->propertyNames->getPrivateName(*ident);
     895            else if (*ident == m_vm->propertyNames->undefinedKeyword)
     896                tokenData->ident = &m_vm->propertyNames->undefinedKeywordPrivateName;
     897            if (!ident)
     898                return INVALID_PRIVATE_NAME_ERRORTOK;
     899        }
    847900        tokenData->ident = ident;
    848901    } else
    849902        tokenData->ident = 0;
    850903   
    851     if (UNLIKELY((remaining < maxTokenLength) && !(lexerFlags & LexerFlagsIgnoreReservedWords))) {
     904    if (UNLIKELY((remaining < maxTokenLength) && !(lexerFlags & LexerFlagsIgnoreReservedWords)) && !isPrivateName) {
    852905        ASSERT(shouldCreateIdentifier);
    853906        if (remaining < maxTokenLength) {
     
    16591712        FALLTHROUGH;
    16601713    case CharacterBackSlash:
     1714        parseIdent:
    16611715        if (lexerFlags & LexexFlagsDontBuildKeywords)
    16621716            token = parseIdentifier<false>(tokenData, lexerFlags, strictMode);
     
    16711725        m_lineStart = m_code;
    16721726        goto start;
     1727    case CharacterPrivateIdentifierStart:
     1728        if (m_parsingBuiltinFunction)
     1729            goto parseIdent;
     1730
     1731        FALLTHROUGH;
    16731732    case CharacterInvalid:
    16741733        m_lexErrorMessage = invalidCharacterMessage();
Note: See TracChangeset for help on using the changeset viewer.