const Bun = @This(); const default_allocator = @import("../../../global.zig").default_allocator; const bun = @import("../../../global.zig"); const Environment = bun.Environment; const NetworkThread = @import("http").NetworkThread; const Global = bun.Global; const strings = bun.strings; const string = bun.string; const Output = @import("../../../global.zig").Output; const MutableString = @import("../../../global.zig").MutableString; const std = @import("std"); const Allocator = std.mem.Allocator; const IdentityContext = @import("../../../identity_context.zig").IdentityContext; const Fs = @import("../../../fs.zig"); const Resolver = @import("../../../resolver/resolver.zig"); const ast = @import("../../../import_record.zig"); const NodeModuleBundle = @import("../../../node_module_bundle.zig").NodeModuleBundle; const MacroEntryPoint = @import("../../../bundler.zig").MacroEntryPoint; const logger = @import("../../../logger.zig"); const Api = @import("../../../api/schema.zig").Api; const options = @import("../../../options.zig"); const Bundler = @import("../../../bundler.zig").Bundler; const ServerEntryPoint = @import("../../../bundler.zig").ServerEntryPoint; const js_printer = @import("../../../js_printer.zig"); const js_parser = @import("../../../js_parser.zig"); const js_ast = @import("../../../js_ast.zig"); const hash_map = @import("../../../hash_map.zig"); const http = @import("../../../http.zig"); const NodeFallbackModules = @import("../../../node_fallbacks.zig"); const ImportKind = ast.ImportKind; const Analytics = @import("../../../analytics/analytics_thread.zig"); const ZigString = @import("../../../jsc.zig").ZigString; const Runtime = @import("../../../runtime.zig"); const Router = @import("./router.zig"); const ImportRecord = ast.ImportRecord; const DotEnv = @import("../../../env_loader.zig"); const ParseResult = @import("../../../bundler.zig").ParseResult; const PackageJSON = @import("../../../resolver/package_json.zig").PackageJSON; const MacroRemap = @import("../../../resolver/package_json.zig").MacroMap; const WebCore = @import("../../../jsc.zig").WebCore; const Request = WebCore.Request; const Response = WebCore.Response; const Headers = WebCore.Headers; const Fetch = WebCore.Fetch; const FetchEvent = WebCore.FetchEvent; const js = @import("../../../jsc.zig").C; const JSC = @import("../../../jsc.zig"); const JSError = @import("../base.zig").JSError; const d = @import("../base.zig").d; const MarkedArrayBuffer = @import("../base.zig").MarkedArrayBuffer; const getAllocator = @import("../base.zig").getAllocator; const JSValue = @import("../../../jsc.zig").JSValue; const NewClass = @import("../base.zig").NewClass; const Microtask = @import("../../../jsc.zig").Microtask; const JSGlobalObject = @import("../../../jsc.zig").JSGlobalObject; const ExceptionValueRef = @import("../../../jsc.zig").ExceptionValueRef; const JSPrivateDataPtr = @import("../../../jsc.zig").JSPrivateDataPtr; const ZigConsoleClient = @import("../../../jsc.zig").ZigConsoleClient; const Node = @import("../../../jsc.zig").Node; const ZigException = @import("../../../jsc.zig").ZigException; const ZigStackTrace = @import("../../../jsc.zig").ZigStackTrace; const ErrorableResolvedSource = @import("../../../jsc.zig").ErrorableResolvedSource; const ResolvedSource = @import("../../../jsc.zig").ResolvedSource; const JSPromise = @import("../../../jsc.zig").JSPromise; const JSInternalPromise = @import("../../../jsc.zig").JSInternalPromise; const JSModuleLoader = @import("../../../jsc.zig").JSModuleLoader; const JSPromiseRejectionOperation = @import("../../../jsc.zig").JSPromiseRejectionOperation; const Exception = @import("../../../jsc.zig").Exception; const ErrorableZigString = @import("../../../jsc.zig").ErrorableZigString; const ZigGlobalObject = @import("../../../jsc.zig").ZigGlobalObject; const VM = @import("../../../jsc.zig").VM; const JSFunction = @import("../../../jsc.zig").JSFunction; const Config = @import("../config.zig"); const URL = @import("../../../url.zig").URL; const Transpiler = @import("./transpiler.zig"); const VirtualMachine = @import("../javascript.zig").VirtualMachine; const IOTask = JSC.IOTask; const ComptimeStringMap = @import("../../../comptime_string_map.zig").ComptimeStringMap; const TCC = @import("../../../../tcc.zig"); pub const FFI = struct { dylib: std.DynLib, functions: std.StringArrayHashMapUnmanaged(Function) = .{}, closed: bool = false, pub const Class = JSC.NewClass( FFI, .{ .name = "class" }, .{ .call = JSC.wrapWithHasContainer(FFI, "close", false, true) }, .{}, ); pub fn close(this: *FFI) JSValue { if (this.closed) { return JSC.JSValue.jsUndefined(); } this.closed = true; this.dylib.close(); const allocator = VirtualMachine.vm.allocator; for (this.functions.values()) |*val| { allocator.free(bun.constStrToU8(std.mem.span(val.base_name))); val.arg_types.deinit(allocator); } this.functions.deinit(allocator); return JSC.JSValue.jsUndefined(); } pub fn print(global: *JSGlobalObject, object: JSC.JSValue) JSValue { const allocator = VirtualMachine.vm.allocator; if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) { return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global.ref()); } var symbols = std.StringArrayHashMapUnmanaged(Function){}; if (generateSymbols(global, &symbols, object) catch JSC.JSValue.zero) |val| { // an error while validating symbols for (symbols.keys()) |key| { allocator.free(bun.constStrToU8(key)); } symbols.clearAndFree(allocator); return val; } var zig_strings = allocator.alloc(ZigString, symbols.count()) catch unreachable; for (symbols.values()) |*function, i| { var arraylist = std.ArrayList(u8).init(allocator); var writer = arraylist.writer(); function.printSourceCode(&writer) catch { // an error while generating source code for (symbols.keys()) |key| { allocator.free(bun.constStrToU8(key)); } for (zig_strings) |zig_string| { allocator.free(bun.constStrToU8(zig_string.slice())); } for (symbols.values()) |*function_| { function_.arg_types.deinit(allocator); } symbols.clearAndFree(allocator); allocator.free(zig_strings); return ZigString.init("Error while printing code").toErrorInstance(global); }; zig_strings[i] = ZigString.init(arraylist.toOwnedSlice()); } const ret = JSC.JSValue.createStringArray(global, zig_strings.ptr, zig_strings.len, true); for (symbols.keys()) |key| { allocator.free(bun.constStrToU8(key)); } for (zig_strings) |zig_string| { allocator.free(bun.constStrToU8(zig_string.slice())); } for (symbols.values()) |*function_| { function_.arg_types.deinit(allocator); if (function_.step == .compiled) { allocator.free(function_.step.compiled.buf); } } symbols.clearAndFree(allocator); return ret; } // pub fn dlcompile(global: *JSGlobalObject, object: JSC.JSValue) JSValue { // const allocator = VirtualMachine.vm.allocator; // if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) { // return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global.ref()); // } // var symbols = std.StringArrayHashMapUnmanaged(Function){}; // if (generateSymbols(global, &symbols, object) catch JSC.JSValue.zero) |val| { // // an error while validating symbols // for (symbols.keys()) |key| { // allocator.free(bun.constStrToU8(key)); // } // symbols.clearAndFree(allocator); // return val; // } // } pub fn open(global: *JSGlobalObject, name_str: ZigString, object: JSC.JSValue) JSC.JSValue { const allocator = VirtualMachine.vm.allocator; var name_slice = name_str.toSlice(allocator); defer name_slice.deinit(); if (name_slice.len == 0) { return JSC.toInvalidArguments("Invalid library name", .{}, global.ref()); } if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) { return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global.ref()); } const name = name_slice.sliceZ(); var symbols = std.StringArrayHashMapUnmanaged(Function){}; if (generateSymbols(global, &symbols, object) catch JSC.JSValue.zero) |val| { // an error while validating symbols for (symbols.keys()) |key| { allocator.free(bun.constStrToU8(key)); } symbols.clearAndFree(allocator); return val; } if (symbols.count() == 0) { return JSC.toInvalidArguments("Expected at least one symbol", .{}, global.ref()); } var dylib = std.DynLib.open(name) catch { return JSC.toInvalidArguments("Failed to open library", .{}, global.ref()); }; var obj = JSC.JSValue.c(JSC.C.JSObjectMake(global.ref(), null, null)); JSC.C.JSValueProtect(global.ref(), obj.asObjectRef()); defer JSC.C.JSValueUnprotect(global.ref(), obj.asObjectRef()); for (symbols.values()) |*function| { var resolved_symbol = dylib.lookup(*anyopaque, function.base_name) orelse { const ret = JSC.toInvalidArguments("Symbol \"{s}\" not found in \"{s}\"", .{ std.mem.span(function.base_name), name_slice.slice() }, global.ref()); for (symbols.values()) |*value| { allocator.free(bun.constStrToU8(std.mem.span(value.base_name))); value.arg_types.clearAndFree(allocator); } symbols.clearAndFree(allocator); dylib.close(); return ret; }; function.symbol_from_dynamic_library = resolved_symbol; function.compile(allocator) catch { const ret = JSC.toInvalidArguments("Failed to compile symbol \"{s}\" in \"{s}\"", .{ std.mem.span(function.base_name), name_slice.slice() }, global.ref()); for (symbols.values()) |*value| { allocator.free(bun.constStrToU8(std.mem.span(value.base_name))); value.arg_types.clearAndFree(allocator); } symbols.clearAndFree(allocator); dylib.close(); return ret; }; switch (function.step) { .failed => |err| { for (symbols.values()) |*value| { allocator.free(bun.constStrToU8(std.mem.span(value.base_name))); value.arg_types.clearAndFree(allocator); } symbols.clearAndFree(allocator); dylib.close(); return ZigString.init(err).toErrorInstance(global); }, .pending => { for (symbols.values()) |*value| { allocator.free(bun.constStrToU8(std.mem.span(value.base_name))); value.arg_types.clearAndFree(allocator); } symbols.clearAndFree(allocator); dylib.close(); return ZigString.init("Failed to compile (nothing happend!)").toErrorInstance(global); }, .compiled => |compiled| { var callback = JSC.C.JSObjectMakeFunctionWithCallback(global.ref(), null, @ptrCast(JSC.C.JSObjectCallAsFunctionCallback, compiled.ptr)); obj.put(global, &ZigString.init(std.mem.span(function.base_name)), JSC.JSValue.cast(callback)); }, } } var lib = allocator.create(FFI) catch unreachable; lib.* = .{ .dylib = dylib, .functions = symbols, }; var close_object = JSC.JSValue.c(Class.make(global.ref(), lib)); return JSC.JSValue.createObject2(global, &ZigString.init("close"), &ZigString.init("symbols"), close_object, obj); } pub fn generateSymbols(global: *JSGlobalObject, symbols: *std.StringArrayHashMapUnmanaged(Function), object: JSC.JSValue) !?JSValue { const allocator = VirtualMachine.vm.allocator; var keys = JSC.C.JSObjectCopyPropertyNames(global.ref(), object.asObjectRef()); defer JSC.C.JSPropertyNameArrayRelease(keys); const count = JSC.C.JSPropertyNameArrayGetCount(keys); try symbols.ensureTotalCapacity(allocator, count); var i: usize = 0; while (i < count) : (i += 1) { var property_name_ref = JSC.C.JSPropertyNameArrayGetNameAtIndex(keys, i); defer JSC.C.JSStringRelease(property_name_ref); const len = JSC.C.JSStringGetLength(property_name_ref); if (len == 0) continue; var prop = JSC.C.JSStringGetCharacters8Ptr(property_name_ref)[0..len]; var value = JSC.JSValue.c(JSC.C.JSObjectGetProperty(global.ref(), object.asObjectRef(), property_name_ref, null)); if (value.isEmptyOrUndefinedOrNull()) { return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Expected an object for key \"{s}\"", .{prop}, global.ref()); } var abi_types = std.ArrayListUnmanaged(ABIType){}; if (value.get(global, "params")) |params| { if (params.isEmptyOrUndefinedOrNull() or !params.jsType().isArray()) { return ZigString.init("Expected an object with \"params\" as an array").toErrorInstance(global); } var array = params.arrayIterator(global); try abi_types.ensureTotalCapacityPrecise(allocator, array.len); while (array.next()) |val| { if (val.isEmptyOrUndefinedOrNull()) { abi_types.clearAndFree(allocator); return ZigString.init("param must be a string (type name) or number").toErrorInstance(global); } if (val.isAnyInt()) { const int = val.toInt32(); switch (int) { 0...13 => { abi_types.appendAssumeCapacity(@intToEnum(ABIType, int)); continue; }, else => { abi_types.clearAndFree(allocator); return ZigString.init("invalid ABI type").toErrorInstance(global); }, } } if (!val.jsType().isStringLike()) { abi_types.clearAndFree(allocator); return ZigString.init("param must be a string (type name) or number").toErrorInstance(global); } var type_name = val.toSlice(global, allocator); defer type_name.deinit(); abi_types.appendAssumeCapacity(ABIType.label.get(type_name.slice()) orelse { abi_types.clearAndFree(allocator); return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Unknown type {s}", .{type_name.slice()}, global.ref()); }); } } // var function var return_type = ABIType.@"void"; if (value.get(global, "return_type")) |ret_value| { var ret_slice = ret_value.toSlice(global, allocator); defer ret_slice.deinit(); return_type = ABIType.label.get(ret_slice.slice()) orelse { abi_types.clearAndFree(allocator); return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Unknown return type {s}", .{ret_slice.slice()}, global.ref()); }; } const function = Function{ .base_name = try allocator.dupeZ(u8, prop), .arg_types = abi_types, .return_type = return_type, }; symbols.putAssumeCapacity(std.mem.span(function.base_name), function); } return null; } pub const Function = struct { symbol_from_dynamic_library: ?*anyopaque = null, base_name: [:0]const u8 = "", return_type: ABIType, arg_types: std.ArrayListUnmanaged(ABIType) = .{}, step: Step = Step{ .pending = {} }, pub const Step = union(enum) { pending: void, compiled: struct { ptr: *anyopaque, buf: []u8, }, failed: []const u8, }; const FFI_HEADER: string = @embedFile("./FFI.h"); pub inline fn ffiHeader() string { if (comptime Environment.isDebug) { var dirpath = std.fs.path.dirname(@src().file).?; var env = std.process.getEnvMap(default_allocator) catch unreachable; const dir = std.mem.replaceOwned( u8, default_allocator, dirpath, "jarred", env.get("USER").?, ) catch unreachable; var runtime_path = std.fs.path.join(default_allocator, &[_]string{ dir, "FFI.h" }) catch unreachable; const file = std.fs.openFileAbsolute(runtime_path, .{}) catch @panic("Missing bun/src/javascript/jsc/api/FFI.h."); defer file.close(); return file.readToEndAlloc(default_allocator, (file.stat() catch unreachable).size) catch unreachable; } else { return FFI_HEADER; } } pub fn handleTCCError(ctx: ?*anyopaque, message: [*c]const u8) callconv(.C) void { var this = bun.cast(*Function, ctx.?); this.step = .{ .failed = std.mem.span(message) }; } extern fn pthread_jit_write_protect_np(enable: bool) callconv(.C) void; pub fn compile( this: *Function, allocator: std.mem.Allocator, ) !void { var source_code = std.ArrayList(u8).init(allocator); var source_code_writer = source_code.writer(); try this.printSourceCode(&source_code_writer); try source_code.append(0); defer source_code.deinit(); var state = TCC.tcc_new() orelse return error.TCCMissing; TCC.tcc_set_options(state, "-std=c11"); TCC.tcc_set_error_func(state, this, handleTCCError); // defer TCC.tcc_delete(state); _ = TCC.tcc_set_output_type(state, TCC.TCC_OUTPUT_MEMORY); const compilation_result = TCC.tcc_compile_string( state, source_code.items.ptr, ); // did tcc report an error? if (this.step == .failed) { return; } // did tcc report failure but never called the error callback? if (compilation_result == -1) { this.step = .{ .failed = "tcc returned -1, which means it failed" }; return; } _ = TCC.tcc_add_symbol(state, this.base_name, this.symbol_from_dynamic_library.?); // i don't fully understand this, why it needs two calls // but that is the API var relocation_size = TCC.tcc_relocate(state, null); if (relocation_size > 0) { var bytes: []u8 = try allocator.rawAlloc(@intCast(usize, relocation_size), 16, 16, 0); if (comptime Environment.isAarch64 and Environment.isMac) { pthread_jit_write_protect_np(false); } _ = TCC.tcc_relocate(state, bytes.ptr); if (comptime Environment.isAarch64 and Environment.isMac) { pthread_jit_write_protect_np(true); } if (this.step == .failed) { allocator.free(bytes); return; } var formatted_symbol_name = try std.fmt.allocPrintZ(allocator, "bun_gen_{s}", .{std.mem.span(this.base_name)}); defer allocator.free(formatted_symbol_name); var symbol = TCC.tcc_get_symbol(state, formatted_symbol_name) orelse { this.step = .{ .failed = "missing generated symbol in source code" }; allocator.free(bytes); return; }; if (this.step == .failed) { allocator.free(bytes); return; } this.step = .{ .compiled = .{ .ptr = symbol, .buf = bytes, }, }; return; } } pub fn printSourceCode( this: *Function, writer: anytype, ) !void { brk: { if (this.return_type.isFloatingPoint()) { try writer.writeAll("#define USES_FLOAT 1\n"); break :brk; } for (this.arg_types.items) |arg| { // conditionally include math.h if (arg.isFloatingPoint()) { try writer.writeAll("#define USES_FLOAT 1\n"); break; } } } if (comptime Environment.isRelease) { try writer.writeAll(std.mem.span(FFI_HEADER)); } else { try writer.writeAll(ffiHeader()); } // -- Generate the FFI function symbol try writer.writeAll("/* --- The Function To Call */\n"); try this.return_type.typename(writer); try writer.writeAll(" "); try writer.writeAll(std.mem.span(this.base_name)); try writer.writeAll("("); var first = true; for (this.arg_types.items) |arg, i| { if (!first) { try writer.writeAll(", "); } first = false; try arg.typename(writer); try writer.print(" arg{d}", .{i}); } try writer.writeAll(");\n\n"); // -- Generate JavaScriptCore's C wrapper function try writer.writeAll("/* ---- Your Wrapper Function ---- */\nvoid* bun_gen_"); try writer.writeAll(std.mem.span(this.base_name)); try writer.writeAll("(JSContext ctx, EncodedJSValue function, EncodedJSValue thisObject, size_t argumentCount, const EncodedJSValue arguments[], void* exception);\n\n"); try writer.writeAll("void* bun_gen_"); try writer.writeAll(std.mem.span(this.base_name)); try writer.writeAll("(JSContext ctx, EncodedJSValue function, EncodedJSValue thisObject, size_t argumentCount, const EncodedJSValue arguments[], void* exception) {\n\n"); var arg_buf: [512]u8 = undefined; arg_buf[0.."arguments[".len].* = "arguments[".*; for (this.arg_types.items) |arg, i| { try writer.writeAll(" "); try arg.typename(writer); var printed = std.fmt.bufPrintIntToSlice(arg_buf["arguments[".len..], i, 10, .lower, .{}); arg_buf["arguments[".len + printed.len] = ']'; try writer.print(" arg{d} = {};\n", .{ i, arg.toC(arg_buf[0 .. printed.len + "arguments[]".len]) }); } try writer.writeAll(" "); if (!(this.return_type == .void)) { try this.return_type.typename(writer); try writer.writeAll(" return_value = "); } try writer.print("{s}(", .{std.mem.span(this.base_name)}); first = true; for (this.arg_types.items) |_, i| { if (!first) { try writer.writeAll(", "); } first = false; try writer.print("arg{d}", .{i}); } try writer.writeAll(");\n"); if (!first) try writer.writeAll("\n"); try writer.writeAll(" "); try writer.writeAll("return "); if (!(this.return_type == .void)) { try writer.print("{}.asPtr", .{this.return_type.toJS("return_value")}); } else { try writer.writeAll("ValueUndefined.asPtr"); } try writer.writeAll(";\n}\n\n"); } }; pub const ABIType = enum(i32) { char = 0, int8_t = 1, uint8_t = 2, int16_t = 3, uint16_t = 4, int32_t = 5, uint32_t = 6, int64_t = 7, uint64_t = 8, double = 9, float = 10, bool = 11, ptr = 12, @"void" = 13, const map = .{ .{ "bool", ABIType.bool }, .{ "c_int", ABIType.int32_t }, .{ "c_uint", ABIType.uint32_t }, .{ "char", ABIType.char }, .{ "char*", ABIType.ptr }, .{ "double", ABIType.double }, .{ "f32", ABIType.float }, .{ "f64", ABIType.double }, .{ "float", ABIType.float }, .{ "i16", ABIType.int16_t }, .{ "i32", ABIType.int32_t }, .{ "i64", ABIType.int64_t }, .{ "i8", ABIType.int8_t }, .{ "int", ABIType.int32_t }, .{ "int16_t", ABIType.int16_t }, .{ "int32_t", ABIType.int32_t }, .{ "int64_t", ABIType.int64_t }, .{ "int8_t", ABIType.int8_t }, .{ "isize", ABIType.int64_t }, .{ "u16", ABIType.uint16_t }, .{ "u32", ABIType.uint32_t }, .{ "u64", ABIType.uint64_t }, .{ "u8", ABIType.uint8_t }, .{ "uint16_t", ABIType.uint16_t }, .{ "uint32_t", ABIType.uint32_t }, .{ "uint64_t", ABIType.uint64_t }, .{ "uint8_t", ABIType.uint8_t }, .{ "usize", ABIType.uint64_t }, .{ "void*", ABIType.ptr }, .{ "ptr", ABIType.ptr }, .{ "pointer", ABIType.ptr }, }; pub const label = ComptimeStringMap(ABIType, map); const EnumMapFormatter = struct { name: []const u8, entry: ABIType, pub fn format(self: EnumMapFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { try writer.writeAll("['"); // these are not all valid identifiers try writer.writeAll(self.name); try writer.writeAll("']:"); try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer); try writer.writeAll(",'"); try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer); try writer.writeAll("':"); try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer); } }; pub const map_to_js_object = brk: { var count: usize = 2; for (map) |item, i| { var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" }; count += std.fmt.count("{}", .{fmt}); count += @boolToInt(i > 0); } var buf: [count]u8 = undefined; buf[0] = '{'; buf[buf.len - 1] = '}'; var end: usize = 1; for (map) |item, i| { var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" }; if (i > 0) { buf[end] = ','; end += 1; } end += (std.fmt.bufPrint(buf[end..], "{}", .{fmt}) catch unreachable).len; } break :brk buf; }; pub fn isFloatingPoint(this: ABIType) bool { return switch (this) { .double, .float => true, else => false, }; } const ToCFormatter = struct { symbol: string, tag: ABIType, pub fn format(self: ToCFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { switch (self.tag) { .void => {}, .bool => { try writer.print("JSVALUE_TO_BOOL({s})", .{self.symbol}); }, .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => { try writer.print("JSVALUE_TO_INT32({s})", .{self.symbol}); }, .int64_t => {}, .uint64_t => {}, .ptr => { try writer.print("JSVALUE_TO_PTR({s})", .{self.symbol}); }, .double => { try writer.print("JSVALUE_TO_DOUBLE({s})", .{self.symbol}); }, .float => { try writer.print("JSVALUE_TO_FLOAT({s})", .{self.symbol}); }, } } }; const ToJSFormatter = struct { symbol: []const u8, tag: ABIType, pub fn format(self: ToJSFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { switch (self.tag) { .void => {}, .bool => { try writer.print("BOOLEAN_TO_JSVALUE({s})", .{self.symbol}); }, .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => { try writer.print("INT32_TO_JSVALUE({s})", .{self.symbol}); }, .int64_t => {}, .uint64_t => {}, .ptr => { try writer.print("PTR_TO_JSVALUE({s})", .{self.symbol}); }, .double => { try writer.print("DOUBLE_TO_JSVALUE({s})", .{self.symbol}); }, .float => { try writer.print("FLOAT_TO_JSVALUE({s})", .{self.symbol}); }, } } }; pub fn toC(this: ABIType, symbol: string) ToCFormatter { return ToCFormatter{ .tag = this, .symbol = symbol }; } pub fn toJS( this: ABIType, symbol: string, ) ToJSFormatter { return ToJSFormatter{ .tag = this, .symbol = symbol, }; } pub fn typename(this: ABIType, writer: anytype) !void { try writer.writeAll(this.typenameLabel()); } pub fn typenameLabel(this: ABIType) []const u8 { return switch (this) { .ptr => "void*", .bool => "bool", .int8_t => "int8_t", .uint8_t => "uint8_t", .int16_t => "int16_t", .uint16_t => "uint16_t", .int32_t => "int32_t", .uint32_t => "uint32_t", .int64_t => "int64_t", .uint64_t => "uint64_t", .double => "float", .float => "float", .char => "char", .void => "void", }; } }; };