Skip to content

Commit

Permalink
Support with { type: "macro"} in bun build (#3059)
Browse files Browse the repository at this point in the history
* [bun macro] Support `assert { type: "macro" }` and `with {type: "macro"}`

* [bun macro] Pass through input as arguments instead of a JSNode

* Fix hang when loading many entry points simultaneously with macros

* do not clone

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
  • Loading branch information
Jarred-Sumner and Jarred-Sumner committed May 25, 2023
1 parent 63740a3 commit 88d9bac
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 51 deletions.
2 changes: 1 addition & 1 deletion src/bun.js/bindings/ModuleLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ static JSValue fetchSourceCode(
}
default: {
auto provider = Zig::SourceProvider::create(res->result.value);
return rejectOrResolve(JSC::JSSourceCode::create(vm, JSC::SourceCode(provider)));
return rejectOrResolve(JSC::JSSourceCode::create(vm, JSC::SourceCode(WTFMove(provider))));
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/bun.js/bindings/ScriptExecutionContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extern "C" void Bun__startLoop(us_loop_t* loop);

namespace WebCore {

static unsigned lastUniqueIdentifier = 0;
static std::atomic<unsigned> lastUniqueIdentifier = 0;

static Lock allScriptExecutionContextsMapLock;
static HashMap<ScriptExecutionContextIdentifier, ScriptExecutionContext*>& allScriptExecutionContextsMap() WTF_REQUIRES_LOCK(allScriptExecutionContextsMapLock)
Expand Down Expand Up @@ -41,7 +41,7 @@ us_socket_context_t* ScriptExecutionContext::webSocketContextSSL()
us_bun_socket_context_options_t opts;
memset(&opts, 0, sizeof(us_bun_socket_context_options_t));
// adds root ca
opts.request_cert = true;
opts.request_cert = true;
// but do not reject unauthorized
opts.reject_unauthorized = false;
this->m_ssl_client_websockets_ctx = us_create_bun_socket_context(1, loop, sizeof(size_t), opts);
Expand Down Expand Up @@ -108,27 +108,27 @@ void ScriptExecutionContext::regenerateIdentifier()
{
Locker locker { allScriptExecutionContextsMapLock };

ASSERT(allScriptExecutionContextsMap().contains(m_identifier));
allScriptExecutionContextsMap().remove(m_identifier);
// ASSERT(allScriptExecutionContextsMap().contains(m_identifier));
// allScriptExecutionContextsMap().remove(m_identifier);

m_identifier = ++lastUniqueIdentifier;

ASSERT(!allScriptExecutionContextsMap().contains(m_identifier));
// ASSERT(!allScriptExecutionContextsMap().contains(m_identifier));
allScriptExecutionContextsMap().add(m_identifier, this);
}

void ScriptExecutionContext::addToContextsMap()
{
Locker locker { allScriptExecutionContextsMapLock };
ASSERT(!allScriptExecutionContextsMap().contains(m_identifier));
allScriptExecutionContextsMap().add(m_identifier, this);
// allScriptExecutionContextsMap().add(m_identifier, this);
}

void ScriptExecutionContext::removeFromContextsMap()
{
Locker locker { allScriptExecutionContextsMapLock };
ASSERT(allScriptExecutionContextsMap().contains(m_identifier));
allScriptExecutionContextsMap().remove(m_identifier);
// allScriptExecutionContextsMap().remove(m_identifier);
}

}
157 changes: 120 additions & 37 deletions src/js_ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1435,21 +1435,16 @@ pub const E = struct {
return ExprNodeList.init(out[0 .. out.len - remain.len]);
}

pub fn toJS(this: @This(), ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.C.JSValueRef {
var stack = std.heap.stackFallback(32 * @sizeOf(ExprNodeList), JSC.getAllocator(ctx));
var allocator = stack.get();
var results = allocator.alloc(JSC.C.JSValueRef, this.items.len) catch {
return JSC.C.JSValueMakeUndefined(ctx);
};
defer if (stack.fixed_buffer_allocator.end_index >= stack.fixed_buffer_allocator.buffer.len - 1) allocator.free(results);

var i: usize = 0;
pub fn toJS(this: @This(), allocator: std.mem.Allocator, globalObject: *JSC.JSGlobalObject) ToJSError!JSC.JSValue {
const items = this.items.slice();
while (i < results.len) : (i += 1) {
results[i] = items[i].toJS(ctx, exception);
var array = JSC.JSValue.createEmptyArray(globalObject, items.len);
array.protect();
defer array.unprotect();
for (items, 0..) |expr, j| {
array.putIndex(globalObject, @truncate(u32, j), try expr.data.toJS(allocator, globalObject));
}

return JSC.C.JSObjectMakeArray(ctx, results.len, results.ptr, exception);
return array;
}
};

Expand Down Expand Up @@ -1762,8 +1757,8 @@ pub const E = struct {
return try std.json.stringify(self.value, opts, o);
}

pub fn toJS(this: @This(), _: JSC.C.JSContextRef, _: JSC.C.ExceptionRef) JSC.C.JSValueRef {
return JSC.JSValue.jsNumber(this.value).asObjectRef();
pub fn toJS(this: @This()) JSC.JSValue {
return JSC.JSValue.jsNumber(this.value);
}
};

Expand All @@ -1776,7 +1771,7 @@ pub const E = struct {
return try std.json.stringify(self.value, opts, o);
}

pub fn toJS(_: @This(), _: JSC.C.JSContextRef, _: JSC.C.ExceptionRef) JSC.C.JSValueRef {
pub fn toJS(_: @This()) JSC.JSValue {
// TODO:
return JSC.JSValue.jsNumber(0);
}
Expand Down Expand Up @@ -1840,6 +1835,22 @@ pub const E = struct {
return if (asProperty(self, key)) |query| query.expr else @as(?Expr, null);
}

pub fn toJS(this: *Object, allocator: std.mem.Allocator, globalObject: *JSC.JSGlobalObject) ToJSError!JSC.JSValue {
var obj = JSC.JSValue.createEmptyObject(globalObject, this.properties.len);
obj.protect();
defer obj.unprotect();
const props: []const G.Property = this.properties.slice();
for (props) |prop| {
if (prop.kind != .normal or prop.class_static_block != null or prop.key == null or prop.key.?.data != .e_string or prop.value == null) {
return error.@"Cannot convert argument type to JS";
}
var key = prop.key.?.data.e_string.toZigString(allocator);
obj.put(globalObject, &key, try prop.value.?.toJS(allocator, globalObject));
}

return obj;
}

pub fn put(self: *Object, allocator: std.mem.Allocator, key: string, expr: Expr) !void {
if (asProperty(self, key)) |query| {
self.properties.ptr[query.i].value = expr;
Expand Down Expand Up @@ -2339,6 +2350,18 @@ pub const E = struct {
}
}

pub fn toJS(s: *String, allocator: std.mem.Allocator, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
return s.toZigString(allocator).toValueGC(globalObject);
}

pub fn toZigString(s: *String, allocator: std.mem.Allocator) JSC.ZigString {
if (s.isUTF8()) {
return JSC.ZigString.fromUTF8(s.slice(allocator));
} else {
return JSC.ZigString.init16(s.slice16());
}
}

pub fn jsonStringify(s: *const String, options: anytype, writer: anytype) !void {
var buf = [_]u8{0} ** 4096;
var i: usize = 0;
Expand Down Expand Up @@ -3090,8 +3113,8 @@ pub const Expr = struct {
return false;
}

pub fn toJS(this: Expr, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.C.JSValueRef {
return this.data.toJS(ctx, exception);
pub fn toJS(this: Expr, allocator: std.mem.Allocator, globalObject: *JSC.JSGlobalObject) ToJSError!JSC.JSValue {
return this.data.toJS(allocator, globalObject);
}

pub fn get(expr: *const Expr, name: string) ?Expr {
Expand Down Expand Up @@ -5203,18 +5226,35 @@ pub const Expr = struct {
return equality;
}

pub fn toJS(this: Data, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.C.JSValueRef {
pub fn toJS(this: Data, allocator: std.mem.Allocator, globalObject: *JSC.JSGlobalObject) ToJSError!JSC.JSValue {
return switch (this) {
.e_array => |e| e.toJS(ctx, exception),
.e_null => |e| e.toJS(ctx, exception),
.e_undefined => |e| e.toJS(ctx, exception),
.e_object => |e| e.toJS(ctx, exception),
.e_boolean => |e| e.toJS(ctx, exception),
.e_number => |e| e.toJS(ctx, exception),
.e_big_int => |e| e.toJS(ctx, exception),
.e_string => |e| e.toJS(ctx, exception),
.e_array => |e| e.toJS(allocator, globalObject),
.e_object => |e| e.toJS(allocator, globalObject),
.e_string => |e| e.toJS(allocator, globalObject),
.e_null => JSC.JSValue.null,
.e_undefined => JSC.JSValue.undefined,
.e_boolean => |boolean| if (boolean.value)
JSC.JSValue.true
else
JSC.JSValue.false,
.e_number => |e| e.toJS(),
// .e_big_int => |e| e.toJS(ctx, exception),

.e_identifier,
.e_import_identifier,
.inline_identifier,
.e_private_identifier,
.e_commonjs_export_identifier,
=> error.@"Cannot convert identifier to JS. Try a statically-known value",

// brk: {
// // var node = try allocator.create(Macro.JSNode);
// // node.* = Macro.JSNode.initExpr(Expr{ .data = this, .loc = logger.Loc.Empty });
// // break :brk JSC.JSValue.c(Macro.JSNode.Class.make(globalObject, node));
// },

else => {
return JSC.C.JSValueMakeUndefined(ctx);
return error.@"Cannot convert argument type to JS";
},
};
}
Expand Down Expand Up @@ -9585,7 +9625,7 @@ pub const Macro = struct {
threadlocal var args_buf: [3]js.JSObjectRef = undefined;
threadlocal var expr_nodes_buf: [1]JSNode = undefined;
threadlocal var exception_holder: Zig.ZigException.Holder = undefined;
pub const MacroError = error{MacroFailed};
pub const MacroError = error{ MacroFailed, OutOfMemory } || ToJSError;

pub fn NewRun(comptime Visitor: type) type {
return struct {
Expand All @@ -9609,6 +9649,7 @@ pub const Macro = struct {
function_name: string,
caller: Expr,
args_count: usize,
args_ptr: [*]JSC.JSValue,
source: *const logger.Source,
id: i32,
visitor: Visitor,
Expand All @@ -9621,7 +9662,7 @@ pub const Macro = struct {
macro_callback,
null,
args_count,
&args_buf,
@ptrCast([*]js.JSObjectRef, args_ptr),
);

var runner = Run{
Expand Down Expand Up @@ -9936,13 +9977,47 @@ pub const Macro = struct {
if (comptime Environment.isDebug) Output.prettyln("<r><d>[macro]<r> call <d><b>{s}<r>", .{function_name});

exception_holder = Zig.ZigException.Holder.init();
expr_nodes_buf[0] = JSNode.initExpr(caller);
args_buf[0] = JSNode.Class.make(
macro.vm.global,
&expr_nodes_buf[0],
);
args_buf[1] = if (javascript_object.isEmpty()) null else javascript_object.asObjectRef();
args_buf[2] = null;
var js_args: []JSC.JSValue = &.{};
defer {
for (js_args[0 .. js_args.len - @as(usize, @boolToInt(!javascript_object.isEmpty()))]) |arg| {
arg.unprotect();
}

allocator.free(js_args);
}

var globalObject = JSC.VirtualMachine.get().global;

switch (caller.data) {
.e_call => |call| {
const call_args: []Expr = call.args.slice();
js_args = try allocator.alloc(JSC.JSValue, call_args.len + @as(usize, @boolToInt(!javascript_object.isEmpty())));

for (call_args, js_args[0..call_args.len]) |in, *out| {
const value = try in.toJS(
allocator,
globalObject,
);
value.protect();
out.* = value;
}
},
.e_template => {
@panic("TODO: support template literals in macros");
},
else => {
@panic("Unexpected caller type");
},
}

if (!javascript_object.isEmpty()) {
if (js_args.len == 0) {
js_args = try allocator.alloc(JSC.JSValue, 1);
}

js_args[js_args.len - 1] = javascript_object;
}

const Run = NewRun(Visitor);

const CallFunction = @TypeOf(Run.runAsync);
Expand Down Expand Up @@ -9970,7 +10045,8 @@ pub const Macro = struct {
allocator,
function_name,
caller,
2 + @as(usize, @boolToInt(!javascript_object.isEmpty())),
js_args.len,
js_args.ptr,
source,
id,
visitor,
Expand Down Expand Up @@ -10301,3 +10377,10 @@ pub const GlobalStoreHandle = struct {
// Stmt | 192
// STry | 384
// -- ESBuild bit sizes

const ToJSError = error{
@"Cannot convert argument type to JS",
@"Cannot convert identifier to JS. Try a statically-known value",
MacroError,
OutOfMemory,
};
38 changes: 32 additions & 6 deletions src/js_parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2300,7 +2300,11 @@ const ThenCatchChain = struct {
has_catch: bool = false,
};

const ParsedPath = struct { loc: logger.Loc, text: string };
const ParsedPath = struct {
loc: logger.Loc,
text: string,
is_macro: bool,
};

const StrictModeFeature = enum {
with_statement,
Expand Down Expand Up @@ -8058,7 +8062,7 @@ fn NewParser_(
}

fn processImportStatement(p: *P, stmt_: S.Import, path: ParsedPath, loc: logger.Loc, was_originally_bare_import: bool) anyerror!Stmt {
const is_macro = FeatureFlags.is_macro_enabled and js_ast.Macro.isMacroPath(path.text);
const is_macro = FeatureFlags.is_macro_enabled and (path.is_macro or js_ast.Macro.isMacroPath(path.text));
var stmt = stmt_;
if (is_macro) {
const id = p.addImportRecord(.stmt, path.loc, path.text);
Expand Down Expand Up @@ -8884,6 +8888,10 @@ fn NewParser_(
// path.assertions
);

if (path.is_macro) {
try p.log.addError(p.source, path.loc, "cannot use macro in export statement");
}

if (comptime track_symbol_usage_during_parse_pass) {
// In the scan pass, we need _some_ way of knowing *not* to mark as unused
p.import_records.items[import_record_index].calls_runtime_re_export_fn = true;
Expand Down Expand Up @@ -8919,6 +8927,10 @@ fn NewParser_(
}
}

if (parsedPath.is_macro) {
try p.log.addError(p.source, loc, "export from cannot be used with \"type\": \"macro\"");
}

const import_record_index = p.addImportRecord(.stmt, parsedPath.loc, parsedPath.text);
const path_name = fs.PathName.init(parsedPath.text);
const namespace_ref = p.storeNameInRef(
Expand Down Expand Up @@ -10913,6 +10925,7 @@ fn NewParser_(
var path = ParsedPath{
.loc = p.lexer.loc(),
.text = p.lexer.string_literal_slice,
.is_macro = false,
};

if (p.lexer.token == .t_no_substitution_template_literal) {
Expand All @@ -10934,15 +10947,28 @@ fn NewParser_(
try p.lexer.expect(.t_open_brace);

while (p.lexer.token != .t_close_brace) {
// Parse the key
if (p.lexer.isIdentifierOrKeyword()) {} else if (p.lexer.token == .t_string_literal) {} else {
try p.lexer.expect(.t_identifier);
}
const is_type_flag = brk: {
// Parse the key
if (p.lexer.isIdentifierOrKeyword()) {
break :brk strings.eqlComptime(p.lexer.identifier, "type");
} else if (p.lexer.token == .t_string_literal) {
break :brk p.lexer.string_literal_is_ascii and strings.eqlComptime(p.lexer.string_literal_slice, "type");
} else {
try p.lexer.expect(.t_identifier);
}

break :brk false;
};

try p.lexer.next();
try p.lexer.expect(.t_colon);

try p.lexer.expect(.t_string_literal);
if (is_type_flag and
p.lexer.string_literal_is_ascii and strings.eqlComptime(p.lexer.string_literal_slice, "macro"))
{
path.is_macro = true;
}

if (p.lexer.token != .t_comma) {
break;
Expand Down

0 comments on commit 88d9bac

Please sign in to comment.