aboutsummaryrefslogtreecommitdiff
path: root/src/codegen/bundle-functions.ts
diff options
context:
space:
mode:
authorGravatar dave caruso <me@paperdave.net> 2023-10-17 19:42:37 -0700
committerGravatar dave caruso <me@paperdave.net> 2023-10-17 19:42:37 -0700
commitcb5c4c71c866362dce24eff79251fed6add53e9f (patch)
tree5635cf21140ff2eac14539316f7c6d6704925bd3 /src/codegen/bundle-functions.ts
parentbf12268274faac1a38d33007be7a48af9e570761 (diff)
downloadbun-cb5c4c71c866362dce24eff79251fed6add53e9f.tar.gz
bun-cb5c4c71c866362dce24eff79251fed6add53e9f.tar.zst
bun-cb5c4c71c866362dce24eff79251fed6add53e9f.zip
Diffstat (limited to 'src/codegen/bundle-functions.ts')
-rw-r--r--src/codegen/bundle-functions.ts627
1 files changed, 627 insertions, 0 deletions
diff --git a/src/codegen/bundle-functions.ts b/src/codegen/bundle-functions.ts
new file mode 100644
index 000000000..ebdf0d748
--- /dev/null
+++ b/src/codegen/bundle-functions.ts
@@ -0,0 +1,627 @@
+import { existsSync, mkdirSync, readdirSync, rmSync } from "fs";
+import path from "path";
+import { sliceSourceCode } from "./builtin-parser";
+import { applyGlobalReplacements, define } from "./replacements";
+import { cap, fmtCPPString, low, writeIfNotChanged } from "./helpers";
+import { createInternalModuleRegistry } from "./internal-module-registry-scanner";
+
+console.log("Bundling Bun builtin functions...");
+
+const PARALLEL = false;
+const KEEP_TMP = true;
+
+const CMAKE_BUILD_ROOT = process.argv[2];
+
+if (!CMAKE_BUILD_ROOT) {
+ console.error("Usage: bun bundle-functions.ts <CMAKE_WORK_DIR>");
+ process.exit(1);
+}
+
+const SRC_DIR = path.join(import.meta.dir, "../js/builtins");
+const OUT_DIR = path.join(CMAKE_BUILD_ROOT, "./js");
+const TMP_DIR = path.join(CMAKE_BUILD_ROOT, "./tmp");
+
+const {
+ //
+ requireTransformer,
+} = createInternalModuleRegistry(path.join(import.meta.dir, "../js"));
+
+if (existsSync(TMP_DIR)) rmSync(TMP_DIR, { recursive: true });
+mkdirSync(TMP_DIR, { recursive: true });
+
+interface ParsedBuiltin {
+ name: string;
+ params: string[];
+ directives: Record<string, any>;
+ source: string;
+ async: boolean;
+}
+interface BundledBuiltin {
+ name: string;
+ directives: Record<string, any>;
+ isGetter: boolean;
+ constructAbility: string;
+ constructKind: string;
+ isLinkTimeConstant: boolean;
+ intrinsic: string;
+ overriddenName: string;
+ source: string;
+ params: string[];
+ visibility: string;
+}
+
+/**
+ * Source .ts file --> Array<bundled js function code>
+ */
+async function processFileSplit(filename: string): Promise<{ functions: BundledBuiltin[]; internal: boolean }> {
+ const basename = path.basename(filename, ".ts");
+ let contents = await Bun.file(filename).text();
+
+ contents = applyGlobalReplacements(contents);
+
+ // first approach doesnt work perfectly because we actually need to split each function declaration
+ // and then compile those separately
+
+ const consumeWhitespace = /^\s*/;
+ const consumeTopLevelContent = /^(\/\*|\/\/|type|import|interface|\$|export (?:async )?function|(?:async )?function)/;
+ const consumeEndOfType = /;|.(?=export|type|interface|\$|\/\/|\/\*|function)/;
+
+ const functions: ParsedBuiltin[] = [];
+ let directives: Record<string, any> = {};
+ const bundledFunctions: BundledBuiltin[] = [];
+ let internal = false;
+
+ while (contents.length) {
+ contents = contents.replace(consumeWhitespace, "");
+ if (!contents.length) break;
+ const match = contents.match(consumeTopLevelContent);
+ if (!match) {
+ throw new SyntaxError("Could not process input:\n" + contents.slice(0, contents.indexOf("\n")));
+ }
+ contents = contents.slice(match.index!);
+ if (match[1] === "import") {
+ // TODO: we may want to do stuff with these
+ const i = contents.indexOf(";");
+ contents = contents.slice(i + 1);
+ } else if (match[1] === "/*") {
+ const i = contents.indexOf("*/") + 2;
+ internal ||= contents.slice(0, i).includes("@internal");
+ contents = contents.slice(i);
+ } else if (match[1] === "//") {
+ const i = contents.indexOf("\n") + 1;
+ internal ||= contents.slice(0, i).includes("@internal");
+ contents = contents.slice(i);
+ } else if (match[1] === "type" || match[1] === "export type") {
+ const i = contents.search(consumeEndOfType);
+ contents = contents.slice(i + 1);
+ } else if (match[1] === "interface") {
+ contents = sliceSourceCode(contents, false).rest;
+ } else if (match[1] === "$") {
+ const directive = contents.match(/^\$([a-zA-Z0-9]+)(?:\s*=\s*([^\n]+?))?\s*;?\n/);
+ if (!directive) {
+ throw new SyntaxError("Could not parse directive:\n" + contents.slice(0, contents.indexOf("\n")));
+ }
+ const name = directive[1];
+ let value;
+ try {
+ value = directive[2] ? JSON.parse(directive[2]) : true;
+ } catch (error) {
+ throw new SyntaxError("Could not parse directive value " + directive[2] + " (must be JSON parsable)");
+ }
+ if (name === "constructor") {
+ directives.ConstructAbility = "CanConstruct";
+ } else if (name === "nakedConstructor") {
+ directives.ConstructAbility = "CanConstruct";
+ directives.ConstructKind = "Naked";
+ } else {
+ directives[name] = value;
+ }
+ contents = contents.slice(directive[0].length);
+ } else if (match[1] === "export function" || match[1] === "export async function") {
+ const declaration = contents.match(
+ /^export\s+(async\s+)?function\s+([a-zA-Z0-9]+)\s*\(([^)]*)\)(?:\s*:\s*([^{\n]+))?\s*{?/,
+ );
+ if (!declaration)
+ throw new SyntaxError("Could not parse function declaration:\n" + contents.slice(0, contents.indexOf("\n")));
+
+ const async = !!declaration[1];
+ const name = declaration[2];
+ const paramString = declaration[3];
+ const params =
+ paramString.trim().length === 0 ? [] : paramString.split(",").map(x => x.replace(/:.+$/, "").trim());
+ if (params[0] === "this") {
+ params.shift();
+ }
+
+ const { result, rest } = sliceSourceCode(contents.slice(declaration[0].length - 1), true, x =>
+ requireTransformer(x, SRC_DIR + "/" + basename),
+ );
+
+ functions.push({
+ name,
+ params,
+ directives,
+ source: result.trim().slice(2, -1),
+ async,
+ });
+ contents = rest;
+ directives = {};
+ } else if (match[1] === "function" || match[1] === "async function") {
+ const fnname = contents.match(/^function ([a-zA-Z0-9]+)\(([^)]*)\)(?:\s*:\s*([^{\n]+))?\s*{?/)![1];
+ throw new SyntaxError("All top level functions must be exported: " + fnname);
+ } else {
+ throw new Error("TODO: parse " + match[1]);
+ }
+ }
+
+ for (const fn of functions) {
+ const tmpFile = path.join(TMP_DIR, `${basename}.${fn.name}.ts`);
+
+ // not sure if this optimization works properly in jsc builtins
+ // const useThis = fn.usesThis;
+ const useThis = true;
+
+ // TODO: we should use format=IIFE so we could bundle imports and extra functions.
+ await Bun.write(
+ tmpFile,
+ `// @ts-nocheck
+// GENERATED TEMP FILE - DO NOT EDIT
+// Sourced from ${path.relative(TMP_DIR, filename)}
+
+// do not allow the bundler to rename a symbol to $
+($);
+
+$$capture_start$$(${fn.async ? "async " : ""}${
+ useThis
+ ? `function(${fn.params.join(",")})`
+ : `${fn.params.length === 1 ? fn.params[0] : `(${fn.params.join(",")})`}=>`
+ } {${fn.source}}).$$capture_end$$;
+`,
+ );
+ await Bun.sleep(1);
+ const build = await Bun.build({
+ entrypoints: [tmpFile],
+ define,
+ minify: { syntax: true, whitespace: false },
+ });
+ if (!build.success) {
+ throw new AggregateError(build.logs, "Failed bundling builtin function " + fn.name + " from " + basename + ".ts");
+ }
+ if (build.outputs.length !== 1) {
+ throw new Error("expected one output");
+ }
+ const output = await build.outputs[0].text();
+ const captured = output.match(/\$\$capture_start\$\$([\s\S]+)\.\$\$capture_end\$\$/)![1];
+ const finalReplacement =
+ (fn.directives.sloppy ? captured : captured.replace(/function\s*\(.*?\)\s*{/, '$&"use strict";'))
+ .replace(/^\((async )?function\(/, "($1function (")
+ // .replace(/__intrinsic__lazy\(/g, "globalThis[globalThis.Symbol.for('Bun.lazy')](")
+ .replace(/__intrinsic__/g, "@") + "\n";
+
+ bundledFunctions.push({
+ name: fn.name,
+ directives: fn.directives,
+ source: finalReplacement,
+ params: fn.params,
+ visibility: fn.directives.visibility ?? (fn.directives.linkTimeConstant ? "Private" : "Public"),
+ isGetter: !!fn.directives.getter,
+ constructAbility: fn.directives.ConstructAbility ?? "CannotConstruct",
+ constructKind: fn.directives.ConstructKind ?? "None",
+ isLinkTimeConstant: !!fn.directives.linkTimeConstant,
+ intrinsic: fn.directives.intrinsic ?? "NoIntrinsic",
+ overriddenName: fn.directives.getter
+ ? `"get ${fn.name}"_s`
+ : fn.directives.overriddenName
+ ? `"${fn.directives.overriddenName}"_s`
+ : "ASCIILiteral()",
+ });
+ }
+
+ return {
+ functions: bundledFunctions.sort((a, b) => a.name.localeCompare(b.name)),
+ internal,
+ };
+}
+
+const filesToProcess = readdirSync(SRC_DIR)
+ .filter(x => x.endsWith(".ts") && !x.endsWith(".d.ts"))
+ .sort();
+
+const files: Array<{ basename: string; functions: BundledBuiltin[]; internal: boolean }> = [];
+async function processFile(x: string) {
+ const basename = path.basename(x, ".ts");
+ try {
+ files.push({
+ basename,
+ ...(await processFileSplit(path.join(SRC_DIR, x))),
+ });
+ } catch (error) {
+ console.error("Failed to process file: " + basename + ".ts");
+ console.error(error);
+ process.exit(1);
+ }
+}
+
+// Bun seems to crash if this is parallelized, :(
+if (PARALLEL) {
+ await Promise.all(filesToProcess.map(processFile));
+} else {
+ for (const x of filesToProcess) {
+ await processFile(x);
+ }
+}
+
+// C++ codegen
+let bundledCPP = `// Generated by \`bun src/js/builtins/codegen\`
+// Do not edit by hand.
+namespace Zig { class GlobalObject; }
+#include "root.h"
+#include "config.h"
+#include "JSDOMGlobalObject.h"
+#include "WebCoreJSClientData.h"
+#include <JavaScriptCore/JSObjectInlines.h>
+
+namespace WebCore {
+
+`;
+
+for (const { basename, functions } of files) {
+ bundledCPP += `/* ${basename}.ts */\n`;
+ const lowerBasename = low(basename);
+ for (const fn of functions) {
+ const name = `${lowerBasename}${cap(fn.name)}Code`;
+ bundledCPP += `// ${fn.name}
+const JSC::ConstructAbility s_${name}ConstructAbility = JSC::ConstructAbility::${fn.constructAbility};
+const JSC::ConstructorKind s_${name}ConstructorKind = JSC::ConstructorKind::${fn.constructKind};
+const JSC::ImplementationVisibility s_${name}ImplementationVisibility = JSC::ImplementationVisibility::${fn.visibility};
+const int s_${name}Length = ${fn.source.length};
+static const JSC::Intrinsic s_${name}Intrinsic = JSC::NoIntrinsic;
+const char* const s_${name} = ${fmtCPPString(fn.source)};
+
+`;
+ }
+ bundledCPP += `#define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \\
+JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \\
+{\\
+ JSVMClientData* clientData = static_cast<JSVMClientData*>(vm.clientData); \\
+ return clientData->builtinFunctions().${lowerBasename}Builtins().codeName##Executable()->link(vm, nullptr, clientData->builtinFunctions().${lowerBasename}Builtins().codeName##Source(), std::nullopt, s_##codeName##Intrinsic); \\
+}
+WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(DEFINE_BUILTIN_GENERATOR)
+#undef DEFINE_BUILTIN_GENERATOR
+
+`;
+}
+
+bundledCPP += `
+
+JSBuiltinInternalFunctions::JSBuiltinInternalFunctions(JSC::VM& vm)
+ : m_vm(vm)
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledCPP += ` , m_${low(basename)}(vm)\n`;
+ }
+}
+
+bundledCPP += `
+{
+ UNUSED_PARAM(vm);
+}
+
+template<typename Visitor>
+void JSBuiltinInternalFunctions::visit(Visitor& visitor)
+{
+`;
+for (const { basename, internal } of files) {
+ if (internal) bundledCPP += ` m_${low(basename)}.visit(visitor);\n`;
+}
+
+bundledCPP += `
+ UNUSED_PARAM(visitor);
+}
+
+template void JSBuiltinInternalFunctions::visit(AbstractSlotVisitor&);
+template void JSBuiltinInternalFunctions::visit(SlotVisitor&);
+
+SUPPRESS_ASAN void JSBuiltinInternalFunctions::initialize(Zig::GlobalObject& globalObject)
+{
+ UNUSED_PARAM(globalObject);
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledCPP += ` m_${low(basename)}.init(globalObject);\n`;
+ }
+}
+
+bundledCPP += `
+ JSVMClientData& clientData = *static_cast<JSVMClientData*>(m_vm.clientData);
+ Zig::GlobalObject::GlobalPropertyInfo staticGlobals[] = {
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledCPP += `#define DECLARE_GLOBAL_STATIC(name) \\
+ Zig::GlobalObject::GlobalPropertyInfo( \\
+ clientData.builtinFunctions().${low(basename)}Builtins().name##PrivateName(), ${low(
+ basename,
+ )}().m_##name##Function.get() , JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly),
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(DECLARE_GLOBAL_STATIC)
+ #undef DECLARE_GLOBAL_STATIC
+ `;
+ }
+}
+
+bundledCPP += `
+ };
+ globalObject.addStaticGlobals(staticGlobals, std::size(staticGlobals));
+ UNUSED_PARAM(clientData);
+}
+
+} // namespace WebCore
+`;
+
+// C++ Header codegen
+let bundledHeader = `// Generated by \`bun src/js/builtins/codegen\`
+// Do not edit by hand.
+#pragma once
+namespace Zig { class GlobalObject; }
+#include "root.h"
+#include <JavaScriptCore/BuiltinUtils.h>
+#include <JavaScriptCore/Identifier.h>
+#include <JavaScriptCore/JSFunction.h>
+#include <JavaScriptCore/UnlinkedFunctionExecutable.h>
+#include <JavaScriptCore/VM.h>
+#include <JavaScriptCore/WeakInlines.h>
+
+namespace JSC {
+class FunctionExecutable;
+}
+
+namespace WebCore {
+`;
+for (const { basename, functions, internal } of files) {
+ bundledHeader += `/* ${basename}.ts */
+`;
+ const lowerBasename = low(basename);
+
+ for (const fn of functions) {
+ const name = `${lowerBasename}${cap(fn.name)}Code`;
+ bundledHeader += `// ${fn.name}
+#define WEBCORE_BUILTIN_${basename.toUpperCase()}_${fn.name.toUpperCase()} 1
+extern const char* const s_${name};
+extern const int s_${name}Length;
+extern const JSC::ConstructAbility s_${name}ConstructAbility;
+extern const JSC::ConstructorKind s_${name}ConstructorKind;
+extern const JSC::ImplementationVisibility s_${name}ImplementationVisibility;
+
+`;
+ }
+ bundledHeader += `#define WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_DATA(macro) \\\n`;
+ for (const fn of functions) {
+ bundledHeader += ` macro(${fn.name}, ${lowerBasename}${cap(fn.name)}, ${fn.params.length}) \\\n`;
+ }
+ bundledHeader += "\n";
+ bundledHeader += `#define WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(macro) \\\n`;
+ for (const fn of functions) {
+ const name = `${lowerBasename}${cap(fn.name)}Code`;
+ bundledHeader += ` macro(${name}, ${fn.name}, ${fn.overriddenName}, s_${name}Length) \\\n`;
+ }
+ bundledHeader += "\n";
+ bundledHeader += `#define WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(macro) \\\n`;
+ for (const fn of functions) {
+ bundledHeader += ` macro(${fn.name}) \\\n`;
+ }
+ bundledHeader += `
+#define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \\
+ JSC::FunctionExecutable* codeName##Generator(JSC::VM&);
+
+WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(DECLARE_BUILTIN_GENERATOR)
+#undef DECLARE_BUILTIN_GENERATOR
+
+class ${basename}BuiltinsWrapper : private JSC::WeakHandleOwner {
+public:
+ explicit ${basename}BuiltinsWrapper(JSC::VM& vm)
+ : m_vm(vm)
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(INITIALIZE_BUILTIN_NAMES)
+#define INITIALIZE_BUILTIN_SOURCE_MEMBERS(name, functionName, overriddenName, length) , m_##name##Source(JSC::makeSource(StringImpl::createWithoutCopying(s_##name, length), { }, JSC::SourceTaintedOrigin::Untainted))
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(INITIALIZE_BUILTIN_SOURCE_MEMBERS)
+#undef INITIALIZE_BUILTIN_SOURCE_MEMBERS
+ {
+ }
+
+#define EXPOSE_BUILTIN_EXECUTABLES(name, functionName, overriddenName, length) \\
+ JSC::UnlinkedFunctionExecutable* name##Executable(); \\
+ const JSC::SourceCode& name##Source() const { return m_##name##Source; }
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(EXPOSE_BUILTIN_EXECUTABLES)
+#undef EXPOSE_BUILTIN_EXECUTABLES
+
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(DECLARE_BUILTIN_IDENTIFIER_ACCESSOR)
+
+ void exportNames();
+
+private:
+ JSC::VM& m_vm;
+
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(DECLARE_BUILTIN_NAMES)
+
+#define DECLARE_BUILTIN_SOURCE_MEMBERS(name, functionName, overriddenName, length) \\
+ JSC::SourceCode m_##name##Source;\\
+ JSC::Weak<JSC::UnlinkedFunctionExecutable> m_##name##Executable;
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(DECLARE_BUILTIN_SOURCE_MEMBERS)
+#undef DECLARE_BUILTIN_SOURCE_MEMBERS
+
+};
+
+#define DEFINE_BUILTIN_EXECUTABLES(name, functionName, overriddenName, length) \\
+inline JSC::UnlinkedFunctionExecutable* ${basename}BuiltinsWrapper::name##Executable() \\
+{\\
+ if (!m_##name##Executable) {\\
+ JSC::Identifier executableName = functionName##PublicName();\\
+ if (overriddenName)\\
+ executableName = JSC::Identifier::fromString(m_vm, overriddenName);\\
+ m_##name##Executable = JSC::Weak<JSC::UnlinkedFunctionExecutable>(JSC::createBuiltinExecutable(m_vm, m_##name##Source, executableName, s_##name##ImplementationVisibility, s_##name##ConstructorKind, s_##name##ConstructAbility), this, &m_##name##Executable);\\
+ }\\
+ return m_##name##Executable.get();\\
+}
+WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(DEFINE_BUILTIN_EXECUTABLES)
+#undef DEFINE_BUILTIN_EXECUTABLES
+
+inline void ${basename}BuiltinsWrapper::exportNames()
+{
+#define EXPORT_FUNCTION_NAME(name) m_vm.propertyNames->appendExternalName(name##PublicName(), name##PrivateName());
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(EXPORT_FUNCTION_NAME)
+#undef EXPORT_FUNCTION_NAME
+}
+`;
+
+ if (internal) {
+ bundledHeader += `class ${basename}BuiltinFunctions {
+public:
+ explicit ${basename}BuiltinFunctions(JSC::VM& vm) : m_vm(vm) { }
+
+ void init(JSC::JSGlobalObject&);
+ template<typename Visitor> void visit(Visitor&);
+
+public:
+ JSC::VM& m_vm;
+
+#define DECLARE_BUILTIN_SOURCE_MEMBERS(functionName) \\
+ JSC::WriteBarrier<JSC::JSFunction> m_##functionName##Function;
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(DECLARE_BUILTIN_SOURCE_MEMBERS)
+#undef DECLARE_BUILTIN_SOURCE_MEMBERS
+};
+
+inline void ${basename}BuiltinFunctions::init(JSC::JSGlobalObject& globalObject)
+{
+#define EXPORT_FUNCTION(codeName, functionName, overriddenName, length) \\
+ m_##functionName##Function.set(m_vm, &globalObject, JSC::JSFunction::create(m_vm, codeName##Generator(m_vm), &globalObject));
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(EXPORT_FUNCTION)
+#undef EXPORT_FUNCTION
+}
+
+template<typename Visitor>
+inline void ${basename}BuiltinFunctions::visit(Visitor& visitor)
+{
+#define VISIT_FUNCTION(name) visitor.append(m_##name##Function);
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(VISIT_FUNCTION)
+#undef VISIT_FUNCTION
+}
+
+template void ${basename}BuiltinFunctions::visit(JSC::AbstractSlotVisitor&);
+template void ${basename}BuiltinFunctions::visit(JSC::SlotVisitor&);
+ `;
+ }
+}
+bundledHeader += `class JSBuiltinFunctions {
+public:
+ explicit JSBuiltinFunctions(JSC::VM& vm)
+ : m_vm(vm)
+`;
+
+for (const { basename } of files) {
+ bundledHeader += ` , m_${low(basename)}Builtins(m_vm)\n`;
+}
+
+bundledHeader += `
+ {
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledHeader += ` m_${low(basename)}Builtins.exportNames();\n`;
+ }
+}
+
+bundledHeader += ` }
+`;
+
+for (const { basename } of files) {
+ bundledHeader += ` ${basename}BuiltinsWrapper& ${low(basename)}Builtins() { return m_${low(
+ basename,
+ )}Builtins; }\n`;
+}
+
+bundledHeader += `
+private:
+ JSC::VM& m_vm;
+`;
+
+for (const { basename } of files) {
+ bundledHeader += ` ${basename}BuiltinsWrapper m_${low(basename)}Builtins;\n`;
+}
+
+bundledHeader += `;
+};
+
+class JSBuiltinInternalFunctions {
+public:
+ explicit JSBuiltinInternalFunctions(JSC::VM&);
+
+ template<typename Visitor> void visit(Visitor&);
+ void initialize(Zig::GlobalObject&);
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledHeader += ` ${basename}BuiltinFunctions& ${low(basename)}() { return m_${low(basename)}; }\n`;
+ }
+}
+
+bundledHeader += `
+private:
+ JSC::VM& m_vm;
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledHeader += ` ${basename}BuiltinFunctions m_${low(basename)};\n`;
+ }
+}
+
+bundledHeader += `
+};
+
+} // namespace WebCore
+`;
+
+writeIfNotChanged(path.join(OUT_DIR, "WebCoreJSBuiltins.h"), bundledHeader);
+writeIfNotChanged(path.join(OUT_DIR, "WebCoreJSBuiltins.cpp"), bundledCPP);
+
+// Generate TS types
+let dts = `// Generated by \`bun src/js/builtins/codegen\`
+// Do not edit by hand.
+type RemoveThis<F> = F extends (this: infer T, ...args: infer A) => infer R ? (...args: A) => R : F;
+`;
+
+for (const { basename, functions, internal } of files) {
+ if (internal) {
+ dts += `\n// ${basename}.ts\n`;
+ for (const fn of functions) {
+ dts += `declare const \$${fn.name}: RemoveThis<typeof import("${path.relative(
+ OUT_DIR,
+ path.join(SRC_DIR, basename),
+ )}")[${JSON.stringify(fn.name)}]>;\n`;
+ }
+ }
+}
+
+writeIfNotChanged(path.join(OUT_DIR, "WebCoreJSBuiltins.d.ts"), dts);
+
+const totalJSSize = files.reduce(
+ (acc, { functions }) => acc + functions.reduce((acc, fn) => acc + fn.source.length, 0),
+ 0,
+);
+
+if (!KEEP_TMP) {
+ await rmSync(TMP_DIR, { recursive: true });
+}
+
+console.log(
+ `Embedded JS size: %s bytes (across %s functions, %s files)`,
+ totalJSSize,
+ files.reduce((acc, { functions }) => acc + functions.length, 0),
+ files.length,
+);
+console.log(`[${performance.now().toFixed(1)}ms]`);