[bindings generator] Support cached values that do not correspond to properties and support call() functions

This commit is contained in:
Jarred Sumner
2022-11-07 00:51:36 -08:00
parent 179cd18f70
commit d925367cae
2 changed files with 145 additions and 81 deletions

View File

@@ -15,6 +15,7 @@ export type Field =
export interface ClassDefinition {
name: string;
construct?: boolean;
call?: boolean;
finalize?: boolean;
klass: Record<string, Field>;
proto: Record<string, Field>;
@@ -29,15 +30,19 @@ export function define(
{
klass = {},
proto = {},
isEventEmitter = false,
values = [],
estimatedSize = false,
call = false,
construct = false,
...rest
} = {} as ClassDefinition
): ClassDefinition {
return {
...rest,
isEventEmitter,
call,
construct,
estimatedSize,
values,
klass: Object.fromEntries(
Object.entries(klass).sort(([a], [b]) => a.localeCompare(b))
),

View File

@@ -3,6 +3,12 @@ import { readdirSync } from "fs";
import { resolve } from "path";
import type { Field, ClassDefinition } from "./class-definitions";
const directoriesToSearch = [
resolve(`${import.meta.dir}/../api`),
resolve(`${import.meta.dir}/../test`),
resolve(`${import.meta.dir}/../webcore`),
];
function symbolName(typeName, name) {
return `${typeName}__${name}`;
}
@@ -283,7 +289,7 @@ function generatePrototype(typeName, obj) {
const { proto: protoFields } = obj;
return `
${
"construct" in obj
obj.construct
? `extern "C" void* ${classSymbolName(
typeName,
"construct"
@@ -296,6 +302,14 @@ ${
? `extern "C" void ${classSymbolName(typeName, "finalize")}(void*);`
: ""
}
${
obj.call
? `extern "C" JSC_DECLARE_HOST_FUNCTION(${classSymbolName(
typeName,
"call"
)});`
: ""
}
${renderDecls(protoSymbolName, typeName, protoFields)}
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(${proto}, ${proto}::Base);
@@ -313,7 +327,13 @@ ${generateHashTable(
const ClassInfo ${proto}::s_info = { "${typeName}"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${proto}) };
${renderFieldsImpl(protoSymbolName, typeName, obj, protoFields)}
${renderFieldsImpl(
protoSymbolName,
typeName,
obj,
protoFields,
obj.values || []
)}
void ${proto}::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
@@ -407,14 +427,10 @@ function generateConstructorHeader(typeName) {
// Must be defined for each specialization class.
static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*);
DECLARE_EXPORT_INFO;
private:
${name}(JSC::VM& vm, JSC::Structure* structure, JSC::NativeFunction nativeFunction)
: Base(vm, structure, nativeFunction, nativeFunction)
{
}
${name}(JSC::VM& vm, JSC::Structure* structure);
void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, ${prototypeName(
typeName
)}* prototype);
@@ -455,10 +471,16 @@ void ${name}::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, ${protot
ASSERT(inherits(info()));
}
${name}::${name}(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure, ${
obj.call ? classSymbolName(typeName, "call") : "construct"
}, construct) {
}
${name}* ${name}::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, ${prototypeName(
typeName
)}* prototype) {
${name}* ptr = new (NotNull, JSC::allocateCell<${name}>(vm)) ${name}(vm, structure, construct);
${name}* ptr = new (NotNull, JSC::allocateCell<${name}>(vm)) ${name}(vm, structure);
ptr->finishCreation(vm, globalObject, prototype);
return ptr;
}
@@ -548,8 +570,8 @@ ${
`;
}
function renderCachedFieldsHeader(typeName, klass, proto) {
const rows = [];
function renderCachedFieldsHeader(typeName, klass, proto, values) {
const rows: string[] = [];
for (const name in klass) {
if (
("cache" in klass[name] && klass[name].cache === true) ||
@@ -565,6 +587,10 @@ function renderCachedFieldsHeader(typeName, klass, proto) {
}
}
for (const name of values) {
rows.push(`mutable JSC::WriteBarrier<JSC::Unknown> m_${name};`);
}
return rows.join("\n");
}
@@ -677,12 +703,40 @@ function renderStaticDecls(symbolName, typeName, fields) {
return rows.join("\n");
}
function writeBarrier(symbolName, typeName, name, cacheName) {
return `
extern "C" void ${symbolName(
typeName,
name
)}SetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value)
{
auto& vm = globalObject->vm();
auto* thisObject = jsCast<${className(
typeName
)}*>(JSValue::decode(thisValue));
thisObject->${cacheName}.set(vm, thisObject, JSValue::decode(value));
}
extern "C" EncodedJSValue ${symbolName(
typeName,
name
)}GetCachedValue(JSC::EncodedJSValue thisValue)
{
auto* thisObject = jsCast<${className(
typeName
)}*>(JSValue::decode(thisValue));
return JSValue::encode(thisObject->${cacheName}.get());
}
`;
}
function renderFieldsImpl(
symbolName: (typeName: string, name: string) => string,
typeName: string,
obj: ClassDefinition,
proto: ClassDefinition["proto"]
proto: ClassDefinition["proto"],
cachedValues: string[]
) {
const rows: string[] = [];
@@ -741,32 +795,7 @@ JSC_DEFINE_CUSTOM_GETTER(${symbolName(
RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
}`);
}
rows.push(`
extern "C" void ${symbolName(
typeName,
name
)}SetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value)
{
auto& vm = globalObject->vm();
auto* thisObject = jsCast<${className(
typeName
)}*>(JSValue::decode(thisValue));
thisObject->${cacheName}.set(vm, thisObject, JSValue::decode(value));
}
extern "C" EncodedJSValue ${symbolName(
typeName,
name
)}GetCachedValue(JSC::EncodedJSValue thisValue)
{
auto* thisObject = jsCast<${className(
typeName
)}*>(JSValue::decode(thisValue));
return JSValue::encode(thisObject->${cacheName}.get());
}
`);
rows.push(writeBarrier(symbolName, typeName, name, cacheName));
} else if (
"getter" in proto[name] ||
("accessor" in proto[name] && proto[name].getter)
@@ -824,30 +853,38 @@ JSC_DEFINE_CUSTOM_SETTER(${symbolName(
if ("fn" in proto[name]) {
rows.push(`
JSC_DEFINE_HOST_FUNCTION(${symbolName(
typeName,
name
)}Callback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
auto& vm = lexicalGlobalObject->vm();
${className(typeName)}* thisObject = jsDynamicCast<${className(
JSC_DEFINE_HOST_FUNCTION(${symbolName(
typeName,
name
)}Callback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
auto& vm = lexicalGlobalObject->vm();
${className(typeName)}* thisObject = jsDynamicCast<${className(
typeName
)}*>(callFrame->thisValue());
if (UNLIKELY(!thisObject)) {
auto throwScope = DECLARE_THROW_SCOPE(vm);
return throwVMTypeError(lexicalGlobalObject, throwScope);
}
if (UNLIKELY(!thisObject)) {
auto throwScope = DECLARE_THROW_SCOPE(vm);
return throwVMTypeError(lexicalGlobalObject, throwScope);
}
JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
return ${symbolName(
typeName,
proto[name].fn
)}(thisObject->wrapped(), lexicalGlobalObject, callFrame);
}
`);
JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
return ${symbolName(
typeName,
proto[name].fn
)}(thisObject->wrapped(), lexicalGlobalObject, callFrame);
}
`);
}
}
if (cachedValues?.length) {
for (const cacheName of cachedValues) {
rows.push(
writeBarrier(symbolName, typeName, cacheName, "m_" + cacheName)
);
}
}
@@ -936,6 +973,7 @@ function generateClassHeader(typeName, obj: ClassDefinition) {
}
static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject);
static JSObject* createConstructor(VM& vm, JSGlobalObject* globalObject, JSValue prototype);
~${name}();
@@ -965,7 +1003,7 @@ function generateClassHeader(typeName, obj: ClassDefinition) {
${DECLARE_VISIT_CHILDREN}
${renderCachedFieldsHeader(typeName, klass, proto)}
${renderCachedFieldsHeader(typeName, klass, proto, values)}
};
`;
}
@@ -998,7 +1036,7 @@ function generateClassImpl(typeName, obj: ClassDefinition) {
})
.join("\n");
var DEFINE_VISIT_CHILDREN = "";
if (DEFINE_VISIT_CHILDREN_LIST.length || estimatedSize) {
if (DEFINE_VISIT_CHILDREN_LIST.length || estimatedSize || values.length) {
DEFINE_VISIT_CHILDREN = `
template<typename Visitor>
void ${name}::visitChildrenImpl(JSCell* cell, Visitor& visitor)
@@ -1114,6 +1152,17 @@ void ${name}::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
Base::analyzeHeap(cell, analyzer);
}
JSObject* ${name}::createConstructor(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
return WebCore::${constructorName(
typeName
)}::create(vm, globalObject, WebCore::${constructorName(
typeName
)}::createStructure(vm, globalObject, globalObject->functionPrototype()), jsCast<WebCore::${prototypeName(
typeName
)}*>(prototype));
}
JSObject* ${name}::createPrototype(VM& vm, JSDOMGlobalObject* globalObject)
{
return ${prototypeName(typeName)}::create(vm, globalObject, ${prototypeName(
@@ -1129,17 +1178,13 @@ ${DEFINE_VISIT_CHILDREN}
}
function generateHeader(typeName, obj) {
return (
generateClassHeader(typeName, obj).trim() +
"\n" +
(!obj.noConstructor ? generateConstructorHeader(typeName).trim() : "") +
"\n"
);
return generateClassHeader(typeName, obj).trim();
}
function generateImpl(typeName, obj) {
const proto = obj.proto;
return [
generateConstructorHeader(typeName).trim() + "\n",
Object.keys(proto).length > 0 && generatePrototype(typeName, obj).trim(),
!obj.noConstructor ? generateConstructorImpl(typeName, obj).trim() : null,
Object.keys(proto).length > 0 && generateClassImpl(typeName, obj).trim(),
@@ -1157,6 +1202,8 @@ function generateZig(
finalize,
noConstructor,
estimatedSize,
call = false,
values = [],
hasPendingActivity = false,
} = {} as ClassDefinition
) {
@@ -1166,6 +1213,10 @@ function generateZig(
exports.set(`constructor`, classSymbolName(typeName, "construct"));
}
if (call) {
exports.set(`call`, classSymbolName(typeName, "call"));
}
if (finalize) {
exports.set(`finalize`, classSymbolName(typeName, "finalize"));
}
@@ -1188,13 +1239,16 @@ function generateZig(
appendSymbols(exports, (name) => protoSymbolName(typeName, name), a)
);
const externs = Object.entries(proto)
const externs = Object.entries({
...proto,
...Object.fromEntries((values || []).map((a) => [a, { internal: true }])),
})
.filter(
([name, { cache, internal }]) =>
(cache && typeof cache !== "string") || internal
)
.map(
([name, { cache }]) =>
([name]) =>
`extern fn ${protoSymbolName(
typeName,
name
@@ -1336,6 +1390,14 @@ function generateZig(
}
});
if (!!call) {
output += `
if (@TypeOf(${typeName}.call) != StaticCallbackType)
@compileLog(
"Expected ${typeName}.call to be a static callback"
);`;
}
return output;
}
@@ -1489,13 +1551,9 @@ function generateLazyClassStructureImpl(typeName, { klass = {}, proto = {} }) {
init.setStructure(WebCore::${className(
typeName
)}::createStructure(init.vm, init.global, init.prototype));
init.setConstructor(WebCore::${constructorName(
init.setConstructor(WebCore::${className(
typeName
)}::create(init.vm, init.global, WebCore::${constructorName(
typeName
)}::createStructure(init.vm, init.global, init.global->functionPrototype()), jsCast<WebCore::${prototypeName(
typeName
)}*>(init.prototype)));
)}::createConstructor(init.vm, init.global, init.prototype));
});
@@ -1531,6 +1589,12 @@ const GENERATED_CLASSES_IMPL_HEADER = `
// Generated by make codegen
#include "root.h"
#include <JavaScriptCore/JSFunction.h>
#include <JavaScriptCore/InternalFunction.h>
#include <JavaScriptCore/LazyClassStructure.h>
#include <JavaScriptCore/LazyClassStructureInlines.h>
#include <JavaScriptCore/FunctionPrototype.h>
#include "ZigGlobalObject.h"
#include <JavaScriptCore/DOMJITAbstractHeap.h>
@@ -1604,11 +1668,6 @@ pub const StaticCallbackType = fn(*JSC.JSGlobalObject, *JSC.CallFrame) callconv(
`;
const directoriesToSearch = [
resolve(`${import.meta.dir}/../api`),
resolve(`${import.meta.dir}/../webcore`),
];
function findClasses() {
var classes = [];
for (let directory of directoriesToSearch) {