Ignore:
Timestamp:
Feb 12, 2014, 9:14:23 AM (11 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.

.:

  • GNUmakefile.am:
  • 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.

  • 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):

(every):

  • builtins/BuiltinExecutables.cpp: Added.

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

  • builtins/BuiltinExecutables.h:

(JSC::BuiltinExecutables::create):

  • builtins/BuiltinNames.h: Added.

(JSC::BuiltinNames::BuiltinNames):
(JSC::BuiltinNames::getPrivateName):
(JSC::BuiltinNames::getPublicName):

  • bytecode/CodeBlock.cpp:

(JSC::CodeBlock::CodeBlock):

  • 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:
  • generate-js-builtins: Added.

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

  • interpreter/CachedCall.h:

(JSC::CachedCall::CachedCall):

  • parser/ASTBuilder.h:

(JSC::ASTBuilder::makeFunctionCallNode):

  • parser/Lexer.cpp:

(JSC::Lexer<T>::Lexer):
(JSC::isSafeBuiltinIdentifier):
(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/ArrayPrototype.cpp:
  • runtime/CodeCache.cpp:

(JSC::CodeCache::getFunctionExecutableFromGlobalCode):

  • runtime/CommonIdentifiers.cpp:

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

  • runtime/CommonIdentifiers.h:

(JSC::CommonIdentifiers::builtinNames):

  • 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/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/JSFunctionInlines.h:

(JSC::JSFunction::nativeFunction):
(JSC::JSFunction::nativeConstructor):
(JSC::isHostFunction):

  • 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/JSSymbolTableObject.cpp:

(JSC::JSSymbolTableObject::getOwnNonIndexPropertyNames):

  • 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/VM.cpp:

(JSC::VM::VM):

  • runtime/VM.h:

(JSC::VM::builtinExecutables):

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

    r163844 r163960  
    9292    // Other types (only one so far)
    9393    CharacterWhiteSpace,
     94    CharacterPrivateIdentifierStart
    9495};
    9596
     
    160161/*  62 - >                  */ CharacterGreater,
    161162/*  63 - ?                  */ CharacterQuestion,
    162 /*  64 - @                  */ CharacterInvalid,
     163/*  64 - @                  */ CharacterPrivateIdentifierStart,
    163164/*  65 - A                  */ CharacterIdentifierStart,
    164165/*  66 - B                  */ CharacterIdentifierStart,
     
    488489
    489490template <typename T>
    490 Lexer<T>::Lexer(VM* vm)
     491Lexer<T>::Lexer(VM* vm, JSParserStrictness strictness)
    491492    : m_isReparsing(false)
    492493    , m_vm(vm)
     494    , m_parsingBuiltinFunction(strictness == JSParseBuiltin)
    493495{
    494496}
     
    755757    m_buffer16.append(static_cast<UChar>(c));
    756758}
    757 
     759   
     760#if !ASSERT_DISABLED
     761bool isSafeBuiltinIdentifier(VM& vm, const Identifier* ident)
     762{
     763    if (!ident)
     764        return true;
     765    /* Just block any use of suspicious identifiers.  This is intended to
     766     * be used as a safety net while implementing builtins.
     767     */
     768    if (*ident == vm.propertyNames->call)
     769        return false;
     770    if (*ident == vm.propertyNames->apply)
     771        return false;
     772    if (*ident == vm.propertyNames->eval)
     773        return false;
     774    if (*ident == vm.propertyNames->Function)
     775        return false;
     776    return true;
     777}
     778#endif
     779   
    758780template <>
    759781template <bool shouldCreateIdentifier> ALWAYS_INLINE JSTokenType Lexer<LChar>::parseIdentifier(JSTokenData* tokenData, unsigned lexerFlags, bool strictMode)
     
    767789        }
    768790    }
    769 
     791   
     792    bool isPrivateName = m_current == '@' && m_parsingBuiltinFunction;
     793    if (isPrivateName)
     794        shift();
     795   
    770796    const LChar* identifierStart = currentSourcePtr();
    771797    unsigned identifierLineStart = currentLineStartOffset();
     
    781807    const Identifier* ident = 0;
    782808   
    783     if (shouldCreateIdentifier) {
     809    if (shouldCreateIdentifier || m_parsingBuiltinFunction) {
    784810        int identifierLength = currentSourcePtr() - identifierStart;
    785811        ident = makeIdentifier(identifierStart, identifierLength);
    786 
     812        if (m_parsingBuiltinFunction) {
     813            if (!isSafeBuiltinIdentifier(*m_vm, ident) && !isPrivateName) {
     814                m_lexErrorMessage = makeString("The use of '", ident->string(), "' is disallowed in builtin functions.");
     815                return ERRORTOK;
     816            }
     817            if (isPrivateName)
     818                ident = m_vm->propertyNames->getPrivateName(*ident);
     819            else if (*ident == m_vm->propertyNames->undefinedKeyword)
     820                tokenData->ident = &m_vm->propertyNames->undefinedPrivateName;
     821            if (!ident)
     822                return INVALID_PRIVATE_NAME_ERRORTOK;
     823        }
    787824        tokenData->ident = ident;
    788825    } else
    789826        tokenData->ident = 0;
    790827
    791     if (UNLIKELY((remaining < maxTokenLength) && !(lexerFlags & LexerFlagsIgnoreReservedWords))) {
     828    if (UNLIKELY((remaining < maxTokenLength) && !(lexerFlags & LexerFlagsIgnoreReservedWords)) && !isPrivateName) {
    792829        ASSERT(shouldCreateIdentifier);
    793830        if (remaining < maxTokenLength) {
     
    816853        }
    817854    }
     855   
     856    bool isPrivateName = m_current == '@' && m_parsingBuiltinFunction;
     857    if (isPrivateName)
     858        shift();
    818859
    819860    const UChar* identifierStart = currentSourcePtr();
     
    828869   
    829870    if (UNLIKELY(m_current == '\\')) {
     871        ASSERT(!isPrivateName);
    830872        setOffsetFromSourcePtr(identifierStart, identifierLineStart);
    831873        return parseIdentifierSlowCase<shouldCreateIdentifier>(tokenData, lexerFlags, strictMode);
     
    839881    const Identifier* ident = 0;
    840882   
    841     if (shouldCreateIdentifier) {
     883    if (shouldCreateIdentifier || m_parsingBuiltinFunction) {
    842884        int identifierLength = currentSourcePtr() - identifierStart;
    843885        if (isAll8Bit)
     
    845887        else
    846888            ident = makeIdentifier(identifierStart, identifierLength);
    847        
     889        if (m_parsingBuiltinFunction) {
     890            if (!isSafeBuiltinIdentifier(*m_vm, ident) && !isPrivateName) {
     891                m_lexErrorMessage = makeString("The use of '", ident->string(), "' is disallowed in builtin functions.");
     892                return ERRORTOK;
     893            }
     894            if (isPrivateName)
     895                ident = m_vm->propertyNames->getPrivateName(*ident);
     896            else if (*ident == m_vm->propertyNames->undefinedKeyword)
     897                tokenData->ident = &m_vm->propertyNames->undefinedPrivateName;
     898            if (!ident)
     899                return INVALID_PRIVATE_NAME_ERRORTOK;
     900        }
    848901        tokenData->ident = ident;
    849902    } else
    850903        tokenData->ident = 0;
    851904   
    852     if (UNLIKELY((remaining < maxTokenLength) && !(lexerFlags & LexerFlagsIgnoreReservedWords))) {
     905    if (UNLIKELY((remaining < maxTokenLength) && !(lexerFlags & LexerFlagsIgnoreReservedWords)) && !isPrivateName) {
    853906        ASSERT(shouldCreateIdentifier);
    854907        if (remaining < maxTokenLength) {
     
    16601713        FALLTHROUGH;
    16611714    case CharacterBackSlash:
     1715        parseIdent:
    16621716        if (lexerFlags & LexexFlagsDontBuildKeywords)
    16631717            token = parseIdentifier<false>(tokenData, lexerFlags, strictMode);
     
    16721726        m_lineStart = m_code;
    16731727        goto start;
     1728    case CharacterPrivateIdentifierStart:
     1729        if (m_parsingBuiltinFunction)
     1730            goto parseIdent;
     1731
     1732        FALLTHROUGH;
    16741733    case CharacterInvalid:
    16751734        m_lexErrorMessage = invalidCharacterMessage();
Note: See TracChangeset for help on using the changeset viewer.