diff options
26 files changed, 428 insertions, 220 deletions
@@ -60,8 +60,7 @@ INCLUDE_DIRS := -Isrc/JavaScript/jsc/WebKit/WebKitBuild/Release/JavaScriptCore/P -Isrc/JavaScript/jsc/bindings/ \ -Isrc/javascript/jsc/WebKit/Source/bmalloc -CLANG_FLAGS = - $(INCLUDE_DIRS) \ +CLANG_FLAGS := $(INCLUDE_DIRS) \ -std=gnu++1z \ -stdlib=libc++ \ -DSTATICALLY_LINKED_WITH_JavaScriptCore=1 \ @@ -79,6 +78,7 @@ CLANG_FLAGS = jsc-bindings-mac: $(OBJ_FILES) + MACOS_ICU_FILES := /usr/local/opt/icu4c/lib/libicudata.a \ /usr/local/opt/icu4c/lib/libicui18n.a \ /usr/local/opt/icu4c/lib/libicuuc.a @@ -23,14 +23,15 @@ npm install -g bun-cli In your project folder root (where `package.json` is): ```bash -npm install bun-framework-next path buffer +npm install bun-framework-next bun bun --use next -open http://localhost:3000; bun dev --origin "http://localhost:3000" +open http://localhost:3000; bun ``` Here are some features of Next.js that **aren't supported** yet: -- `getStaticPaths`. These functions will not be called. +- `getStaticPaths` +- `fetch` inside of `getStaticProps` or `getServerSideProps` - locales, zones, `assetPrefix` (workaround: change `--origin \"http://localhsot:3000/assetPrefixInhere\"`) - `next/image` - `<Image />` component diff --git a/src/allocators.zig b/src/allocators.zig index 29f660a12..4150a37c8 100644 --- a/src/allocators.zig +++ b/src/allocators.zig @@ -685,6 +685,10 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, store_keys: boo slice = try self.map.allocator.dupe(u8, key); } + if (comptime remove_trailing_slashes) { + slice = constStrToU8(std.mem.trimRight(u8, slice, "/")); + } + if (!result.index.is_overflow) { key_list_slices[result.index.index] = slice; } else { @@ -945,6 +949,6 @@ pub fn TBSSMap(comptime ValueType: type, comptime count: anytype, store_keys: bo }; } -pub fn constStrToU8(s: []const u8) []u8 { +pub inline fn constStrToU8(s: []const u8) []u8 { return @intToPtr([*]u8, @ptrToInt(s.ptr))[0..s.len]; } diff --git a/src/bundler.zig b/src/bundler.zig index 8b86203f5..a43d6884c 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -1102,7 +1102,7 @@ pub fn NewBundler(cache_files: bool) type { if (!strings.eqlComptime(file_path.namespace, "node")) break :brk try bundler.resolver.caches.fs.readFileShared( bundler.fs, - file_path.text, + file_path.textZ(), resolve.dirname_fd, if (resolve.file_fd != 0) resolve.file_fd else null, shared_buffer, @@ -1476,7 +1476,7 @@ pub fn NewBundler(cache_files: bool) type { => { const entry = bundler.resolver.caches.fs.readFileShared( bundler.fs, - file_path.text, + file_path.textZ(), resolve.dirname_fd, if (resolve.file_fd != 0) resolve.file_fd else null, shared_buffer, @@ -2046,6 +2046,7 @@ pub fn NewBundler(cache_files: bool) type { relative_path: string, _extension: string, comptime client_entry_point_enabled: bool, + comptime serve_as_package_path: bool, ) !ServeResult { var extension = _extension; var old_log = bundler.log; @@ -2061,13 +2062,15 @@ pub fn NewBundler(cache_files: bool) type { }; } - // We make some things faster in theory by using absolute paths instead of relative paths - var absolute_path = resolve_path.joinAbsStringBuf( - bundler.fs.top_level_dir, - &tmp_buildfile_buf, - &([_][]const u8{relative_path}), - .auto, - ); + var absolute_path = if (comptime serve_as_package_path) + relative_path + else + resolve_path.joinAbsStringBuf( + bundler.fs.top_level_dir, + &tmp_buildfile_buf, + &([_][]const u8{relative_path}), + .auto, + ); defer { js_ast.Expr.Data.Store.reset(); diff --git a/src/cache.zig b/src/cache.zig index 840cd7357..73c093d57 100644 --- a/src/cache.zig +++ b/src/cache.zig @@ -68,7 +68,7 @@ pub fn NewCache(comptime cache_files: bool) type { pub fn readFileShared( c: *Fs, _fs: *fs.FileSystem, - path: string, + path: [:0]const u8, dirname_fd: StoredFileDescriptorType, _file_handle: ?StoredFileDescriptorType, shared: *MutableString, @@ -88,11 +88,7 @@ pub fn NewCache(comptime cache_files: bool) type { var file_handle: std.fs.File = if (_file_handle) |__file| std.fs.File{ .handle = __file } else undefined; if (_file_handle == null) { - if (FeatureFlags.store_file_descriptors and dirname_fd > 0) { - file_handle = try std.fs.Dir.openFile(std.fs.Dir{ .fd = dirname_fd }, std.fs.path.basename(path), .{ .read = true }); - } else { - file_handle = try std.fs.openFileAbsolute(path, .{ .read = true }); - } + file_handle = try std.fs.openFileAbsoluteZ(path, .{ .read = true }); } defer { diff --git a/src/fallback.ts b/src/fallback.ts index ae969062c..103c9db1d 100644 --- a/src/fallback.ts +++ b/src/fallback.ts @@ -6,8 +6,8 @@ import { } from "./api/schema"; function getFallbackInfo(): FallbackMessageContainer { - var binary_string = globalThis.atob( - document.getElementById("#__bunfallback").textContent.trim() + const binary_string = globalThis.atob( + document.getElementById("__bunfallback").textContent.trim() ); var len = binary_string.length; diff --git a/src/fallback.version b/src/fallback.version index e684473e4..fb76dcb20 100644 --- a/src/fallback.version +++ b/src/fallback.version @@ -1 +1 @@ -703503c7cc54abc8
\ No newline at end of file +c098be5f3e938123
\ No newline at end of file diff --git a/src/fs.zig b/src/fs.zig index 2bed1ee47..f1e572835 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -368,7 +368,7 @@ pub const FileSystem = struct { pub fn kind(entry: *Entry, fs: *Implementation) Kind { if (entry.need_stat) { entry.need_stat = false; - entry.cache = fs.kind(entry.dir, entry.base()) catch unreachable; + entry.cache = fs.kind(entry.dir, entry.base(), entry.cache.fd) catch unreachable; } return entry.cache.kind; } @@ -376,7 +376,7 @@ pub const FileSystem = struct { pub fn symlink(entry: *Entry, fs: *Implementation) string { if (entry.need_stat) { entry.need_stat = false; - entry.cache = fs.kind(entry.dir, entry.base()) catch unreachable; + entry.cache = fs.kind(entry.dir, entry.base(), entry.cache.fd) catch unreachable; } return entry.cache.symlink; } @@ -398,6 +398,10 @@ pub const FileSystem = struct { return @call(.{ .modifier = .always_inline }, path_handler.normalizeString, .{ str, true, .auto }); } + pub fn normalizeBuf(f: *@This(), buf: []u8, str: string) string { + return @call(.{ .modifier = .always_inline }, path_handler.normalizeStringBuf, .{ str, buf, false, .auto, false }); + } + pub fn join(f: *@This(), parts: anytype) string { return @call(.{ .modifier = .always_inline }, path_handler.joinStringBuf, .{ &join_buf, @@ -883,7 +887,7 @@ pub const FileSystem = struct { return try fs.readFileWithHandle(path, _size, file); } - pub fn kind(fs: *RealFS, _dir: string, base: string) !Entry.Cache { + pub fn kind(fs: *RealFS, _dir: string, base: string, existing_fd: StoredFileDescriptorType) !Entry.Cache { var dir = _dir; var combo = [2]string{ dir, base }; var outpath: [std.fs.MAX_PATH_BYTES]u8 = undefined; @@ -900,13 +904,13 @@ pub const FileSystem = struct { var cache = Entry.Cache{ .kind = Entry.Kind.file, .symlink = "" }; var symlink: []const u8 = ""; if (is_symlink) { - var file = try std.fs.openFileAbsoluteZ(absolute_path_c, .{ .read = true }); + var file = if (existing_fd != 0) std.fs.File{ .handle = existing_fd } else try std.fs.openFileAbsoluteZ(absolute_path_c, .{ .read = true }); setMaxFd(file.handle); defer { - if (fs.needToCloseFiles()) { + if (fs.needToCloseFiles() and existing_fd == 0) { file.close(); - } else { + } else if (comptime FeatureFlags.store_file_descriptors) { cache.fd = file.handle; } } @@ -1048,6 +1052,10 @@ pub const Path = struct { is_parent_package: bool = false, }; + pub inline fn textZ(this: *const Path) [:0]const u8 { + return @as([:0]const u8, this.text.ptr[0..this.text.len :0]); + } + pub inline fn sourceDir(this: *const Path) string { return this.name.dirWithTrailingSlash(); } diff --git a/src/http.zig b/src/http.zig index e9fd74ee1..e013c7dbf 100644 --- a/src/http.zig +++ b/src/http.zig @@ -17,6 +17,7 @@ const NodeModuleBundle = @import("./node_module_bundle.zig").NodeModuleBundle; const resolve_path = @import("./resolver/resolve_path.zig"); const OutputFile = Options.OutputFile; const DotEnv = @import("./env_loader.zig"); +const mimalloc = @import("./allocators/mimalloc.zig"); pub fn constStrToU8(s: string) []u8 { return @intToPtr([*]u8, @ptrToInt(s.ptr))[0..s.len]; } @@ -109,7 +110,7 @@ pub const RequestContext = struct { url: URLPath, conn: *tcp.Connection, allocator: *std.mem.Allocator, - arena: std.heap.ArenaAllocator, + arena: *std.heap.ArenaAllocator, log: logger.Log, bundler: *Bundler, keep_alive: bool = true, @@ -508,7 +509,7 @@ pub const RequestContext = struct { pub fn init( req: Request, - arena: std.heap.ArenaAllocator, + arena: *std.heap.ArenaAllocator, conn: *tcp.Connection, bundler_: *Bundler, watcher_: *Watcher, @@ -521,7 +522,7 @@ pub const RequestContext = struct { .log = undefined, .url = try URLPath.parse(req.path), .conn = conn, - .allocator = undefined, + .allocator = &arena.allocator, .method = Method.which(req.method) orelse return error.InvalidMethod, .watcher = watcher_, .timer = timer, @@ -993,12 +994,12 @@ pub const RequestContext = struct { // defer stderr.flush() catch {}; Output.Source.set(&output_source); - js_ast.Stmt.Data.Store.create(default_allocator); - js_ast.Expr.Data.Store.create(default_allocator); + js_ast.Stmt.Data.Store.create(std.heap.c_allocator); + js_ast.Expr.Data.Store.create(std.heap.c_allocator); defer Output.flush(); var vm = JavaScript.VirtualMachine.init( - default_allocator, + std.heap.c_allocator, handler.args, handler.existing_bundle, handler.log, @@ -1121,11 +1122,14 @@ pub const RequestContext = struct { ); } + var __arena: std.heap.ArenaAllocator = undefined; pub fn runLoop(vm: *JavaScript.VirtualMachine, thread: *HandlerThread) !void { var module_map = ZigGlobalObject.getModuleRegistryMap(vm.global); - JavaScript.VirtualMachine.vm.has_loaded = true; while (true) { + __arena = std.heap.ArenaAllocator.init(vm.allocator); + JavaScript.VirtualMachine.vm.arena = &__arena; + JavaScript.VirtualMachine.vm.has_loaded = true; defer { JavaScript.VirtualMachine.vm.flush(); std.debug.assert( @@ -1135,9 +1139,13 @@ pub const RequestContext = struct { js_ast.Expr.Data.Store.reset(); JavaScript.Bun.flushCSSImports(); Output.flush(); + JavaScript.VirtualMachine.vm.arena.deinit(); + JavaScript.VirtualMachine.vm.has_loaded = false; + mimalloc.mi_collect(false); } var handler: *JavaScriptHandler = try channel.readItem(); + JavaScript.VirtualMachine.vm.preflush(); JavaScript.EventListenerMixin.emitFetchEvent( @@ -1985,12 +1993,12 @@ pub const RequestContext = struct { return true; } - if (ctx.url.path.len > "blob:".len and strings.eqlComptime(ctx.url.path[0.."blob:".len], "blob:")) { + if (ctx.url.path.len > "blob:".len and strings.eqlComptimeIgnoreLen(ctx.url.path[0.."blob:".len], "blob:")) { try ctx.handleBlobURL(server); return true; } - if (ctx.url.path.len > "bun:".len and strings.eqlComptime(ctx.url.path[0.."bun:".len], "bun:")) { + if (ctx.url.path.len > "bun:".len and strings.eqlComptimeIgnoreLen(ctx.url.path[0.."bun:".len], "bun:")) { try ctx.handleBunURL(server); return true; } @@ -2001,21 +2009,45 @@ pub const RequestContext = struct { pub fn handleGet(ctx: *RequestContext) !void { const result = brk: { if (ctx.bundler.options.isFrontendFrameworkEnabled()) { - break :brk try ctx.bundler.buildFile( - &ctx.log, - ctx.allocator, - ctx.url.pathWithoutAssetPrefix(ctx.bundler.options.routes.asset_prefix_path), - ctx.url.extname, - true, - ); + if (serve_as_package_path) { + break :brk try ctx.bundler.buildFile( + &ctx.log, + ctx.allocator, + ctx.url.pathWithoutAssetPrefix(ctx.bundler.options.routes.asset_prefix_path), + ctx.url.extname, + true, + true, + ); + } else { + break :brk try ctx.bundler.buildFile( + &ctx.log, + ctx.allocator, + ctx.url.pathWithoutAssetPrefix(ctx.bundler.options.routes.asset_prefix_path), + ctx.url.extname, + true, + false, + ); + } } else { - break :brk try ctx.bundler.buildFile( - &ctx.log, - ctx.allocator, - ctx.url.pathWithoutAssetPrefix(ctx.bundler.options.routes.asset_prefix_path), - ctx.url.extname, - false, - ); + if (serve_as_package_path) { + break :brk try ctx.bundler.buildFile( + &ctx.log, + ctx.allocator, + ctx.url.pathWithoutAssetPrefix(ctx.bundler.options.routes.asset_prefix_path), + ctx.url.extname, + false, + true, + ); + } else { + break :brk try ctx.bundler.buildFile( + &ctx.log, + ctx.allocator, + ctx.url.pathWithoutAssetPrefix(ctx.bundler.options.routes.asset_prefix_path), + ctx.url.extname, + false, + true, + ); + } } }; @@ -2033,6 +2065,7 @@ pub const RequestContext = struct { } } }; +var serve_as_package_path = false; // // u32 == File ID from Watcher // pub const WatcherBuildChannel = sync.Channel(u32, .Dynamic); @@ -2072,6 +2105,7 @@ pub const RequestContext = struct { // - Resolver time // - Parsing time // - IO read time + pub const Server = struct { log: logger.Log, allocator: *std.mem.Allocator, @@ -2079,7 +2113,6 @@ pub const Server = struct { watcher: *Watcher, timer: std.time.Timer = undefined, transform_options: Api.TransformOptions, - javascript_enabled: bool = false, pub fn adjustUlimit() !void { @@ -2266,13 +2299,24 @@ pub const Server = struct { } Output.flush(); - // var listener_handle = try std.os.kqueue(); - // var change_list = std.mem.zeroes([2]os.Kevent); - // change_list[0].ident = @intCast(usize, listener.socket.fd); - // change_list[1].ident = @intCast(usize, listener.socket.fd); + var did_init = false; + while (!did_init) { + defer Output.flush(); + var conn = listener.accept(.{ .close_on_exec = true }) catch |err| { + continue; + }; + + // We want to bind to the network socket as quickly as possible so that opening the URL works + // We use a secondary loop so that we avoid the extra branch in a hot code path + server.detectFastRefresh(); + server.detectTSConfig(); + try server.initWatcher(); + did_init = true; + + server.handleConnection(&conn, comptime features); + } - // var eventlist: [128]os.Kevent = undefined; while (true) { defer Output.flush(); var conn = listener.accept(.{ .close_on_exec = true }) catch |err| { @@ -2283,10 +2327,6 @@ pub const Server = struct { } } - pub fn sendError(server: *Server, request: *Request, conn: *tcp.Connection, code: HTTPStatusCode, msg: string) !void { - try server.writeStatus(code, connection); - } - threadlocal var req_buf: [32_000]u8 = undefined; pub const ConnectionFeatures = struct { @@ -2319,7 +2359,8 @@ pub const Server = struct { return; }; - var request_arena = std.heap.ArenaAllocator.init(server.allocator); + var request_arena = server.allocator.create(std.heap.ArenaAllocator) catch unreachable; + request_arena.* = std.heap.ArenaAllocator.init(server.allocator); var req_ctx: RequestContext = undefined; req_ctx = RequestContext.init( @@ -2351,7 +2392,6 @@ pub const Server = struct { } } - req_ctx.allocator = &req_ctx.arena.allocator; req_ctx.log = logger.Log.init(server.allocator); var log = &req_ctx.log; @@ -2524,6 +2564,14 @@ pub const Server = struct { }; } + pub fn detectTSConfig(this: *Server) void { + defer this.bundler.resetStore(); + + const dir_info = (this.bundler.resolver.readDirInfo(this.bundler.fs.top_level_dir) catch return) orelse return; + const tsconfig = dir_info.tsconfig_json orelse return; + serve_as_package_path = tsconfig.base_url_for_paths.len > 0 or tsconfig.base_url.len > 0; + } + pub var global_start_time: std.time.Timer = undefined; pub fn start(allocator: *std.mem.Allocator, options: Api.TransformOptions) !void { var log = logger.Log.init(allocator); @@ -2541,10 +2589,6 @@ pub const Server = struct { server.bundler.configureLinker(); try server.bundler.configureRouter(true); - server.detectFastRefresh(); - - try server.initWatcher(); - const public_folder_is_top_level = server.bundler.options.routes.static_dir_enabled and strings.eql( server.bundler.fs.top_level_dir, server.bundler.options.routes.static_dir, diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.cpp b/src/javascript/jsc/bindings/ZigGlobalObject.cpp index a251d49f1..0d9022a30 100644 --- a/src/javascript/jsc/bindings/ZigGlobalObject.cpp +++ b/src/javascript/jsc/bindings/ZigGlobalObject.cpp @@ -2,35 +2,61 @@ #include "helpers.h" #include "ZigConsoleClient.h" +#include <JavaScriptCore/AggregateError.h> +#include <JavaScriptCore/BytecodeIndex.h> #include <JavaScriptCore/CallFrameInlines.h> #include <JavaScriptCore/CatchScope.h> #include <JavaScriptCore/ClassInfo.h> +#include <JavaScriptCore/CodeBlock.h> +#include <JavaScriptCore/CodeCache.h> #include <JavaScriptCore/Completion.h> #include <JavaScriptCore/Error.h> +#include <JavaScriptCore/ErrorInstance.h> #include <JavaScriptCore/Exception.h> +#include <JavaScriptCore/ExceptionScope.h> +#include <JavaScriptCore/FunctionConstructor.h> #include <JavaScriptCore/HashMapImpl.h> #include <JavaScriptCore/HashMapImplInlines.h> +#include <JavaScriptCore/Heap.h> #include <JavaScriptCore/Identifier.h> #include <JavaScriptCore/InitializeThreading.h> +#include <JavaScriptCore/IteratorOperations.h> +#include <JavaScriptCore/JSArray.h> +#include <JavaScriptCore/JSCInlines.h> +#include <JavaScriptCore/JSCallbackObject.h> #include <JavaScriptCore/JSCast.h> +#include <JavaScriptCore/JSClassRef.h> #include <JavaScriptCore/JSContextInternal.h> #include <JavaScriptCore/JSInternalPromise.h> +#include <JavaScriptCore/JSLock.h> #include <JavaScriptCore/JSMap.h> #include <JavaScriptCore/JSModuleLoader.h> +#include <JavaScriptCore/JSModuleRecord.h> #include <JavaScriptCore/JSNativeStdFunction.h> +#include <JavaScriptCore/JSObject.h> #include <JavaScriptCore/JSPromise.h> +#include <JavaScriptCore/JSSet.h> #include <JavaScriptCore/JSSourceCode.h> #include <JavaScriptCore/JSString.h> #include <JavaScriptCore/JSValueInternal.h> #include <JavaScriptCore/JSVirtualMachineInternal.h> #include <JavaScriptCore/ObjectConstructor.h> +#include <JavaScriptCore/OptionsList.h> +#include <JavaScriptCore/ParserError.h> +#include <JavaScriptCore/ScriptExecutable.h> #include <JavaScriptCore/SourceOrigin.h> +#include <JavaScriptCore/StackFrame.h> +#include <JavaScriptCore/StackVisitor.h> #include <JavaScriptCore/VM.h> +#include <JavaScriptCore/VMEntryScope.h> #include <JavaScriptCore/WasmFaultSignalHandler.h> -#include <wtf/URL.h> - -#include <JavaScriptCore/JSLock.h> #include <wtf/StdLibExtras.h> +#include <wtf/URL.h> +#include <wtf/text/ExternalStringImpl.h> +#include <wtf/text/StringCommon.h> +#include <wtf/text/StringImpl.h> +#include <wtf/text/StringView.h> +#include <wtf/text/WTFString.h> #include <cstdlib> #include <exception> @@ -60,8 +86,13 @@ extern "C" JSC__JSGlobalObject *Zig__GlobalObject__create(JSClassRef *globalObje WTF::initializeMainThread(); JSC::initialize(); - JSC::VM &vm = JSC::VM::create(JSC::LargeHeap).leakRef(); + // JSC::Options::useCodeCache() = false; + JSC::Options::useSourceProviderCache() = true; + JSC::Options::useUnlinkedCodeBlockJettisoning() = false; + JSC::Options::useTopLevelAwait() = true; + JSC::VM &vm = JSC::VM::create(JSC::LargeHeap).leakRef(); + vm.heap.acquireAccess(); #if ENABLE(WEBASSEMBLY) JSC::Wasm::enableFastMemory(); #endif @@ -244,39 +275,80 @@ extern "C" bool Zig__GlobalObject__resetModuleRegistryMap(JSC__JSGlobalObject *g void *map_ptr) { if (map_ptr == nullptr) return false; JSC::JSMap *map = reinterpret_cast<JSC::JSMap *>(map_ptr); - + JSC::VM &vm = globalObject->vm(); if (JSC::JSObject *obj = JSC::jsDynamicCast<JSC::JSObject *>(globalObject->vm(), globalObject->moduleLoader())) { auto identifier = JSC::Identifier::fromString(globalObject->vm(), "registry"); if (JSC::JSMap *oldMap = JSC::jsDynamicCast<JSC::JSMap *>( globalObject->vm(), obj->getDirect(globalObject->vm(), identifier))) { - // Help the GC by releasing the old map. oldMap->clear(globalObject); - // forEachInIterable( - // globalObject, oldMap, [&](VM &vm, JSGlobalObject *globalObject, JSValue nextValue) { - // auto scope = DECLARE_THROW_SCOPE(vm); - // JSC::JSValue key = nextObject->getIndex(globalObject, static_cast<unsigned>(0)); - // RETURN_IF_EXCEPTION(scope, void()); + // vm.finalizeSynchronousJSExecution(); - // if (!map->has(globalObject, key)) { + obj->putDirect(globalObject->vm(), identifier, + map->clone(globalObject, globalObject->vm(), globalObject->mapStructure())); - // JSC::JSValue value = nextObject->getIndex(globalObject, static_cast<unsigned>(1)); - // RETURN_IF_EXCEPTION(scope, void()); + vm.codeCache()->write(vm); + vm.shrinkFootprintWhenIdle(); + // vm.deleteAllLinkedCode(JSC::DeleteAllCodeEffort::DeleteAllCodeIfNotCollecting); + // JSC::Heap::PreventCollectionScope(vm.heap); + // vm.heap.completeAllJITPlans(); + + // vm.forEachScriptExecutableSpace([&](auto &spaceAndSet) { + // JSC::HeapIterationScope heapIterationScope(vm.heap); + // auto &set = spaceAndSet.set; + // set.forEachLiveCell([&](JSC::HeapCell *cell, JSC::HeapCell::Kind) { + // if (JSC::ModuleProgramExecutable *executable = + // JSC::jsDynamicCast<JSC::ModuleProgramExecutable *>(cell)) { + // executable->clearCode(set); // } - // scope.release(); // }); - }; - - return obj->putDirect( - globalObject->vm(), identifier, - map->clone(globalObject, globalObject->vm(), globalObject->mapStructure())); + // }); + } + // globalObject->vm().heap.deleteAllUnlinkedCodeBlocks( + // JSC::DeleteAllCodeEffort::PreventCollectionAndDeleteAllCode); + // vm.whenIdle([globalObject, oldMap, map]() { + // auto recordIdentifier = JSC::Identifier::fromString(globalObject->vm(), "module"); + + // JSC::JSModuleRecord *record; + // JSC::JSValue key; + // JSC::JSValue value; + // JSC::JSObject *mod; + // JSC::JSObject *nextObject; + // JSC::forEachInIterable( + // globalObject, oldMap, + // [&](JSC::VM &vm, JSC::JSGlobalObject *globalObject, JSC::JSValue nextValue) { + // nextObject = JSC::jsDynamicCast<JSC::JSObject *>(vm, nextValue); + // key = nextObject->getIndex(globalObject, static_cast<unsigned>(0)); + + // if (!map->has(globalObject, key)) { + // value = nextObject->getIndex(globalObject, static_cast<unsigned>(1)); + // mod = JSC::jsDynamicCast<JSC::JSObject *>(vm, value); + // if (mod) { + // record = JSC::jsDynamicCast<JSC::JSModuleRecord *>( + // vm, mod->getDirect(vm, recordIdentifier)); + // if (record) { + // auto code = &record->sourceCode(); + // if (code) { + + // Zig::SourceProvider *provider = + // reinterpret_cast<Zig::SourceProvider *>(code->provider()); + // // code->~SourceCode(); + // if (provider) { provider->freeSourceCode(); } + // } + // } + // } + // } + // }); + + // oldMap->clear(globalObject); + // } + // } + // map } - - return false; + return true; } - JSC::JSInternalPromise *GlobalObject::moduleLoaderFetch(JSGlobalObject *globalObject, JSModuleLoader *loader, JSValue key, JSValue value1, JSValue value2) { @@ -319,7 +391,7 @@ JSC::JSInternalPromise *GlobalObject::moduleLoaderFetch(JSGlobalObject *globalOb promise->resolve(globalObject, jsSourceCode); globalObject->vm().drainMicrotasks(); return promise; - } +} JSC::JSObject *GlobalObject::moduleLoaderCreateImportMetaProperties(JSGlobalObject *globalObject, JSModuleLoader *loader, diff --git a/src/javascript/jsc/bindings/ZigSourceProvider.cpp b/src/javascript/jsc/bindings/ZigSourceProvider.cpp index 63bf1dcfd..a2cb86c39 100644 --- a/src/javascript/jsc/bindings/ZigSourceProvider.cpp +++ b/src/javascript/jsc/bindings/ZigSourceProvider.cpp @@ -23,11 +23,34 @@ using String = WTF::String; using SourceProviderSourceType = JSC::SourceProviderSourceType; Ref<SourceProvider> SourceProvider::create(ResolvedSource resolvedSource) { - return adoptRef(*new SourceProvider( - resolvedSource, - JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(toString(resolvedSource.source_url))), - toStringNotConst(resolvedSource.source_url), TextPosition(), - JSC::SourceProviderSourceType::Module)); + void *allocator = resolvedSource.allocator; + + WTF::StringImpl *stringImpl = nullptr; + if (allocator) { + Ref<WTF::ExternalStringImpl> stringImpl_ = WTF::ExternalStringImpl::create( + resolvedSource.source_code.ptr, resolvedSource.source_code.len, + [allocator](WTF::ExternalStringImpl *str, void *ptr, unsigned int len) { + ZigString__free((const unsigned char *)ptr, len, allocator); + }); + return adoptRef(*new SourceProvider( + resolvedSource, reinterpret_cast<WTF::StringImpl *>(stringImpl_.ptr()), + JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(toString(resolvedSource.source_url))), + toStringNotConst(resolvedSource.source_url), TextPosition(), + JSC::SourceProviderSourceType::Module)); + + } else { + Ref<WTF::ExternalStringImpl> stringImpl_ = WTF::ExternalStringImpl::create( + resolvedSource.source_code.ptr, resolvedSource.source_code.len, + [=](WTF::ExternalStringImpl *str, void *ptr, unsigned int len) { + // ZigString__free((const unsigned char *)ptr, len, + // allocator); + }); + return adoptRef(*new SourceProvider( + resolvedSource, reinterpret_cast<WTF::StringImpl *>(stringImpl_.ptr()), + JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(toString(resolvedSource.source_url))), + toStringNotConst(resolvedSource.source_url), TextPosition(), + JSC::SourceProviderSourceType::Module)); + } } unsigned SourceProvider::getHash() { @@ -37,6 +60,20 @@ unsigned SourceProvider::getHash() { return m_hash; } +void SourceProvider::freeSourceCode() { + if (did_free_source_code) { return; } + did_free_source_code = true; + if (m_resolvedSource.allocator != 0) { // // WTF::ExternalStringImpl::destroy(m_source.ptr()); + this->m_source = WTF::StringImpl::empty()->isolatedCopy(); + this->m_hash = 0; + m_resolvedSource.allocator = 0; + } + // if (m_resolvedSource.allocator != 0) { + // ZigString__free(m_resolvedSource.source_code.ptr, m_resolvedSource.source_code.len, + // m_resolvedSource.allocator); + // } +} + void SourceProvider::updateCache(const UnlinkedFunctionExecutable *executable, const SourceCode &, CodeSpecializationKind kind, const UnlinkedFunctionCodeBlock *codeBlock) { @@ -122,15 +159,14 @@ int SourceProvider::readCache(JSC::VM &vm, const JSC::SourceCode &sourceCode) { if (fileTotalSize == 0) return 0; Ref<JSC::CachedBytecode> cachedBytecode = JSC::CachedBytecode::create(WTFMove(mappedFile)); - auto key = JSC::sourceCodeKeyForSerializedModule(vm, sourceCode); - if (isCachedBytecodeStillValid(vm, cachedBytecode.copyRef(), key, - JSC::SourceCodeType::ModuleType)) { - m_cachedBytecode = WTFMove(cachedBytecode); - return 1; - } else { - FileSystem::truncateFile(fd, 0); - return 0; - } + // auto key = JSC::sourceCodeKeyForSerializedModule(vm, sourceCode); + // if (isCachedBytecodeStillValid(vm, cachedBytecode.copyRef(), key, + // JSC::SourceCodeType::ModuleType)) { + m_cachedBytecode = WTFMove(cachedBytecode); + return 1; + // } else { + // FileSystem::truncateFile(fd, 0); + // return 0; + // } } - }; // namespace Zig
\ No newline at end of file diff --git a/src/javascript/jsc/bindings/ZigSourceProvider.h b/src/javascript/jsc/bindings/ZigSourceProvider.h index 11e89310a..41002af97 100644 --- a/src/javascript/jsc/bindings/ZigSourceProvider.h +++ b/src/javascript/jsc/bindings/ZigSourceProvider.h @@ -34,7 +34,11 @@ class SourceProvider final : public JSC::SourceProvider { public: static Ref<SourceProvider> create(ResolvedSource resolvedSource); - ~SourceProvider() { commitCachedBytecode(); } + ~SourceProvider() { + freeSourceCode(); + + commitCachedBytecode(); + } unsigned hash() const { return m_hash; }; StringView source() const { return StringView(m_source.get()); } @@ -52,23 +56,25 @@ class SourceProvider final : public JSC::SourceProvider { void readOrGenerateByteCodeCache(JSC::VM &vm, const JSC::SourceCode &sourceCode); ResolvedSource m_resolvedSource; int readCache(JSC::VM &vm, const JSC::SourceCode &sourceCode); + void freeSourceCode(); private: - SourceProvider(ResolvedSource resolvedSource, const SourceOrigin &sourceOrigin, - WTF::String &&sourceURL, const TextPosition &startPosition, - JSC::SourceProviderSourceType sourceType) - : Base(sourceOrigin, WTFMove(sourceURL), startPosition, sourceType), - m_source( - *WTF::String(resolvedSource.source_code.ptr, resolvedSource.source_code.len).impl()) { + SourceProvider(ResolvedSource resolvedSource, WTF::StringImpl *sourceImpl, + const SourceOrigin &sourceOrigin, WTF::String &&sourceURL, + const TextPosition &startPosition, JSC::SourceProviderSourceType sourceType) + : Base(sourceOrigin, WTFMove(sourceURL), startPosition, sourceType), m_source(*sourceImpl) { m_resolvedSource = resolvedSource; + m_hash = resolvedSource.hash; getHash(); } + unsigned m_hash; unsigned getHash(); RefPtr<JSC::CachedBytecode> m_cachedBytecode; Ref<WTF::StringImpl> m_source; + bool did_free_source_code = false; // JSC::SourceCodeKey key; }; diff --git a/src/javascript/jsc/bindings/exports.zig b/src/javascript/jsc/bindings/exports.zig index 41e13d98e..07fd9b02c 100644 --- a/src/javascript/jsc/bindings/exports.zig +++ b/src/javascript/jsc/bindings/exports.zig @@ -217,10 +217,19 @@ pub const ResolvedSource = extern struct { source_url: ZigString, hash: u32, + allocator: ?*c_void, + // 0 means disabled bytecodecache_fd: u64, }; +export fn ZigString__free(ptr: [*]const u8, len: usize, allocator_: ?*c_void) void { + var allocator: *std.mem.Allocator = @ptrCast(*std.mem.Allocator, @alignCast(@alignOf(*std.mem.Allocator), allocator_ orelse return)); + + var str = ptr[0..len]; + allocator.free(str); +} + pub const JSErrorCode = enum(u8) { Error = 0, EvalError = 1, diff --git a/src/javascript/jsc/bindings/header-gen.zig b/src/javascript/jsc/bindings/header-gen.zig index 692f60130..8b9bb504e 100644 --- a/src/javascript/jsc/bindings/header-gen.zig +++ b/src/javascript/jsc/bindings/header-gen.zig @@ -67,15 +67,15 @@ pub fn cTypeLabel(comptime Type: type) ?[]const u8 { }; } -var buffer = std.ArrayList(u8).init(default_allocator); +var buffer = std.ArrayList(u8).init(std.heap.c_allocator); var writer = buffer.writer(); -var impl_buffer = std.ArrayList(u8).init(default_allocator); +var impl_buffer = std.ArrayList(u8).init(std.heap.c_allocator); var impl_writer = impl_buffer.writer(); -var bufset = std.BufSet.init(default_allocator); -var type_names = TypeNameMap.init(default_allocator); -var opaque_types = std.BufSet.init(default_allocator); -var size_map = std.StringHashMap(u32).init(default_allocator); -var align_map = std.StringHashMap(u29).init(default_allocator); +var bufset = std.BufSet.init(std.heap.c_allocator); +var type_names = TypeNameMap.init(std.heap.c_allocator); +var opaque_types = std.BufSet.init(std.heap.c_allocator); +var size_map = std.StringHashMap(u32).init(std.heap.c_allocator); +var align_map = std.StringHashMap(u29).init(std.heap.c_allocator); pub const C_Generator = struct { filebase: []const u8, @@ -618,13 +618,13 @@ pub fn HeaderGen(comptime import: type, comptime fname: []const u8) type { \\ ) catch {}; - var impl_second_buffer = std.ArrayList(u8).init(default_allocator); + var impl_second_buffer = std.ArrayList(u8).init(std.heap.c_allocator); var impl_second_writer = impl_second_buffer.writer(); - var impl_third_buffer = std.ArrayList(u8).init(default_allocator); + var impl_third_buffer = std.ArrayList(u8).init(std.heap.c_allocator); var impl_third_writer = impl_third_buffer.writer(); - var impl_fourth_buffer = std.ArrayList(u8).init(default_allocator); + var impl_fourth_buffer = std.ArrayList(u8).init(std.heap.c_allocator); var impl_fourth_writer = impl_fourth_buffer.writer(); // inline for (import.all_static_externs) |static_extern, i| { @@ -770,7 +770,7 @@ pub fn HeaderGen(comptime import: type, comptime fname: []const u8) type { var iter = type_names.iterator(); const NamespaceMap = std.StringArrayHashMap(std.BufMap); - var namespaces = NamespaceMap.init(default_allocator); + var namespaces = NamespaceMap.init(std.heap.c_allocator); var size_iter = size_map.iterator(); while (size_iter.next()) |size| { @@ -808,7 +808,7 @@ pub fn HeaderGen(comptime import: type, comptime fname: []const u8) type { } if (!namespaces.contains(namespace)) { - namespaces.put(namespace, std.BufMap.init(default_allocator)) catch unreachable; + namespaces.put(namespace, std.BufMap.init(std.heap.c_allocator)) catch unreachable; } const class = key[namespace_start + 2 ..]; namespaces.getPtr(namespace).?.put(class, value) catch unreachable; diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h index 50fad777b..312476e25 100644 --- a/src/javascript/jsc/bindings/headers-cpp.h +++ b/src/javascript/jsc/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1628467440 +//-- AUTOGENERATED FILE -- 1630718473 // clang-format off #pragma once diff --git a/src/javascript/jsc/bindings/headers-handwritten.h b/src/javascript/jsc/bindings/headers-handwritten.h index 10bf85a52..a8d53f6e3 100644 --- a/src/javascript/jsc/bindings/headers-handwritten.h +++ b/src/javascript/jsc/bindings/headers-handwritten.h @@ -1,3 +1,5 @@ +#pragma once + typedef uint16_t ZigErrorCode; typedef struct ZigString { @@ -21,6 +23,7 @@ typedef struct ResolvedSource { ZigString source_code; ZigString source_url; uint32_t hash; + void *allocator; uint64_t bytecodecache_fd; } ResolvedSource; typedef union ErrorableResolvedSourceResult { @@ -93,4 +96,5 @@ const JSErrorCode JSErrorCodeUserErrorCode = 254; #ifdef __cplusplus extern "C" ZigErrorCode Zig_ErrorCodeParserError; +extern "C" void ZigString__free(const unsigned char *ptr, size_t len, void *allocator); #endif diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h index ed9678c65..3671095cc 100644 --- a/src/javascript/jsc/bindings/headers.h +++ b/src/javascript/jsc/bindings/headers.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1628467440 +//-- AUTOGENERATED FILE -- 1630718473 // clang-format: off #pragma once diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig index 10a5dd526..98db40c44 100644 --- a/src/javascript/jsc/bindings/headers.zig +++ b/src/javascript/jsc/bindings/headers.zig @@ -37,7 +37,7 @@ pub const __mbstate_t = extern union { pub const __darwin_mbstate_t = __mbstate_t; pub const __darwin_ptrdiff_t = c_long; pub const __darwin_size_t = c_ulong; - + pub const JSC__RegExpPrototype = struct_JSC__RegExpPrototype; pub const JSC__GeneratorPrototype = struct_JSC__GeneratorPrototype; diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index 8bbfa3e74..14b010b17 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -350,6 +350,7 @@ pub const VirtualMachine = struct { flush_list: std.ArrayList(string), entry_point: ServerEntryPoint = undefined, + arena: *std.heap.ArenaAllocator = undefined, has_loaded: bool = false, transpiled_count: usize = 0, @@ -415,6 +416,14 @@ pub const VirtualMachine = struct { ); VirtualMachine.vm_loaded = true; + if (!source_code_printer_loaded) { + var writer = try js_printer.BufferWriter.init(allocator); + source_code_printer = js_printer.BufferPrinter.init(writer); + source_code_printer.ctx.append_null_byte = false; + + source_code_printer_loaded = true; + } + return VirtualMachine.vm; } @@ -460,6 +469,7 @@ pub const VirtualMachine = struct { const code = try vm.node_modules.?.readCodeAsStringSlow(vm.allocator); return ResolvedSource{ + .allocator = null, .source_code = ZigString.init(code), .specifier = ZigString.init(vm.bundler.linker.nodeModuleBundleImportPath()), .source_url = ZigString.init(vm.bundler.options.node_modules_bundle_pretty_path), @@ -471,6 +481,7 @@ pub const VirtualMachine = struct { }; } else if (strings.eqlComptime(_specifier, Runtime.Runtime.Imports.Name)) { return ResolvedSource{ + .allocator = null, .source_code = ZigString.init(Runtime.Runtime.sourceContent()), .specifier = ZigString.init(Runtime.Runtime.Imports.Name), .source_url = ZigString.init(Runtime.Runtime.Imports.Name), @@ -521,14 +532,6 @@ pub const VirtualMachine = struct { false, ); - if (!source_code_printer_loaded) { - var writer = try js_printer.BufferWriter.init(vm.allocator); - source_code_printer = js_printer.BufferPrinter.init(writer); - source_code_printer.ctx.append_null_byte = false; - - source_code_printer_loaded = true; - } - source_code_printer.ctx.reset(); var written = try vm.bundler.print( @@ -543,6 +546,7 @@ pub const VirtualMachine = struct { } return ResolvedSource{ + .allocator = null, .source_code = ZigString.init(vm.allocator.dupe(u8, source_code_printer.ctx.written) catch unreachable), .specifier = ZigString.init(std.mem.span(main_file_name)), .source_url = ZigString.init(std.mem.span(main_file_name)), @@ -564,6 +568,8 @@ pub const VirtualMachine = struct { vm.bundler.resetStore(); const hash = http.Watcher.getHash(path.text); + var allocator = if (vm.has_loaded) &vm.arena.allocator else vm.allocator; + var fd: ?StoredFileDescriptorType = null; if (vm.watcher) |watcher| { @@ -583,7 +589,7 @@ pub const VirtualMachine = struct { } var parse_result = vm.bundler.parse( - vm.bundler.allocator, + allocator, path, loader, 0, @@ -606,14 +612,6 @@ pub const VirtualMachine = struct { vm.resolved_count += vm.bundler.linker.import_counter - start_count; vm.bundler.linker.import_counter = 0; - if (!source_code_printer_loaded) { - var writer = try js_printer.BufferWriter.init(vm.allocator); - source_code_printer = js_printer.BufferPrinter.init(writer); - source_code_printer.ctx.append_null_byte = false; - - source_code_printer_loaded = true; - } - source_code_printer.ctx.reset(); var written = try vm.bundler.print( @@ -628,6 +626,7 @@ pub const VirtualMachine = struct { } return ResolvedSource{ + .allocator = if (vm.has_loaded) vm.allocator else null, .source_code = ZigString.init(vm.allocator.dupe(u8, source_code_printer.ctx.written) catch unreachable), .specifier = ZigString.init(specifier), .source_url = ZigString.init(path.text), @@ -637,6 +636,7 @@ pub const VirtualMachine = struct { }, else => { return ResolvedSource{ + .allocator = vm.allocator, .source_code = ZigString.init(try strings.quotedAlloc(VirtualMachine.vm.allocator, path.pretty)), .specifier = ZigString.init(path.text), .source_url = ZigString.init(path.text), @@ -1334,6 +1334,7 @@ pub const EventListenerMixin = struct { // Rely on JS finalizer var fetch_event = try vm.allocator.create(FetchEvent); + fetch_event.* = FetchEvent{ .request_context = request_context, .request = Request{ .request_context = request_context }, diff --git a/src/js_ast.zig b/src/js_ast.zig index 8899df8c5..750100750 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -31,7 +31,7 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { used: usize = 0, allocator: *std.mem.Allocator, - pub fn isFull(block: *const Block) bool { + pub inline fn isFull(block: *const Block) bool { return block.used >= block.items.len; } diff --git a/src/linker.zig b/src/linker.zig index f07fa3f07..dea14f675 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -166,7 +166,11 @@ pub fn NewLinker(comptime BundlerType: type) type { } pub inline fn nodeModuleBundleImportPath(this: *const ThisLinker) string { - return if (this.options.node_modules_bundle_url.len > 0) this.options.node_modules_bundle_url else this.options.node_modules_bundle.?.bundle.import_from_name; + return if (this.options.platform != .bun and + this.options.node_modules_bundle_url.len > 0) + this.options.node_modules_bundle_url + else + this.options.node_modules_bundle.?.bundle.import_from_name; } // pub const Scratch = struct { diff --git a/src/memory_allocator.zig b/src/memory_allocator.zig index 3f5a7897c..d8476d0f8 100644 --- a/src/memory_allocator.zig +++ b/src/memory_allocator.zig @@ -115,6 +115,7 @@ const CAllocator = struct { return mem.alignAllocLen(full_len, new_len, len_align); } } + return error.OutOfMemory; } }; diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 1d438999d..4d8f91881 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -40,7 +40,8 @@ pub const TemporaryBuffer = struct { pub threadlocal var ExtensionPathBuf: [512]u8 = undefined; pub threadlocal var TSConfigMatchStarBuf: [512]u8 = undefined; pub threadlocal var TSConfigMatchPathBuf: [512]u8 = undefined; - pub threadlocal var TSConfigMatchFullBuf: [512]u8 = undefined; + pub threadlocal var TSConfigMatchFullBuf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + pub threadlocal var TSConfigMatchFullBuf2: [std.fs.MAX_PATH_BYTES]u8 = undefined; }; pub const PathPair = struct { @@ -195,6 +196,7 @@ threadlocal var _open_dirs: [256]std.fs.Dir = undefined; threadlocal var resolve_without_remapping_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; threadlocal var index_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; threadlocal var dir_info_uncached_filename_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; +threadlocal var dir_info_uncached_path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; threadlocal var tsconfig_base_url_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; threadlocal var relative_abs_path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; threadlocal var load_as_file_or_directory_via_tsconfig_base_path: [std.fs.MAX_PATH_BYTES]u8 = undefined; @@ -1240,16 +1242,22 @@ pub fn NewResolver(cache_files: bool) type { return r.dir_cache.get(path); } - inline fn dirInfoCachedMaybeLog(r: *ThisResolver, path: string, comptime enable_logging: bool, comptime follow_symlinks: bool) !?*DirInfo { + inline fn dirInfoCachedMaybeLog(r: *ThisResolver, __path: string, comptime enable_logging: bool, comptime follow_symlinks: bool) !?*DirInfo { r.mutex.lock(); defer r.mutex.unlock(); + var _path = __path; + if (strings.eqlComptime(_path, "./") or strings.eqlComptime(_path, ".")) + _path = r.fs.top_level_dir; - const top_result = try r.dir_cache.getOrPut(path); + const top_result = try r.dir_cache.getOrPut(_path); if (top_result.status != .unknown) { return r.dir_cache.atIndex(top_result.index); } var i: i32 = 1; + std.mem.copy(u8, &dir_info_uncached_path_buf, _path); + var path = dir_info_uncached_path_buf[0.._path.len]; + _dir_entry_paths_to_resolve[0] = (DirEntryResolveQueueItem{ .result = top_result, .unsafe_path = path, .safe_path = "" }); var top = Dirname.dirname(path); @@ -1258,7 +1266,12 @@ pub fn NewResolver(cache_files: bool) type { .hash = 0, .status = .not_found, }; - const root_path = if (isWindows) std.fs.path.diskDesignator(path) else "/"; + const root_path = if (comptime isWindows) + std.fs.path.diskDesignator(path) + else + // we cannot just use "/" + // we will write to the buffer past the ptr len so it must be a non-const buffer + path[0..1]; var rfs: *Fs.FileSystem.RealFS = &r.fs.fs; rfs.entries_mutex.lock(); @@ -1321,16 +1334,15 @@ pub fn NewResolver(cache_files: bool) type { // We want to walk in a straight line from the topmost directory to the desired directory // For each directory we visit, we get the entries, but not traverse into child directories // (unless those child directores are in the queue) - // Going top-down rather than bottom-up should have best performance because we can use - // the file handle from the parent directory to open the child directory - // It's important that we walk in precisely a straight line - // For example + // We go top-down instead of bottom-up to increase odds of reusing previously open file handles // "/home/jarred/Code/node_modules/react/cjs/react.development.js" // ^ // If we start there, we will traverse all of /home/jarred, including e.g. /home/jarred/Downloads // which is completely irrelevant. - // After much experimentation, fts_open is not the fastest way. fts actually just uses readdir!! + // After much experimentation... + // - fts_open is not the fastest way to read directories. fts actually just uses readdir!! + // - remember var _safe_path: ?string = null; // Start at the top. @@ -1341,20 +1353,21 @@ pub fn NewResolver(cache_files: bool) type { var _open_dir: anyerror!std.fs.Dir = undefined; if (queue_top.fd == 0) { - if (open_dir_count > 0) { - _open_dir = _open_dirs[open_dir_count - 1].openDir( - std.fs.path.basename(queue_top.unsafe_path), - .{ .iterate = true, .no_follow = !follow_symlinks }, - ); - } else { - _open_dir = std.fs.openDirAbsolute( - queue_top.unsafe_path, - .{ - .iterate = true, - .no_follow = !follow_symlinks, - }, - ); - } + + // This saves us N copies of .toPosixPath + // which was likely the perf gain from resolving directories relative to the parent directory, anyway. + const prev_char = path.ptr[queue_top.unsafe_path.len]; + path.ptr[queue_top.unsafe_path.len] = 0; + defer path.ptr[queue_top.unsafe_path.len] = prev_char; + var sentinel = path.ptr[0..queue_top.unsafe_path.len :0]; + _open_dir = std.fs.openDirAbsoluteZ( + sentinel, + .{ + .iterate = true, + .no_follow = !follow_symlinks, + }, + ); + // } } const open_dir = if (queue_top.fd != 0) std.fs.Dir{ .fd = queue_top.fd } else (_open_dir catch |err| { @@ -1495,14 +1508,7 @@ pub fn NewResolver(cache_files: bool) type { // This closely follows the behavior of "tryLoadModuleUsingPaths()" in the // official TypeScript compiler - pub fn matchTSConfigPaths(r: *ThisResolver, tsconfig: *TSConfigJSON, path_: string, kind: ast.ImportKind) ?MatchResult { - // Rewrite absolute import paths to be project-relative - // This is so that whe nimporting URLs from the web, we can still match them. - var path = path_; - if (strings.startsWith(path_, r.fs.top_level_dir)) { - path = path[r.fs.top_level_dir.len..]; - } - + pub fn matchTSConfigPaths(r: *ThisResolver, tsconfig: *TSConfigJSON, path: string, kind: ast.ImportKind) ?MatchResult { if (r.debug_logs) |*debug| { debug.addNoteFmt("Matching \"{s}\" against \"paths\" in \"{s}\"", .{ path, tsconfig.abs_path }) catch unreachable; } @@ -1569,9 +1575,8 @@ pub fn NewResolver(cache_files: bool) type { // because we want the output to always be deterministic if (strings.startsWith(path, prefix) and strings.endsWith(path, suffix) and - (prefix.len > longest_match_prefix_length or - (prefix.len == longest_match_prefix_length and - suffix.len > longest_match_suffix_length))) + (prefix.len >= longest_match_prefix_length and + suffix.len > longest_match_suffix_length)) { longest_match_prefix_length = @intCast(i32, prefix.len); longest_match_suffix_length = @intCast(i32, suffix.len); @@ -1591,31 +1596,20 @@ pub fn NewResolver(cache_files: bool) type { // Swap out the "*" in the original path for whatever the "*" matched const matched_text = path[longest_match.prefix.len .. path.len - longest_match.suffix.len]; - std.mem.copy( - u8, - &TemporaryBuffer.TSConfigMatchPathBuf, - original_path, - ); - var start: usize = 0; - var total_length: usize = 0; - const star = std.mem.indexOfScalar(u8, original_path, '*') orelse unreachable; - total_length = star; - const parts = [_]string{ original_path[0..total_length], matched_text, longest_match.suffix }; - const region = r.fs.joinBuf(&parts, &TemporaryBuffer.TSConfigMatchPathBuf); - - // Load the original path relative to the "baseUrl" from tsconfig.json - var absolute_original_path: string = region; - - if (!std.fs.path.isAbsolute(region)) { - var paths = [_]string{ abs_base_url, region }; - absolute_original_path = r.fs.absAlloc(r.allocator, &paths) catch unreachable; - } else { - absolute_original_path = std.mem.dupe(r.allocator, u8, region) catch unreachable; - } + const total_length = std.mem.indexOfScalar(u8, original_path, '*') orelse unreachable; + var prefix_parts = [_]string{ abs_base_url, original_path[0..total_length] }; - defer { - r.allocator.free(absolute_original_path); - } + // 1. Normalize the base path + // so that "/Users/foo/project/", "../components/*" => "/Users/foo/components/"" + var prefix = r.fs.absBuf(&prefix_parts, &TemporaryBuffer.TSConfigMatchFullBuf2); + + // 2. Join the new base path with the matched result + // so that "/Users/foo/components/", "/foo/bar" => /Users/foo/components/foo/bar + var parts = [_]string{ prefix, std.mem.trimLeft(u8, matched_text, "/"), std.mem.trimLeft(u8, longest_match.suffix, "/") }; + var absolute_original_path = r.fs.absBuf( + &parts, + &TemporaryBuffer.TSConfigMatchFullBuf, + ); if (r.loadAsFileOrDirectory(absolute_original_path, kind)) |res| { return res; @@ -1881,7 +1875,10 @@ pub fn NewResolver(cache_files: bool) type { } } - const dir_info = (r.dirInfoCached(path) catch null) orelse return null; + const dir_info = (r.dirInfoCached(path) catch |err| { + if (comptime isDebug) Output.prettyErrorln("err: {s} reading {s}", .{ @errorName(err), path }); + return null; + }) orelse return null; var package_json: ?*PackageJSON = null; // Try using the main field(s) from "package.json" @@ -2181,6 +2178,7 @@ pub fn NewResolver(cache_files: bool) type { if (!r.opts.preserve_symlinks) { if (parent.?.getEntries()) |parent_entries| { if (parent_entries.get(base)) |lookup| { + if (entries.fd != 0 and lookup.entry.cache.fd == 0) lookup.entry.cache.fd = entries.fd; const entry = lookup.entry; var symlink = entry.symlink(rfs); diff --git a/src/resolver/tsconfig_json.zig b/src/resolver/tsconfig_json.zig index 5ca1f4b26..20ebd3990 100644 --- a/src/resolver/tsconfig_json.zig +++ b/src/resolver/tsconfig_json.zig @@ -39,7 +39,7 @@ pub const TSConfigJSON = struct { preserve_imports_not_used_as_values: bool = false, - pub fn hasBaseURL(tsconfig: *TSConfigJSON) bool { + pub fn hasBaseURL(tsconfig: *const TSConfigJSON) bool { return tsconfig.base_url.len > 0; } @@ -214,7 +214,6 @@ pub const TSConfigJSON = struct { } } if (count > 0) { - result.paths.put( key, values[0..count], diff --git a/src/string_immutable.zig b/src/string_immutable.zig index f48850908..055239c1a 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -275,16 +275,24 @@ pub inline fn eqlInsensitive(self: string, other: anytype) bool { } pub fn eqlComptime(self: string, comptime alt: anytype) bool { + return eqlComptimeCheckLen(self, alt, true); +} + +pub fn eqlComptimeIgnoreLen(self: string, comptime alt: anytype) bool { + return eqlComptimeCheckLen(self, alt, false); +} + +inline fn eqlComptimeCheckLen(self: string, comptime alt: anytype, comptime check_len: bool) bool { switch (comptime alt.len) { 0 => { @compileError("Invalid size passed to eqlComptime"); }, 2 => { const check = comptime std.mem.readIntNative(u16, alt[0..alt.len]); - return self.len == alt.len and std.mem.readIntNative(u16, self[0..2]) == check; + return ((comptime !check_len) or self.len == alt.len) and std.mem.readIntNative(u16, self[0..2]) == check; }, 1, 3 => { - if (alt.len != self.len) { + if ((comptime check_len) and alt.len != self.len) { return false; } @@ -295,7 +303,7 @@ pub fn eqlComptime(self: string, comptime alt: anytype) bool { }, 4 => { const check = comptime std.mem.readIntNative(u32, alt[0..alt.len]); - return self.len == alt.len and std.mem.readIntNative(u32, self[0..4]) == check; + return ((comptime !check_len) or self.len == alt.len) and std.mem.readIntNative(u32, self[0..4]) == check; }, 6 => { const first = std.mem.readIntNative(u32, alt[0..4]); @@ -306,7 +314,7 @@ pub fn eqlComptime(self: string, comptime alt: anytype) bool { }, 5, 7 => { const check = comptime std.mem.readIntNative(u32, alt[0..4]); - if (self.len != alt.len or std.mem.readIntNative(u32, self[0..4]) != check) { + if (((comptime check_len) and self.len != alt.len) or std.mem.readIntNative(u32, self[0..4]) != check) { return false; } const remainder = self[4..]; @@ -317,12 +325,12 @@ pub fn eqlComptime(self: string, comptime alt: anytype) bool { }, 8 => { const check = comptime std.mem.readIntNative(u64, alt[0..alt.len]); - return self.len == alt.len and std.mem.readIntNative(u64, self[0..8]) == check; + return ((comptime !check_len) or self.len == alt.len) and std.mem.readIntNative(u64, self[0..8]) == check; }, 9...11 => { const first = std.mem.readIntNative(u64, alt[0..8]); - if (self.len != alt.len or first != std.mem.readIntNative(u64, self[0..8])) { + if (((comptime check_len) and self.len != alt.len) or first != std.mem.readIntNative(u64, self[0..8])) { return false; } @@ -334,13 +342,13 @@ pub fn eqlComptime(self: string, comptime alt: anytype) bool { 12 => { const first = comptime std.mem.readIntNative(u64, alt[0..8]); const second = comptime std.mem.readIntNative(u32, alt[8..12]); - return (self.len == alt.len) and first == std.mem.readIntNative(u64, self[0..8]) and second == std.mem.readIntNative(u32, self[8..12]); + return ((comptime !check_len) or self.len == alt.len) and first == std.mem.readIntNative(u64, self[0..8]) and second == std.mem.readIntNative(u32, self[8..12]); }, 13...15 => { const first = comptime std.mem.readIntNative(u64, alt[0..8]); const second = comptime std.mem.readIntNative(u32, alt[8..12]); - if (self.len != alt.len or first != std.mem.readIntNative(u64, self[0..8]) or second != std.mem.readIntNative(u32, self[8..12])) { + if (((comptime !check_len) or self.len != alt.len) or first != std.mem.readIntNative(u64, self[0..8]) or second != std.mem.readIntNative(u32, self[8..12])) { return false; } @@ -353,7 +361,7 @@ pub fn eqlComptime(self: string, comptime alt: anytype) bool { 16 => { const first = comptime std.mem.readIntNative(u64, alt[0..8]); const second = comptime std.mem.readIntNative(u64, alt[8..15]); - return (self.len == alt.len) and first == std.mem.readIntNative(u64, self[0..8]) and second == std.mem.readIntNative(u64, self[8..16]); + return ((comptime !check_len) or self.len == alt.len) and first == std.mem.readIntNative(u64, self[0..8]) and second == std.mem.readIntNative(u64, self[8..16]); }, else => { @compileError(alt ++ " is too long."); @@ -365,6 +373,20 @@ pub inline fn append(allocator: *std.mem.Allocator, self: string, other: string) return std.fmt.allocPrint(allocator, "{s}{s}", .{ self, other }); } +pub inline fn joinBuf(out: []u8, parts: anytype, comptime parts_len: usize) []u8 { + var remain = out; + var count: usize = 0; + comptime var i: usize = 0; + inline while (i < parts_len) : (i += 1) { + const part = parts[i]; + std.mem.copy(u8, remain, part); + remain = remain[part.len..]; + count += part.len; + } + + return out[0..count]; +} + pub fn index(self: string, str: string) i32 { if (std.mem.indexOf(u8, self, str)) |i| { return @intCast(i32, i); diff --git a/src/test/fixtures/tsconfig.json b/src/test/fixtures/tsconfig.json index 3408c09f1..b3105de67 100644 --- a/src/test/fixtures/tsconfig.json +++ b/src/test/fixtures/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "baseUrl": "/Users/jarredsumner/Code/bun/src/test/fixtures", "paths": { - "components": ["components/*"] + "components/*": ["components/*"] }, "jsx": "preserve" } |