Skip to content

Commit

Permalink
stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
paperdave committed Jul 28, 2023
1 parent 559cef2 commit 230f5bb
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 199 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ cold-jsc-start.d

/test.ts

src/js/out/modules
src/js/out/functions
src/js/out/modules*
src/js/out/functions*
src/js/out/tmp

make-dev-stats.csv
9 changes: 7 additions & 2 deletions src/js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,14 @@ V8 has a [similar feature](https://v8.dev/blog/embedded-builtins) to this syntax
On top of this, we have some special functions that are handled by the bundle preprocessor:

- `require` works, but it must be a string literal that resolves to a module within src/js. This call gets replaced with `$requireId(id)`, which is a special function that skips the module resolver and directly loads the module by it's generated numerical ID.
- `$debug` is exactly like console.log, but is stripped in release builds. It is disabled by default, requiring you to pass one of: `BUN_DEBUG_MODULE_NAME=1`, `BUN_DEBUG_JS=1`, or `BUN_DEBUG_ALL=1`.

- `$debug` is exactly like console.log, but is stripped in release builds. It is disabled by default, requiring you to pass one of: `BUN_DEBUG_MODULE_NAME=1`, `BUN_DEBUG_JS=1`, or `BUN_DEBUG_ALL=1`. You can also do `if($debug) {}` to check if debug env var is set.

- `IS_BUN_DEVELOPMENT` is inlined to be `true` in all development builds.

- `process.platform` is properly inlined and DCE'd. Do use this to run different code on different platforms.
- `$bundleError` is like Zig's `@compileError`. It will stop a compile from succeeding.

- `$bundleError()` is like Zig's `@compileError`. It will stop a compile from succeeding.

## Builtin Modules

Expand Down
53 changes: 24 additions & 29 deletions src/js/_codegen/build-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from "fs";
import path from "path";
import { sliceSourceCode } from "./builtin-parser";
import { cap, fmtCPPString, readdirRecursive, resolveSyncOrNull } from "./helpers";
import { createLogClient } from "./log-client";
import { createAssertClientJS, createLogClientJS } from "./client-js";

let start = performance.now();
function mark(log: string) {
Expand Down Expand Up @@ -130,6 +130,7 @@ const config = ({ platform, debug }: { platform: typeof process.platform; debug?
root: TMP,
define: {
IS_BUN_DEVELOPMENT: String(!!debug),
__intrinsic__debug: debug ? "$debug_log_enabled" : "false",
"process.platform": `"${platform}"`,
},
});
Expand All @@ -153,44 +154,38 @@ const bundledOutputs = {
win32: new Map(),
};

for (const [bundle, outputs] of [
[bundled_host, bundledOutputs.host],
[bundled_linux, bundledOutputs.linux],
[bundled_darwin, bundledOutputs.darwin],
[bundled_win32, bundledOutputs.win32],
for (const [name, bundle, outputs] of [
["modules_dev", bundled_host, bundledOutputs.host],
["modules_linux", bundled_linux, bundledOutputs.linux],
["modules_darwin", bundled_darwin, bundledOutputs.darwin],
["modules_win32", bundled_win32, bundledOutputs.win32],
] as const) {
for (const file of bundle.outputs) {
const output = await file.text();
let captured = output.match(/\$\$capture_start\$\$([\s\S]+)\.\$\$capture_end\$\$/)![1];
let usesDebug = false;
let usesDebug = output.includes("$debug_log");
let usesAssert = output.includes("$assert");
captured =
captured
.replace(/^\((async )?function\(/, "($1function (")
.replace(/__debug_start\(\s*\[\s*\]\s*,\s*__debug_end__\)\s*,?\s*/g, "")
.replace(/__debug_start\(\s*\[/g, () => {
usesDebug = true;
return "$log(";
})
.replace(/]\s*,\s*__(debug|assert)_end__\)/g, ")")
.replace(/]\s*,\s*__debug_end__\)/g, ")")
.replace(/__intrinsic__lazy\(/g, "globalThis[globalThis.Symbol.for('Bun.lazy')](")
.replace(/__intrinsic__/g, "@") + "\n";
if (usesDebug) {
captured = captured.replace(
/function\s*\(.*?\)\s*{/,
'$&"use strict";' +
createLogClient(
file.path.replace(".js", ""),
idToPublicSpecifierOrEnumName(file.path).replace(/^node:|^bun:/, ""),
),
);
} else {
captured = captured.replace(/function\s*\(.*?\)\s*{/, '$&"use strict";');
}
const outputPath = path.join(BASE, "out/modules", file.path);
if (bundle === bundled_host) {
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, captured);
}
captured = captured.replace(
/function\s*\(.*?\)\s*{/,
'$&"use strict";' +
(usesDebug
? createLogClientJS(
file.path.replace(".js", ""),
idToPublicSpecifierOrEnumName(file.path).replace(/^node:|^bun:/, ""),
)
: "") +
(usesAssert ? createAssertClientJS(idToPublicSpecifierOrEnumName(file.path).replace(/^node:|^bun:/, "")) : ""),
);
const outputPath = path.join(BASE, "out", name, file.path);
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, captured);
outputs.set(file.path.replace(".js", ""), captured);
}
}
Expand Down
14 changes: 13 additions & 1 deletion src/js/_codegen/builtin-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ export function sliceSourceCode(
contents: string,
replace: boolean,
replaceRequire?: (specifier: string) => string,
endOnComma = false,
): { result: string; rest: string } {
let bracketCount = 0;
let i = 0;
let result = "";
while (contents.length) {
const match = contents.match(/([(,=;:{]\s*)\/[^\/\*]|\/\*|\/\/|['"}`\)]|(?<!\$)\brequire\(/);
const match = contents.match(
endOnComma && bracketCount <= 1
? /([(,=;:{]\s*)\/[^\/\*]|\/\*|\/\/|['"}`\),]|(?<!\$)\brequire\(/
: /([(,=;:{]\s*)\/[^\/\*]|\/\*|\/\/|['"}`\)]|(?<!\$)\brequire\(/,
);
i = match?.index ?? contents.length;
bracketCount += [...contents.slice(0, i).matchAll(/[({]/g)].length;
const chunk = replace ? applyReplacements(contents, i) : [contents.slice(0, i), contents.slice(0, i)];
Expand Down Expand Up @@ -67,6 +72,13 @@ export function sliceSourceCode(
break;
}
i = 1;
} else if (endOnComma && contents.startsWith(",")) {
if (bracketCount <= 1) {
result += ",";
contents = contents.slice(1);
break;
}
i = 1;
} else if (contents.startsWith("require(")) {
if (replaceRequire) {
const staticSpecifier = contents.match(/\brequire\(["']([^"']+)["']\)/);
Expand Down
37 changes: 37 additions & 0 deletions src/js/_codegen/client-js.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// This is the implementation for $debug
export function createLogClientJS(filepath: string, publicName: string) {
return `
let $debug_log_enabled = ((env) => (
// The rationale for checking all these variables is just so you don't have to exactly remember which one you set.
(env.BUN_DEBUG_ALL && env.BUN_DEBUG_ALL !== '0')
|| (env.BUN_DEBUG_${filepath
.replace(/^.*?:/, "")
.split(/[-_./]/g)
.join("_")
.toUpperCase()})
|| (env.DEBUG_${filepath
.replace(/^.*?:/, "")
.split(/[-_./]/g)
.join("_")
.toUpperCase()})
))(@Bun.env);
let $debug_log = $debug_log_enabled ? (...args) => {
// warn goes to stderr without colorizing
console.warn(Bun.enableANSIColors ? '\\x1b[90m[${publicName}]\\x1b[0m' : '[${publicName}]', ...args);
} : () => {};
`;
}

export function createAssertClientJS(publicName: string) {
return `
let $assert = function(check, sourceString, ...message) {
if (!check) {
console.error('[${publicName}] ASSERTION FAILED: ' + sourceString);
if(message.length)console.warn (' ${" ".repeat(publicName.length)}', ...message);
const e = new Error(sourceString);
e.name = 'AssertionError';
throw e;
}
}
`;
}
24 changes: 0 additions & 24 deletions src/js/_codegen/log-client.ts

This file was deleted.

36 changes: 26 additions & 10 deletions src/js/_codegen/replacements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,33 @@ export function applyReplacements(src: string, length: number) {
slice = slice.replace(replacement.from, replacement.to.replaceAll("$", "__intrinsic__"));
}
let match;
if ((match = slice.match(/__intrinsic__debug\(/))) {
if ((match = slice.match(/__intrinsic__(debug|assert)\(/))) {
const name = match[1];
const combined = slice + rest;
const innerSlice = sliceSourceCode(combined.slice(match.index + match[0].length), true);
return [
slice.slice(0, match.index) +
"__debug_start(IS_BUN_DEVELOPMENT?[" +
innerSlice.result.slice(0, -1) +
"]:[],__debug_end__)",
combined.slice(match.index + match[0].length + innerSlice.result.length),
true,
];
if (name === "debug") {
const innerSlice = sliceSourceCode("(" + combined.slice(match.index + match[0].length), true);
return [
slice.slice(0, match.index) + "(IS_BUN_DEVELOPMENT?$debug_log" + innerSlice.result + ":void 0)",
innerSlice.rest.slice(1),
true,
];
} else if (name === "assert") {
const checkSlice = sliceSourceCode("(" + combined.slice(match.index + match[0].length), true, undefined, true);
const messageSlice =
!checkSlice.result.endsWith(")") &&
sliceSourceCode("(" + combined.slice(match.index + match[0].length + checkSlice.result.length), true);
return [
slice.slice(0, match.index) +
"(IS_BUN_DEVELOPMENT?$assert(" +
checkSlice.result.slice(1, -1) +
"," +
JSON.stringify(checkSlice.result.slice(1, -1).replace(/__intrinsic__/g, "$")) +
(messageSlice ? "," + messageSlice.result.slice(1, -1) : "") +
"):void 0)",
(messageSlice || checkSlice).rest,
true,
];
}
}
return [slice, rest, false];
}
Expand Down
7 changes: 5 additions & 2 deletions src/js/builtins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ type TODO = any;
* This only works in debug builds, the log fn is completely removed in release builds.
*/
declare function $debug(...args: any[]): void;
/** $assert is a preprocessor macro that only runs in debug mode. it throws an error if the first argument is falsy.
* The source code passed to `check` is inlined in the message, but in addition you can pass additional messages.
*/
declare function $assert(check: any, ...message: any[]): void;

/** Place this directly above a function declaration (like a decorator) to make it a getter. */
declare const $getter: never;
Expand All @@ -34,8 +38,6 @@ declare function $extractHighWaterMarkFromQueuingStrategyInit(obj: any): any;
// And implemented here: (search for "emit_intrinsic_<name>", like "emit_intrinsic_arrayPush")
// https://github.com/WebKit/WebKit/blob/main/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

/** Assert a value is true */
declare function $assert(index: any): void;
/** returns `arguments[index]` */
declare function $argument<T = any>(index: number): any;
/** returns number of arguments */
Expand Down Expand Up @@ -364,6 +366,7 @@ declare function $removeEventListener(): TODO;
declare function $require(): TODO;
declare function $requireESM(path: string): any;
declare const $requireMap: Map<string, NodeModule>;
declare const $internalModuleRegistry: InternalFieldObject<any[]>;
declare function $resolve(name: string, from: string): Promise<string>;
declare function $resolveSync(name: string, from: string, isESM?: boolean): string;
declare function $resume(): TODO;
Expand Down
9 changes: 9 additions & 0 deletions src/js/builtins/Module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ export function require(this: CommonJSModuleRecord, id: string) {
return mod.exports;
}

export function requireId(id: number) {
let module = $getInternalField($internalModuleRegistry, id);
if (!module) {
module = $loadInternalModuleById(id);
$putInternalField($internalModuleRegistry, id, module);
}
return module;
}

export function requireResolve(this: CommonJSModuleRecord, id: string) {
return $resolveSync(id, this.path, false);
}
1 change: 1 addition & 0 deletions src/js/node/async_hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class AsyncLocalStorage {
set(context);
}
try {
$debug("Running with context value", get());
return callback(...args);
} catch (e) {
throw e;
Expand Down
26 changes: 9 additions & 17 deletions src/js/node/child_process.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,9 @@ var Uint8ArrayPrototypeIncludes = Uint8Array.prototype.includes;

const MAX_BUFFER = 1024 * 1024;

// General debug vs tracking stdio streams. Useful for stream debugging in particular
const __DEBUG__ = process.env.DEBUG || false;

// You can use this env var along with `process.env.DEBUG_TRACK_EE` to debug stdio streams
// Just set `DEBUG_TRACK_EE=PARENT_STDOUT-0, PARENT_STDOUT-1`, etc. and `DEBUG_STDIO=1` and you will be able to track particular stdio streams
// TODO: Add ability to track a range of IDs rather than just enumerated ones
const __TRACK_STDIO__ = process.env.DEBUG_STDIO;
const debug = __DEBUG__ ? console.log : () => {};

if (__TRACK_STDIO__) {
debug("child_process: debug mode on");
// Pass DEBUG_CHILD_PROCESS=1 to enable debug output
if ($debug) {
$debug("child_process: debug mode on");
globalThis.__lastId = null;
globalThis.__getId = () => {
return globalThis.__lastId !== null ? globalThis.__lastId++ : 0;
Expand Down Expand Up @@ -159,7 +151,7 @@ function spawn(file, args, options) {
const killSignal = sanitizeKillSignal(options.killSignal);
const child = new ChildProcess();

debug("spawn", options);
$debug("spawn", options);
child.spawn(options);

if (options.timeout > 0) {
Expand Down Expand Up @@ -548,7 +540,7 @@ function spawnSync(file, args, options) {
const maxBuffer = options.maxBuffer;
const encoding = options.encoding;

debug("spawnSync", options);
$debug("spawnSync", options);

// Validate the timeout, if present.
validateTimeout(options.timeout);
Expand Down Expand Up @@ -949,11 +941,11 @@ class ChildProcess extends EventEmitter {
}

#getBunSpawnIo(i, encoding) {
if (__DEBUG__ && !this.#handle) {
if ($debug && !this.#handle) {
if (this.#handle === null) {
debug("ChildProcess: getBunSpawnIo: this.#handle is null. This means the subprocess already exited");
$debug("ChildProcess: getBunSpawnIo: this.#handle is null. This means the subprocess already exited");
} else {
debug("ChildProcess: getBunSpawnIo: this.#handle is undefined");
$debug("ChildProcess: getBunSpawnIo: this.#handle is undefined");
}
}

Expand Down Expand Up @@ -1157,7 +1149,7 @@ class ChildProcess extends EventEmitter {
}

#maybeClose() {
debug("Attempting to maybe close...");
$debug("Attempting to maybe close...");
this.#closesGot++;
if (this.#closesGot === this.#closesNeeded) {
this.emit("close", this.exitCode, this.signalCode);
Expand Down
6 changes: 4 additions & 2 deletions src/js/node/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,9 @@ class EventEmitterAsyncResource extends EventEmitter {
asyncResource;

constructor(options) {
if (!AsyncResource) AsyncResource = require("node:async_hooks").AsyncResource;
if (!AsyncResource) {
AsyncResource = require("node:async_hooks").AsyncResource;
}
var { captureRejections = false, triggerAsyncId, name = new.target.name, requireManualDestroy } = options || {};
super({ captureRejections });
this.triggerAsyncId = triggerAsyncId ?? 0;
Expand Down Expand Up @@ -469,7 +471,7 @@ Object.assign(EventEmitter, {
EventEmitter,
usingDomains: false,
captureRejectionSymbol,
// EventEmitterAsyncResource,
EventEmitterAsyncResource,
errorMonitor: kErrorMonitor,
setMaxListeners,
init: EventEmitter,
Expand Down
Loading

0 comments on commit 230f5bb

Please sign in to comment.