source: webkit/trunk/Source/JavaScriptCore/generate-js-builtins@ 165074

Last change on this file since 165074 was 163960, checked in by [email protected], 11 years ago

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 size: 11.1 KB
Line 
1#!/usr/bin/python
2# Copyright (C) 2014 Apple Inc. All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions
6# are met:
7# 1. Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9# 2. Redistributions in binary form must reproduce the above copyright
10# notice, this list of conditions and the following disclaimer in the
11# documentation and/or other materials provided with the distribution.
12#
13# THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
25import filecmp
26import fnmatch
27import os
28import re
29import shutil
30import sys
31import datetime
32import json
33
34copyrightText = """ *
35 * Redistribution and use in source and binary forms, with or without
36 * modification, are permitted provided that the following conditions
37 * are met:
38 * 1. Redistributions of source code must retain the above copyright
39 * notice, this list of conditions and the following disclaimer.
40 * 2. Redistributions in binary form must reproduce the above copyright
41 * notice, this list of conditions and the following disclaimer in the
42 * documentation and/or other materials provided with the distribution.
43 *
44 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
45 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
46 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
47 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
48 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
49 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
50 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
51 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
52 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
53 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
54 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
55 */\n
56"""
57
58generatorString = "/* Generated by %s do not hand edit. */\n" % os.path.basename(__file__)
59
60functionHeadRegExp = re.compile(r"function\s+\w+\s*\(.*?\)", re.MULTILINE | re.S)
61functionNameRegExp = re.compile(r"function\s+(\w+)\s*\(", re.MULTILINE | re.S)
62functionParameterFinder = re.compile(r"^function\s+(?:\w+)\s*\(((?:\s*\w+)?\s*(?:\s*,\s*\w+)*)?\)", re.MULTILINE | re.S)
63
64multilineCommentRegExp = re.compile(r"\/\*.*?\*\/", re.MULTILINE | re.S)
65singleLineCommentRegExp = re.compile(r"\/\/.*?\n", re.MULTILINE | re.S)
66
67def getCopyright(source):
68 copyrightBlock = multilineCommentRegExp.findall(source)[0]
69 copyrightBlock = copyrightBlock[:copyrightBlock.index("Redistribution")]
70 copyRightLines = []
71
72 for line in copyrightBlock.split("\n"):
73 line = line.replace("/*", "")
74 line = line.replace("*/", "")
75 line = line.replace("*", "")
76 line = line.replace("Copyright", "")
77 line = line.replace("copyright", "")
78 line = line.replace("(C)", "")
79 line = line.replace("(c)", "")
80 line = line.strip()
81 if len(line) == 0:
82 continue
83
84 copyRightLines.append(line)
85
86 return list(set(copyRightLines))
87
88
89
90def getFunctions(source):
91
92 source = multilineCommentRegExp.sub("/**/", singleLineCommentRegExp.sub("//\n", source))
93
94 matches = [ f for f in functionHeadRegExp.finditer(source)]
95 functionBounds = []
96 start = 0
97 end = 0
98 for match in matches:
99 start = match.start()
100 if start < end:
101 continue
102 end = match.end()
103 while source[end] != '{':
104 end = end + 1
105 depth = 1
106 end = end + 1
107 while depth > 0:
108 if source[end] == '{':
109 depth = depth + 1
110 elif source[end] == '}':
111 depth = depth - 1
112 end = end + 1
113 functionBounds.append((start, end))
114
115 functions = [source[start:end].strip() for (start, end) in functionBounds]
116 result = []
117 for function in functions:
118 function = multilineCommentRegExp.sub("", function)
119 functionName = functionNameRegExp.findall(function)[0]
120 functionParameters = functionParameterFinder.findall(function)[0].split(',')
121 if len(functionParameters[0]) == 0:
122 functionParameters = []
123
124 result.append((functionName, function, functionParameters))
125 return result
126
127
128def generateCode(source):
129 inputFile = open(source, "r")
130 baseName = os.path.basename(source).replace(".js", "")
131
132 source = ""
133 for line in inputFile:
134 source = source + line
135
136 if sys.platform == "cygwin":
137 source = source.replace("\r\n", "\n")
138 return (baseName, getFunctions(source), getCopyright(source))
139
140def mangleName(object, name):
141 qName = object + "." + name
142 mangledName = ""
143 i = 0
144 while i < len(qName):
145 if qName[i] == '.':
146 mangledName = mangledName + qName[i + 1].upper()
147 i = i + 1
148 else:
149 mangledName = mangledName + qName[i]
150 i = i + 1
151 return mangledName
152
153builtins = []
154
155baseName = sys.argv[-1]
156builtin_definitions = sys.argv[1:-1]
157(output_base, _) = os.path.splitext(sys.argv[-1])
158
159copyrights = []
160for file in builtin_definitions:
161 if fnmatch.fnmatch(file, '*.js'):
162 (baseName, functions, objectCopyrights) = generateCode(file)
163 copyrights.extend(objectCopyrights)
164 builtins.append((baseName, functions))
165
166copyrights = list(set(copyrights))
167
168copyrightBody = ""
169for copyright in copyrights:
170 copyrightBody = copyrightBody +" * Copyright (C) " + copyright + "\n"
171
172builtinsHeader = open(output_base + ".h.tmp", "w")
173builtinsImplementation = open(output_base + ".cpp.tmp", "w")
174copyrightText = "/*\n" + copyrightBody + copyrightText
175builtinsHeader.write("""%s
176%s
177
178#ifndef JSCBuiltins_H
179#define JSCBuiltins_H
180
181namespace JSC {
182
183class FunctionExecutable;
184class Identifier;
185class JSGlobalObject;
186class SourceCode;
187class UnlinkedFunctionExecutable;
188class VM;
189
190FunctionExecutable* createBuiltinExecutable(VM&, UnlinkedFunctionExecutable*, const SourceCode&);
191
192""" % (generatorString, copyrightText))
193
194codeReferences = []
195
196for (objectName, functions) in builtins:
197 print("Generating bindings for the %s builtin." % objectName)
198 builtinsHeader.write("/* %s functions */\n" % objectName)
199 for (name, implementation, _) in functions:
200 mangledName = mangleName(objectName, name)
201 mangledName = mangledName[0].lower() + mangledName[1:] + "Code"
202 codeReferences.append((mangledName, name, implementation))
203 builtinsHeader.write("extern const char* s_%s;\n" % mangledName)
204 builtinsHeader.write("extern const int s_%sLength;\n" % mangledName)
205 builtinsHeader.write("\n")
206 builtinsHeader.write("#define JSC_FOREACH_%s_BUILTIN(macro) \\\n" % objectName.replace(".", "_").upper())
207 for (name, implementation, arguments) in functions:
208 mangledName = mangleName(objectName, name)
209 builtinsHeader.write(" macro(%s, %s, %d) \\\n" % (name, mangledName, len(arguments)))
210 builtinsHeader.write("\n")
211 for (name, implementation, arguments) in functions:
212 builtinsHeader.write("#define JSC_BUILTIN_%s 1\n" % mangleName(objectName, name).upper())
213 builtinsHeader.write("\n\n")
214names = []
215builtinsHeader.write("#define JSC_FOREACH_BUILTIN(macro)\\\n")
216for (codeReference, functionName, source) in codeReferences:
217 builtinsHeader.write(" macro(%s, %s, s_%sLength) \\\n" % (codeReference, functionName, codeReference))
218 names.append(functionName)
219
220builtinsHeader.write("\n\n")
221builtinsHeader.write("#define JSC_FOREACH_BUILTIN_FUNCTION_NAME(macro) \\\n")
222for name in sorted(set(names)):
223 builtinsHeader.write(" macro(%s) \\\n" % name)
224builtinsHeader.write("""
225
226#define JSC_DECLARE_BUILTIN_GENERATOR(codeName, functionName, argumentCount) \\
227 FunctionExecutable* codeName##Generator(VM&);
228
229JSC_FOREACH_BUILTIN(JSC_DECLARE_BUILTIN_GENERATOR)
230#undef JSC_DECLARE_BUILTIN_GENERATOR
231
232#define JSC_BUILTIN_EXISTS(name) defined JSC_BUILTIN_ ## name
233
234}
235
236#endif
237
238""")
239
240builtinsImplementation.write("""%s
241%s
242
243#include "config.h"
244
245#include "JSCBuiltins.h"
246
247#include "BuiltinExecutables.h"
248#include "Executable.h"
249#include "JSCellInlines.h"
250#include "VM.h"
251
252namespace JSC {
253
254""" % (generatorString, copyrightText))
255
256for (codeReference, name, source) in codeReferences:
257 source = "(function " + source[source.index("("):] + ")"
258 lines = json.dumps(source)[1:-1].split("\\n")
259 sourceLength = len(source)
260 source = ""
261 for line in lines:
262 source = source + (" \"%s\\n\" \\\n" % line)
263 builtinsImplementation.write("const char* s_%s =\n%s;\n\n" % (codeReference, source))
264 builtinsImplementation.write("const int s_%sLength = %d;\n\n" % (codeReference, sourceLength + 1)) # + 1 for \n
265
266builtinsImplementation.write("""
267FunctionExecutable* createBuiltinExecutable(VM& vm, UnlinkedFunctionExecutable* unlinkedExecutable, const SourceCode& source)
268{
269 unsigned lineCount = unlinkedExecutable->lineCount();
270 unsigned startColumn = 1;
271 unsigned sourceLength = unlinkedExecutable->sourceLength();
272 bool endColumnIsOnStartLine = !lineCount;
273 unsigned endColumnExcludingBraces = unlinkedExecutable->unlinkedBodyEndColumn() + (endColumnIsOnStartLine ? 0 : 1);
274 unsigned startOffset = unlinkedExecutable->startOffset();
275 unsigned startOffsetExcludingOpenBrace = startOffset + 1;
276 unsigned endOffsetExcludingCloseBrace = startOffset + sourceLength - 1;
277 SourceCode bodySource(source.provider(), startOffsetExcludingOpenBrace, endOffsetExcludingCloseBrace, 0, startColumn);
278 return FunctionExecutable::create(vm, bodySource, unlinkedExecutable, 0, lineCount, startColumn, endColumnExcludingBraces, false);
279}
280
281#define JSC_DEFINE_BUILTIN_GENERATOR(codeName, functionName, argumentCount) \\
282FunctionExecutable* codeName##Generator(VM& vm) \\
283{ \\
284 return createBuiltinExecutable(vm, vm.builtinExecutables()->codeName##Executable(), vm.builtinExecutables()->codeName##Source()); \\
285}
286
287JSC_FOREACH_BUILTIN(JSC_DEFINE_BUILTIN_GENERATOR)
288#undef JSC_DEFINE_BUILTIN_GENERATOR
289}
290
291""")
292
293builtinsHeader.close()
294builtinsImplementation.close()
295
296if (not os.path.exists(output_base + ".h")) or (not filecmp.cmp(output_base + ".h.tmp", output_base + ".h", shallow=False)):
297 os.rename(output_base + ".h.tmp", output_base + ".h")
298else:
299 os.remove(output_base + ".h.tmp")
300
301if (not os.path.exists(output_base + ".cpp")) or (not filecmp.cmp(output_base + ".cpp.tmp", output_base + ".cpp", shallow=False)):
302 os.rename(output_base + ".cpp.tmp", output_base + ".cpp")
303else:
304 os.remove(output_base + ".cpp.tmp")
Note: See TracBrowser for help on using the repository browser.