aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-11-25 06:48:02 -0800
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-11-25 06:48:02 -0800
commite851e5fddba5748c70338c60752ff6567ac9bf86 (patch)
tree70b58c3fd6d33652ab3bb2d478638ff2533026c8
parent1aff60d2ba3f7f5db61cb0fad35381fe63e8909e (diff)
downloadbun-e851e5fddba5748c70338c60752ff6567ac9bf86.tar.gz
bun-e851e5fddba5748c70338c60752ff6567ac9bf86.tar.zst
bun-e851e5fddba5748c70338c60752ff6567ac9bf86.zip
Fix macros that return a Promise
-rw-r--r--src/bun.js/bindings/coroutine.cpp42
-rw-r--r--src/js_ast.zig61
-rw-r--r--test/bun.js/inline.macro.js16
-rw-r--r--test/bun.js/transpiler.test.js85
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;`,