diff options
author | 2022-11-25 06:48:02 -0800 | |
---|---|---|
committer | 2022-11-25 06:48:02 -0800 | |
commit | e851e5fddba5748c70338c60752ff6567ac9bf86 (patch) | |
tree | 70b58c3fd6d33652ab3bb2d478638ff2533026c8 | |
parent | 1aff60d2ba3f7f5db61cb0fad35381fe63e8909e (diff) | |
download | bun-e851e5fddba5748c70338c60752ff6567ac9bf86.tar.gz bun-e851e5fddba5748c70338c60752ff6567ac9bf86.tar.zst bun-e851e5fddba5748c70338c60752ff6567ac9bf86.zip |
Fix macros that return a Promise
-rw-r--r-- | src/bun.js/bindings/coroutine.cpp | 42 | ||||
-rw-r--r-- | src/js_ast.zig | 61 | ||||
-rw-r--r-- | test/bun.js/inline.macro.js | 16 | ||||
-rw-r--r-- | test/bun.js/transpiler.test.js | 85 |
4 files changed, 166 insertions, 38 deletions
diff --git a/src/bun.js/bindings/coroutine.cpp b/src/bun.js/bindings/coroutine.cpp new file mode 100644 index 000000000..a58804d7a --- /dev/null +++ b/src/bun.js/bindings/coroutine.cpp @@ -0,0 +1,42 @@ +#include "root.h" + +// #include "mimalloc.h" +#include "JavaScriptCore/VM.h" + +// #define MCO_API +// #define MCO_MALLOC mi_malloc +// #define MCO_FREE mi_free +// #define MCO_USE_ASM + +// #define MINICORO_IMPL +// #include "minicoro.h" + +typedef void* (*BunMacroFunction)(); + +// thread_local JSC::JSGlobalObject* globalObjectToUse; +// static void Bun__enterMacro(mco_coro* coro) +// { +// JSC::VM& vm = globalObjectToUse->vm(); +// JSC::JSLockHolder lock(vm); +// reinterpret_cast<BunMacroFunction>(coro->user_data)(); +// JSC::sanitizeStackForVM(vm); +// mco_yield(coro); +// } + +// TODO: figure out how to make coroutines work properly +// We tried using minicoro (https://github.com/edubart/minicoro) +// but it crashes when entering/exiting JavaScriptCore in "sanitizeStackForVMImpl" +// I don't want to block the release on this seldom-used feature of Bun +// we will just have stack overflow-risky macros for now. +extern "C" void Bun__startMacro(BunMacroFunction ctx, JSC::JSGlobalObject* globalObject) +{ + // globalObjectToUse = globalObject; + // JSC::JSLockHolder lock(globalObject->vm()); + ctx(); + // mco_coro* co; + // mco_desc desc = mco_desc_init(Bun__enterMacro, 1024 * 1024 * 2); + // desc.user_data = ctx; + // mco_result res = mco_create(&co, &desc); + // mco_resume(co); + // mco_destroy(co); +}
\ No newline at end of file diff --git a/src/js_ast.zig b/src/js_ast.zig index 0ca11272a..c7197a3b7 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -7790,7 +7790,7 @@ pub const Macro = struct { source: *const logger.Source, id: i32, visitor: Visitor, - ) callconv(.Async) MacroError!Expr { + ) MacroError!Expr { if (comptime is_bindgen) return undefined; var macro_callback = macro.vm.macros.get(id) orelse return caller; @@ -8044,9 +8044,7 @@ pub const Macro = struct { return Expr.init(E.Number, E.Number{ .value = value.asNumber() }, this.caller.loc); }, .String => { - var zig_str = value.getZigString(this.global); - zig_str.detectEncoding(); - var sliced = zig_str.toSlice(this.allocator); + var sliced = value.toSlice(this.allocator).cloneIfNeeded(this.allocator) catch unreachable; return Expr.init(E.String, E.String.init(sliced.slice()), this.caller.loc); }, .Promise => { @@ -8055,13 +8053,31 @@ pub const Macro = struct { return _entry.value_ptr.*; } - var promise = JSC.JSPromise.resolvedPromise(this.global, value); - while (promise.status(this.global.vm()) == .Pending) { - this.macro.vm.tick(); - } + var promise_result = JSC.JSValue.zero; + var rejected = false; + if (value.asPromise()) |promise| { + while (true) { + if (promise.status(this.global.vm()) != .Pending) break; + this.macro.vm.tick(); + if (promise.status(this.global.vm()) != .Pending) break; + this.macro.vm.eventLoop().autoTick(); + } - const rejected = promise.status(this.global.vm()) == .Rejected; - const promise_result = promise.result(this.global.vm()); + promise_result = promise.result(this.global.vm()); + rejected = promise.status(this.global.vm()) == .Rejected; + } else if (value.asInternalPromise()) |promise| { + while (true) { + if (promise.status(this.global.vm()) != .Pending) break; + this.macro.vm.tick(); + if (promise.status(this.global.vm()) != .Pending) break; + this.macro.vm.eventLoop().autoTick(); + } + + promise_result = promise.result(this.global.vm()); + rejected = promise.status(this.global.vm()) == .Rejected; + } else { + @panic("Unexpected promise type"); + } if (promise_result.isUndefined() and this.is_top_level) { this.is_top_level = false; @@ -8118,15 +8134,23 @@ pub const Macro = struct { args_buf[2] = null; const Run = NewRun(Visitor); - // Give it >= 256 KB stack space - // Cast to usize to ensure we get an 8 byte aligned pointer - const PooledFrame = ObjectPool([@maximum(@sizeOf(@Frame(Run.runAsync)), 1024 * 1024 * 2) / @sizeOf(usize)]usize, null, true, 1); - var pooled_frame = PooledFrame.get(default_allocator); - defer pooled_frame.release(); + const CallFunction = @TypeOf(Run.runAsync); + const CallArgs = std.meta.ArgsTuple(CallFunction); + const CallData = struct { + threadlocal var call_args: CallArgs = undefined; + threadlocal var result: MacroError!Expr = undefined; + pub fn callWrapper(args: CallArgs) MacroError!Expr { + call_args = args; + Bun__startMacro(call, JSC.VirtualMachine.vm.global); + return result; + } - var result: MacroError!Expr = error.MacroFailed; + pub fn call() callconv(.C) void { + result = @call(.{}, Run.runAsync, call_args); + } + }; - _ = nosuspend @asyncCall(std.mem.asBytes(&pooled_frame.data), &result, Run.runAsync, .{ + return CallData.callWrapper(.{ macro, log, allocator, @@ -8137,8 +8161,9 @@ pub const Macro = struct { id, visitor, }); - return result; } + + extern "C" fn Bun__startMacro(function: *const anyopaque, *anyopaque) void; }; }; diff --git a/test/bun.js/inline.macro.js b/test/bun.js/inline.macro.js index ff0292d0a..0fd7491b9 100644 --- a/test/bun.js/inline.macro.js +++ b/test/bun.js/inline.macro.js @@ -1,3 +1,19 @@ export function whatDidIPass(expr, ctx) { return ctx; } + +export function promiseReturningFunction(expr, ctx) { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(1); + }, 1); + }); +} + +export function promiseReturningCtx(expr, ctx) { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(ctx); + }, 1); + }); +} diff --git a/test/bun.js/transpiler.test.js b/test/bun.js/transpiler.test.js index c81b00ec5..83d30f3b1 100644 --- a/test/bun.js/transpiler.test.js +++ b/test/bun.js/transpiler.test.js @@ -109,84 +109,84 @@ describe("Bun.Transpiler", () => { it("satisfies", () => { ts.expectPrinted_( "const t1 = { a: 1 } satisfies I1;", - "const t1 = { a: 1 };\n" + "const t1 = { a: 1 };\n", ); ts.expectPrinted_( "const t2 = { a: 1, b: 1 } satisfies I1;", - "const t2 = { a: 1, b: 1 };\n" + "const t2 = { a: 1, b: 1 };\n", ); ts.expectPrinted_("const t3 = { } satisfies I1;", "const t3 = {};\n"); ts.expectPrinted_( "const t4: T1 = { a: 'a' } satisfies T1;", - 'const t4 = { a: "a" };\n' + 'const t4 = { a: "a" };\n', ); ts.expectPrinted_( "const t5 = (m => m.substring(0)) satisfies T2;", - "const t5 = (m) => m.substring(0);\n" + "const t5 = (m) => m.substring(0);\n", ); ts.expectPrinted_( "const t6 = [1, 2] satisfies [number, number];", - "const t6 = [1, 2];\n" + "const t6 = [1, 2];\n", ); ts.expectPrinted_( "let t7 = { a: 'test' } satisfies A;", - 'let t7 = { a: "test" };\n' + 'let t7 = { a: "test" };\n', ); ts.expectPrinted_( "let t8 = { a: 'test', b: 'test' } satisfies A;", - 'let t8 = { a: "test", b: "test" };\n' + 'let t8 = { a: "test", b: "test" };\n', ); ts.expectPrinted_( "export default {} satisfies Foo;", - "export default {};\n" + "export default {};\n", ); ts.expectPrinted_( "export default { a: 1 } satisfies Foo;", - "export default { a: 1 };\n" + "export default { a: 1 };\n", ); ts.expectPrinted_( "const p = { isEven: n => n % 2 === 0, isOdd: n => n % 2 === 1 } satisfies Predicates;", - "const p = { isEven: (n) => n % 2 === 0, isOdd: (n) => n % 2 === 1 };\n" + "const p = { isEven: (n) => n % 2 === 0, isOdd: (n) => n % 2 === 1 };\n", ); ts.expectPrinted_( "let obj: { f(s: string): void } & Record<string, unknown> = { f(s) { }, g(s) { } } satisfies { g(s: string): void } & Record<string, unknown>;", - "let obj = { f(s) {\n}, g(s) {\n} };\n" + "let obj = { f(s) {\n}, g(s) {\n} };\n", ); ts.expectPrinted_( "const car = { start() { }, move(d) { }, stop() { } } satisfies Movable & Record<string, unknown>;", - "const car = { start() {\n}, move(d) {\n}, stop() {\n} };\n" + "const car = { start() {\n}, move(d) {\n}, stop() {\n} };\n", ); ts.expectPrinted_( "var v = undefined satisfies 1;", - "var v = undefined;\n" + "var v = undefined;\n", ); ts.expectPrinted_( "const a = { x: 10 } satisfies Partial<Point2d>;", - "const a = { x: 10 };\n" + "const a = { x: 10 };\n", ); ts.expectPrinted_( 'const p = { a: 0, b: "hello", x: 8 } satisfies Partial<Record<Keys, unknown>>;', - 'const p = { a: 0, b: "hello", x: 8 };\n' + 'const p = { a: 0, b: "hello", x: 8 };\n', ); ts.expectPrinted_( 'const p = { a: 0, b: "hello", x: 8 } satisfies Record<Keys, unknown>;', - 'const p = { a: 0, b: "hello", x: 8 };\n' + 'const p = { a: 0, b: "hello", x: 8 };\n', ); ts.expectPrinted_( 'const x2 = { m: true, s: "false" } satisfies Facts;', - 'const x2 = { m: true, s: "false" };\n' + 'const x2 = { m: true, s: "false" };\n', ); ts.expectPrinted_( "export const Palette = { white: { r: 255, g: 255, b: 255 }, black: { r: 0, g: 0, d: 0 }, blue: { r: 0, g: 0, b: 255 }, } satisfies Record<string, Color>;", - "export const Palette = { white: { r: 255, g: 255, b: 255 }, black: { r: 0, g: 0, d: 0 }, blue: { r: 0, g: 0, b: 255 } };\n" + "export const Palette = { white: { r: 255, g: 255, b: 255 }, black: { r: 0, g: 0, d: 0 }, blue: { r: 0, g: 0, b: 255 } };\n", ); ts.expectPrinted_( 'const a: "baz" = "foo" satisfies "foo" | "bar";', - 'const a = "foo";\n' + 'const a = "foo";\n', ); ts.expectPrinted_( 'const b: { xyz: "baz" } = { xyz: "foo" } satisfies { xyz: "foo" | "bar" };', - 'const b = { xyz: "foo" };\n' + 'const b = { xyz: "foo" };\n', ); }); }); @@ -373,6 +373,8 @@ describe("Bun.Transpiler", () => { macro: { inline: { whatDidIPass: `${import.meta.dir}/inline.macro.js`, + promiseReturningFunction: `${import.meta.dir}/inline.macro.js`, + promiseReturningCtx: `${import.meta.dir}/inline.macro.js`, }, react: { bacon: `${import.meta.dir}/macro-check.js`, @@ -969,6 +971,49 @@ export var ComponentThatHasSpreadCausesDeopt = jsx(Hello, { `); }); + it("macros can return a promise", () => { + var object = { + helloooooooo: { + message: [12345], + }, + }; + + const output = bunTranspiler.transformSync( + ` + import {promiseReturningFunction} from 'inline'; + + export function foo() { + return promiseReturningFunction(); + } + `, + object, + ); + expect(output).toBe(`export function foo() { + return 1; +} +`); + }); + + it("macros can return a Response body", () => { + var object = Response.json({ hello: "world" }); + + const input = ` +import {promiseReturningCtx} from 'inline'; + +export function foo() { + return promiseReturningCtx(); +} +`.trim(); + + const output = ` +export function foo() { + return { hello: "world" }; +} +`.trim(); + + expect(bunTranspiler.transformSync(input, object).trim()).toBe(output); + }); + it("rewrite string to length", () => { expectPrinted_( `export const foo = "a".length + "b".length;`, |