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

.:

  • 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/bytecompiler/NodesCodegen.cpp

    r163844 r163960  
    536536    RefPtr<RegisterID> base = generator.emitNode(m_base);
    537537    generator.emitExpressionInfo(subexpressionDivot(), subexpressionStart(), subexpressionEnd());
    538     RefPtr<RegisterID> function = generator.emitGetById(generator.tempDestination(dst), base.get(), m_ident);
     538    RefPtr<RegisterID> function = generator.emitGetById(generator.tempDestination(dst), base.get(), generator.propertyNames().call);
    539539    RefPtr<RegisterID> returnValue = generator.finalDestination(dst, function.get());
    540     generator.emitJumpIfNotFunctionCall(function.get(), realCall.get());
     540    bool emitCallCheck = !generator.isBuiltinFunction();
     541    if (emitCallCheck)
     542        generator.emitJumpIfNotFunctionCall(function.get(), realCall.get());
     543
    541544    {
    542545        if (m_args->m_listNode && m_args->m_listNode->m_expr) {
     
    548551            generator.emitNode(callArguments.thisRegister(), oldList->m_expr);
    549552            generator.emitCall(returnValue.get(), realFunction.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd());
    550             generator.emitJump(end.get());
    551 
    552553            m_args->m_listNode = oldList;
    553554        } else {
     
    556557            generator.emitLoad(callArguments.thisRegister(), jsUndefined());
    557558            generator.emitCall(returnValue.get(), realFunction.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd());
    558             generator.emitJump(end.get());
    559         }
    560     }
    561     generator.emitLabel(realCall.get());
    562     {
    563         CallArguments callArguments(generator, m_args);
    564         generator.emitMove(callArguments.thisRegister(), base.get());
    565         generator.emitCall(returnValue.get(), function.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd());
    566     }
    567     generator.emitLabel(end.get());
     559        }
     560    }
     561    if (emitCallCheck) {
     562        generator.emitJump(end.get());
     563        generator.emitLabel(realCall.get());
     564        {
     565            CallArguments callArguments(generator, m_args);
     566            generator.emitMove(callArguments.thisRegister(), base.get());
     567            generator.emitCall(returnValue.get(), function.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd());
     568        }
     569        generator.emitLabel(end.get());
     570    }
    568571    return returnValue.get();
    569572}
     
    586589    RefPtr<RegisterID> base = generator.emitNode(m_base);
    587590    generator.emitExpressionInfo(subexpressionDivot(), subexpressionStart(), subexpressionEnd());
    588     RefPtr<RegisterID> function = generator.emitGetById(generator.tempDestination(dst), base.get(), m_ident);
     591    RefPtr<RegisterID> function;
    589592    RefPtr<RegisterID> returnValue = generator.finalDestination(dst, function.get());
    590     generator.emitJumpIfNotFunctionApply(function.get(), realCall.get());
    591     {
    592         if (mayBeCall) {
    593             if (m_args->m_listNode && m_args->m_listNode->m_expr) {
    594                 ArgumentListNode* oldList = m_args->m_listNode;
    595                 if (m_args->m_listNode->m_next) {
    596                     ASSERT(m_args->m_listNode->m_next->m_expr->isSimpleArray());
    597                     ASSERT(!m_args->m_listNode->m_next->m_next);
    598                     m_args->m_listNode = static_cast<ArrayNode*>(m_args->m_listNode->m_next->m_expr)->toArgumentList(generator.vm(), 0, 0);
    599                     RefPtr<RegisterID> realFunction = generator.emitMove(generator.tempDestination(dst), base.get());
    600                     CallArguments callArguments(generator, m_args);
    601                     generator.emitNode(callArguments.thisRegister(), oldList->m_expr);
    602                     generator.emitCall(returnValue.get(), realFunction.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd());
    603                 } else {
    604                     m_args->m_listNode = m_args->m_listNode->m_next;
    605                     RefPtr<RegisterID> realFunction = generator.emitMove(generator.tempDestination(dst), base.get());
    606                     CallArguments callArguments(generator, m_args);
    607                     generator.emitNode(callArguments.thisRegister(), oldList->m_expr);
    608                     generator.emitCall(returnValue.get(), realFunction.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd());
    609                 }
    610                 m_args->m_listNode = oldList;
    611             } else {
     593    bool emitCallCheck = !generator.isBuiltinFunction();
     594    if (emitCallCheck) {
     595        function = generator.emitGetById(generator.tempDestination(dst), base.get(), generator.propertyNames().apply);
     596        generator.emitJumpIfNotFunctionApply(function.get(), realCall.get());
     597    }
     598    if (mayBeCall) {
     599        if (m_args->m_listNode && m_args->m_listNode->m_expr) {
     600            ArgumentListNode* oldList = m_args->m_listNode;
     601            if (m_args->m_listNode->m_next) {
     602                ASSERT(m_args->m_listNode->m_next->m_expr->isSimpleArray());
     603                ASSERT(!m_args->m_listNode->m_next->m_next);
     604                m_args->m_listNode = static_cast<ArrayNode*>(m_args->m_listNode->m_next->m_expr)->toArgumentList(generator.vm(), 0, 0);
    612605                RefPtr<RegisterID> realFunction = generator.emitMove(generator.tempDestination(dst), base.get());
    613606                CallArguments callArguments(generator, m_args);
    614                 generator.emitLoad(callArguments.thisRegister(), jsUndefined());
     607                generator.emitNode(callArguments.thisRegister(), oldList->m_expr);
     608                generator.emitCall(returnValue.get(), realFunction.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd());
     609            } else {
     610                m_args->m_listNode = m_args->m_listNode->m_next;
     611                RefPtr<RegisterID> realFunction = generator.emitMove(generator.tempDestination(dst), base.get());
     612                CallArguments callArguments(generator, m_args);
     613                generator.emitNode(callArguments.thisRegister(), oldList->m_expr);
    615614                generator.emitCall(returnValue.get(), realFunction.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd());
    616615            }
     616            m_args->m_listNode = oldList;
    617617        } else {
    618             ASSERT(m_args->m_listNode && m_args->m_listNode->m_next);
    619             RefPtr<RegisterID> profileHookRegister;
    620             if (generator.shouldEmitProfileHooks())
    621                 profileHookRegister = generator.newTemporary();
    622618            RefPtr<RegisterID> realFunction = generator.emitMove(generator.tempDestination(dst), base.get());
    623             RefPtr<RegisterID> thisRegister = generator.emitNode(m_args->m_listNode->m_expr);
    624             RefPtr<RegisterID> argsRegister;
    625             ArgumentListNode* args = m_args->m_listNode->m_next;
    626             if (args->m_expr->isResolveNode() && generator.willResolveToArguments(static_cast<ResolveNode*>(args->m_expr)->identifier()))
    627                 argsRegister = generator.uncheckedRegisterForArguments();
    628             else
    629                 argsRegister = generator.emitNode(args->m_expr);
    630 
    631             // Function.prototype.apply ignores extra arguments, but we still
    632             // need to evaluate them for side effects.
    633             while ((args = args->m_next))
    634                 generator.emitNode(args->m_expr);
    635 
    636             generator.emitCallVarargs(returnValue.get(), realFunction.get(), thisRegister.get(), argsRegister.get(), generator.newTemporary(), profileHookRegister.get(), divot(), divotStart(), divotEnd());
    637         }
     619            CallArguments callArguments(generator, m_args);
     620            generator.emitLoad(callArguments.thisRegister(), jsUndefined());
     621            generator.emitCall(returnValue.get(), realFunction.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd());
     622        }
     623    } else {
     624        ASSERT(m_args->m_listNode && m_args->m_listNode->m_next);
     625        RefPtr<RegisterID> profileHookRegister;
     626        if (generator.shouldEmitProfileHooks())
     627            profileHookRegister = generator.newTemporary();
     628        RefPtr<RegisterID> realFunction = generator.emitMove(generator.tempDestination(dst), base.get());
     629        RefPtr<RegisterID> thisRegister = generator.emitNode(m_args->m_listNode->m_expr);
     630        RefPtr<RegisterID> argsRegister;
     631        ArgumentListNode* args = m_args->m_listNode->m_next;
     632        if (args->m_expr->isResolveNode() && generator.willResolveToArguments(static_cast<ResolveNode*>(args->m_expr)->identifier()))
     633            argsRegister = generator.uncheckedRegisterForArguments();
     634        else
     635            argsRegister = generator.emitNode(args->m_expr);
     636
     637        // Function.prototype.apply ignores extra arguments, but we still
     638        // need to evaluate them for side effects.
     639        while ((args = args->m_next))
     640            generator.emitNode(args->m_expr);
     641
     642        generator.emitCallVarargs(returnValue.get(), realFunction.get(), thisRegister.get(), argsRegister.get(), generator.newTemporary(), profileHookRegister.get(), divot(), divotStart(), divotEnd());
     643    }
     644    if (emitCallCheck) {
    638645        generator.emitJump(end.get());
    639     }
    640     generator.emitLabel(realCall.get());
    641     {
     646        generator.emitLabel(realCall.get());
    642647        CallArguments callArguments(generator, m_args);
    643648        generator.emitMove(callArguments.thisRegister(), base.get());
    644649        generator.emitCall(returnValue.get(), function.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd());
    645     }
    646     generator.emitLabel(end.get());
     650        generator.emitLabel(end.get());
     651    }
    647652    return returnValue.get();
    648653}
Note: See TracChangeset for help on using the changeset viewer.