diff options
author | 2021-05-28 23:26:13 -0700 | |
---|---|---|
committer | 2021-05-28 23:26:13 -0700 | |
commit | 05ec7232bfc97894ae8f04d313fed97c1d619503 (patch) | |
tree | 7cbf3b697e80b807f143422ff25dd364bf072c18 | |
parent | 254a2b779b2b51616f8ab895bd9d54b984b73a2c (diff) | |
download | bun-05ec7232bfc97894ae8f04d313fed97c1d619503.tar.gz bun-05ec7232bfc97894ae8f04d313fed97c1d619503.tar.zst bun-05ec7232bfc97894ae8f04d313fed97c1d619503.zip |
all
Former-commit-id: 664dbf569c423280c4fb40d3114e81e0d7b9ddc3
-rw-r--r-- | esdev-fd-relative.REMOVED.git-id | 1 | ||||
-rw-r--r-- | esdev-fd.REMOVED.git-id | 1 | ||||
-rw-r--r-- | esdev-lists.REMOVED.git-id | 1 | ||||
-rw-r--r-- | esdev-nolists.REMOVED.git-id | 1 | ||||
-rw-r--r-- | src/ast/Wyhash.zig | 332 | ||||
-rw-r--r-- | src/ast/base.zig | 10 | ||||
-rw-r--r-- | src/bundler.zig | 46 | ||||
-rw-r--r-- | src/cache.zig | 24 | ||||
-rw-r--r-- | src/cli.zig | 21 | ||||
-rw-r--r-- | src/fs.zig | 114 | ||||
-rw-r--r-- | src/global.zig | 9 | ||||
-rw-r--r-- | src/resolver/package_json.zig | 10 | ||||
-rw-r--r-- | src/resolver/resolver.zig | 106 | ||||
-rw-r--r-- | src/test/fixtures/await.ts | 4 |
14 files changed, 591 insertions, 89 deletions
diff --git a/esdev-fd-relative.REMOVED.git-id b/esdev-fd-relative.REMOVED.git-id new file mode 100644 index 000000000..5c4e7e6cd --- /dev/null +++ b/esdev-fd-relative.REMOVED.git-id @@ -0,0 +1 @@ +e5a1f05f3dd585ebeddf0b78a78082c4f9d42a31
\ No newline at end of file diff --git a/esdev-fd.REMOVED.git-id b/esdev-fd.REMOVED.git-id new file mode 100644 index 000000000..41da9e584 --- /dev/null +++ b/esdev-fd.REMOVED.git-id @@ -0,0 +1 @@ +3d1b9752b1c462355c07baf04a899d7e243de9ba
\ No newline at end of file diff --git a/esdev-lists.REMOVED.git-id b/esdev-lists.REMOVED.git-id new file mode 100644 index 000000000..61bcfda8e --- /dev/null +++ b/esdev-lists.REMOVED.git-id @@ -0,0 +1 @@ +de17c61c5834facba4ce5ff167414afdb16a81ed
\ No newline at end of file diff --git a/esdev-nolists.REMOVED.git-id b/esdev-nolists.REMOVED.git-id new file mode 100644 index 000000000..8768a3198 --- /dev/null +++ b/esdev-nolists.REMOVED.git-id @@ -0,0 +1 @@ +34dfc5e5b01bb30bf117a1e4e18520646131b19d
\ No newline at end of file diff --git a/src/ast/Wyhash.zig b/src/ast/Wyhash.zig new file mode 100644 index 000000000..b13e267b4 --- /dev/null +++ b/src/ast/Wyhash.zig @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +const std = @import("std"); +const mem = std.mem; + +const primes = [_]u64{ + 0xa0761d6478bd642f, + 0xe7037ed1a0b428db, + 0x8ebc6af09c88c6e3, + 0x589965cc75374cc3, + 0x1d8e4e27c47d124f, +}; + +fn read_bytes(comptime bytes: u8, data: []const u8) u64 { + const T = std.meta.Int(.unsigned, 8 * bytes); + return mem.readIntLittle(T, data[0..bytes]); +} + +fn read_8bytes_swapped(data: []const u8) u64 { + return (read_bytes(4, data) << 32 | read_bytes(4, data[4..])); +} + +fn mum(a: u64, b: u64) u64 { + var r = std.math.mulWide(u64, a, b); + r = (r >> 64) ^ r; + return @truncate(u64, r); +} + +fn mix0(a: u64, b: u64, seed: u64) u64 { + return mum(a ^ seed ^ primes[0], b ^ seed ^ primes[1]); +} + +fn mix1(a: u64, b: u64, seed: u64) u64 { + return mum(a ^ seed ^ primes[2], b ^ seed ^ primes[3]); +} + +// Wyhash version which does not store internal state for handling partial buffers. +// This is needed so that we can maximize the speed for the short key case, which will +// use the non-iterative api which the public Wyhash exposes. +pub fn WyhashGenerator(comptime ValueType: type) type { + return struct { + seed: u64, + msg_len: usize, + + pub fn init(seed: u64) WyhashStateless { + return WyhashStateless{ + .seed = seed, + .msg_len = 0, + }; + } + + fn round(self: *WyhashStateless, b: []const u8) void { + std.debug.assert(b.len == 32); + + self.seed = mix0( + read_bytes(8, b[0..]), + read_bytes(8, b[8..]), + self.seed, + ) ^ mix1( + read_bytes(8, b[16..]), + read_bytes(8, b[24..]), + self.seed, + ); + } + + pub fn update(self: *WyhashStateless, b: []const u8) void { + std.debug.assert(b.len % 32 == 0); + + var off: usize = 0; + while (off < b.len) : (off += 32) { + @call(.{ .modifier = .always_inline }, self.round, .{b[off .. off + 32]}); + } + + self.msg_len += b.len; + } + + pub fn final(self: *WyhashStateless, b: []const u8) u64 { + std.debug.assert(b.len < 32); + + const seed = self.seed; + const rem_len = @intCast(u5, b.len); + const rem_key = b[0..rem_len]; + + self.seed = switch (rem_len) { + 0 => seed, + 1 => mix0(read_bytes(1, rem_key), primes[4], seed), + 2 => mix0(read_bytes(2, rem_key), primes[4], seed), + 3 => mix0((read_bytes(2, rem_key) << 8) | read_bytes(1, rem_key[2..]), primes[4], seed), + 4 => mix0(read_bytes(4, rem_key), primes[4], seed), + 5 => mix0((read_bytes(4, rem_key) << 8) | read_bytes(1, rem_key[4..]), primes[4], seed), + 6 => mix0((read_bytes(4, rem_key) << 16) | read_bytes(2, rem_key[4..]), primes[4], seed), + 7 => mix0((read_bytes(4, rem_key) << 24) | (read_bytes(2, rem_key[4..]) << 8) | read_bytes(1, rem_key[6..]), primes[4], seed), + 8 => mix0(read_8bytes_swapped(rem_key), primes[4], seed), + 9 => mix0(read_8bytes_swapped(rem_key), read_bytes(1, rem_key[8..]), seed), + 10 => mix0(read_8bytes_swapped(rem_key), read_bytes(2, rem_key[8..]), seed), + 11 => mix0(read_8bytes_swapped(rem_key), (read_bytes(2, rem_key[8..]) << 8) | read_bytes(1, rem_key[10..]), seed), + 12 => mix0(read_8bytes_swapped(rem_key), read_bytes(4, rem_key[8..]), seed), + 13 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 8) | read_bytes(1, rem_key[12..]), seed), + 14 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 16) | read_bytes(2, rem_key[12..]), seed), + 15 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 24) | (read_bytes(2, rem_key[12..]) << 8) | read_bytes(1, rem_key[14..]), seed), + 16 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed), + 17 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(1, rem_key[16..]), primes[4], seed), + 18 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(2, rem_key[16..]), primes[4], seed), + 19 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(2, rem_key[16..]) << 8) | read_bytes(1, rem_key[18..]), primes[4], seed), + 20 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(4, rem_key[16..]), primes[4], seed), + 21 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 8) | read_bytes(1, rem_key[20..]), primes[4], seed), + 22 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 16) | read_bytes(2, rem_key[20..]), primes[4], seed), + 23 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 24) | (read_bytes(2, rem_key[20..]) << 8) | read_bytes(1, rem_key[22..]), primes[4], seed), + 24 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), primes[4], seed), + 25 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(1, rem_key[24..]), seed), + 26 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(2, rem_key[24..]), seed), + 27 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(2, rem_key[24..]) << 8) | read_bytes(1, rem_key[26..]), seed), + 28 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(4, rem_key[24..]), seed), + 29 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 8) | read_bytes(1, rem_key[28..]), seed), + 30 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 16) | read_bytes(2, rem_key[28..]), seed), + 31 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 24) | (read_bytes(2, rem_key[28..]) << 8) | read_bytes(1, rem_key[30..]), seed), + }; + + self.msg_len += b.len; + return mum(self.seed ^ self.msg_len, primes[4]); + } + + pub fn hash(seed: u64, value: ValueType) u64 { + const input = std.mem.asBytes(&value); + const aligned_len = @sizeOf(value) - (@sizeOf(value) % 32); + + var c = WyhashStateless.init(seed); + @call(.{ .modifier = .always_inline }, c.update, .{input[0..aligned_len]}); + return @call(.{ .modifier = .always_inline }, c.final, .{input[aligned_len..]}); + } + }; +} +// Wyhash version which does not store internal state for handling partial buffers. +// This is needed so that we can maximize the speed for the short key case, which will +// use the non-iterative api which the public Wyhash exposes. +const WyhashStateless = struct { + seed: u64, + msg_len: usize, + + pub fn init(seed: u64) WyhashStateless { + return WyhashStateless{ + .seed = seed, + .msg_len = 0, + }; + } + + fn round(self: *WyhashStateless, b: []const u8) void { + std.debug.assert(b.len == 32); + + self.seed = mix0( + read_bytes(8, b[0..]), + read_bytes(8, b[8..]), + self.seed, + ) ^ mix1( + read_bytes(8, b[16..]), + read_bytes(8, b[24..]), + self.seed, + ); + } + + pub fn update(self: *WyhashStateless, b: []const u8) void { + std.debug.assert(b.len % 32 == 0); + + var off: usize = 0; + while (off < b.len) : (off += 32) { + @call(.{ .modifier = .always_inline }, self.round, .{b[off .. off + 32]}); + } + + self.msg_len += b.len; + } + + pub fn final(self: *WyhashStateless, b: []const u8) u64 { + std.debug.assert(b.len < 32); + + const seed = self.seed; + const rem_len = @intCast(u5, b.len); + const rem_key = b[0..rem_len]; + + self.seed = switch (rem_len) { + 0 => seed, + 1 => mix0(read_bytes(1, rem_key), primes[4], seed), + 2 => mix0(read_bytes(2, rem_key), primes[4], seed), + 3 => mix0((read_bytes(2, rem_key) << 8) | read_bytes(1, rem_key[2..]), primes[4], seed), + 4 => mix0(read_bytes(4, rem_key), primes[4], seed), + 5 => mix0((read_bytes(4, rem_key) << 8) | read_bytes(1, rem_key[4..]), primes[4], seed), + 6 => mix0((read_bytes(4, rem_key) << 16) | read_bytes(2, rem_key[4..]), primes[4], seed), + 7 => mix0((read_bytes(4, rem_key) << 24) | (read_bytes(2, rem_key[4..]) << 8) | read_bytes(1, rem_key[6..]), primes[4], seed), + 8 => mix0(read_8bytes_swapped(rem_key), primes[4], seed), + 9 => mix0(read_8bytes_swapped(rem_key), read_bytes(1, rem_key[8..]), seed), + 10 => mix0(read_8bytes_swapped(rem_key), read_bytes(2, rem_key[8..]), seed), + 11 => mix0(read_8bytes_swapped(rem_key), (read_bytes(2, rem_key[8..]) << 8) | read_bytes(1, rem_key[10..]), seed), + 12 => mix0(read_8bytes_swapped(rem_key), read_bytes(4, rem_key[8..]), seed), + 13 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 8) | read_bytes(1, rem_key[12..]), seed), + 14 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 16) | read_bytes(2, rem_key[12..]), seed), + 15 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 24) | (read_bytes(2, rem_key[12..]) << 8) | read_bytes(1, rem_key[14..]), seed), + 16 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed), + 17 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(1, rem_key[16..]), primes[4], seed), + 18 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(2, rem_key[16..]), primes[4], seed), + 19 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(2, rem_key[16..]) << 8) | read_bytes(1, rem_key[18..]), primes[4], seed), + 20 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(4, rem_key[16..]), primes[4], seed), + 21 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 8) | read_bytes(1, rem_key[20..]), primes[4], seed), + 22 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 16) | read_bytes(2, rem_key[20..]), primes[4], seed), + 23 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 24) | (read_bytes(2, rem_key[20..]) << 8) | read_bytes(1, rem_key[22..]), primes[4], seed), + 24 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), primes[4], seed), + 25 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(1, rem_key[24..]), seed), + 26 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(2, rem_key[24..]), seed), + 27 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(2, rem_key[24..]) << 8) | read_bytes(1, rem_key[26..]), seed), + 28 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(4, rem_key[24..]), seed), + 29 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 8) | read_bytes(1, rem_key[28..]), seed), + 30 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 16) | read_bytes(2, rem_key[28..]), seed), + 31 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 24) | (read_bytes(2, rem_key[28..]) << 8) | read_bytes(1, rem_key[30..]), seed), + }; + + self.msg_len += b.len; + return mum(self.seed ^ self.msg_len, primes[4]); + } + + pub fn hash(seed: u64, input: []const u8) u64 { + const aligned_len = input.len - (input.len % 32); + + var c = WyhashStateless.init(seed); + @call(.{ .modifier = .always_inline }, c.update, .{input[0..aligned_len]}); + return @call(.{ .modifier = .always_inline }, c.final, .{input[aligned_len..]}); + } +}; + +/// Fast non-cryptographic 64bit hash function. +/// See https://github.com/wangyi-fudan/wyhash +pub const Wyhash = struct { + state: WyhashStateless, + + buf: [32]u8, + buf_len: usize, + + pub fn init(seed: u64) Wyhash { + return Wyhash{ + .state = WyhashStateless.init(seed), + .buf = undefined, + .buf_len = 0, + }; + } + + pub fn update(self: *Wyhash, b: []const u8) void { + var off: usize = 0; + + if (self.buf_len != 0 and self.buf_len + b.len >= 32) { + off += 32 - self.buf_len; + mem.copy(u8, self.buf[self.buf_len..], b[0..off]); + self.state.update(self.buf[0..]); + self.buf_len = 0; + } + + const remain_len = b.len - off; + const aligned_len = remain_len - (remain_len % 32); + self.state.update(b[off .. off + aligned_len]); + + mem.copy(u8, self.buf[self.buf_len..], b[off + aligned_len ..]); + self.buf_len += @intCast(u8, b[off + aligned_len ..].len); + } + + pub fn final(self: *Wyhash) u64 { + const seed = self.state.seed; + const rem_len = @intCast(u5, self.buf_len); + const rem_key = self.buf[0..self.buf_len]; + + return self.state.final(rem_key); + } + + pub fn hash(seed: u64, input: []const u8) u64 { + return WyhashStateless.hash(seed, input); + } +}; + +const expectEqual = std.testing.expectEqual; + +test "test vectors" { + const hash = Wyhash.hash; + + try expectEqual(hash(0, ""), 0x0); + try expectEqual(hash(1, "a"), 0xbed235177f41d328); + try expectEqual(hash(2, "abc"), 0xbe348debe59b27c3); + try expectEqual(hash(3, "message digest"), 0x37320f657213a290); + try expectEqual(hash(4, "abcdefghijklmnopqrstuvwxyz"), 0xd0b270e1d8a7019c); + try expectEqual(hash(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x602a1894d3bbfe7f); + try expectEqual(hash(6, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x829e9c148b75970e); +} + +test "test vectors streaming" { + var wh = Wyhash.init(5); + for ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") |e| { + wh.update(mem.asBytes(&e)); + } + try expectEqual(wh.final(), 0x602a1894d3bbfe7f); + + const pattern = "1234567890"; + const count = 8; + const result = 0x829e9c148b75970e; + try expectEqual(Wyhash.hash(6, pattern ** 8), result); + + wh = Wyhash.init(6); + var i: u32 = 0; + while (i < count) : (i += 1) { + wh.update(pattern); + } + try expectEqual(wh.final(), result); +} + +test "iterative non-divisible update" { + var buf: [8192]u8 = undefined; + for (buf) |*e, i| { + e.* = @truncate(u8, i); + } + + const seed = 0x128dad08f; + + var end: usize = 32; + while (end < buf.len) : (end += 32) { + const non_iterative_hash = Wyhash.hash(seed, buf[0..end]); + + var wy = Wyhash.init(seed); + var i: usize = 0; + while (i < end) : (i += 33) { + wy.update(buf[i..std.math.min(i + 33, end)]); + } + const iterative_hash = wy.final(); + + try std.testing.expectEqual(iterative_hash, non_iterative_hash); + } +} diff --git a/src/ast/base.zig b/src/ast/base.zig index cf08ca780..3f10beb37 100644 --- a/src/ast/base.zig +++ b/src/ast/base.zig @@ -27,6 +27,10 @@ pub const Ref = packed struct { inner_index: Int = 0, is_source_contents_slice: bool = false, + pub fn hash(key: Ref) u64 { + @compileError("Dont call"); + } + // 2 bits of padding for whatever is the parent pub const Int = u30; pub const None = Ref{ @@ -70,11 +74,11 @@ pub const RequireOrImportMeta = struct { exports_ref: Ref = Ref.None, is_wrapper_async: bool = false, }; -pub fn debug(comptime fmt: []const u8, args: anytype) callconv(.Inline) void { +pub inline fn debug(comptime fmt: []const u8, args: anytype) void { // Output.print(fmt, args); } -pub fn debugl( +pub inline fn debugl( comptime fmt: []const u8, -) callconv(.Inline) void { +) void { // Output.print("{s}\n", .{fmt}); } diff --git a/src/bundler.zig b/src/bundler.zig index dcb5a25c5..4d88b27f2 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -171,15 +171,20 @@ pub const Bundler = struct { // Run the resolver // Don't parse/print automatically. if (bundler.options.resolve_mode != .lazy) { - if (!bundler.resolve_results.contains(resolve_result.path_pair.primary.text)) { - try bundler.resolve_results.put(resolve_result.path_pair.primary.text, resolve_result); + var hash_key = resolve_result.path_pair.primary.text; + + // Shorter hash key is faster to hash + if (strings.startsWith(resolve_result.path_pair.primary.text, bundler.fs.top_level_dir)) { + hash_key = resolve_result.path_pair.primary.text[bundler.fs.top_level_dir.len..]; + } + + if (!bundler.resolve_results.contains(hash_key)) { + try bundler.resolve_results.put(hash_key, resolve_result); try bundler.resolve_queue.writeItem(resolve_result); } } - if (!strings.eql(import_record.path.text, resolve_result.path_pair.primary.text)) { - import_record.path = try bundler.generateImportPath(source_dir, resolve_result.path_pair.primary.text); - } + import_record.path = try bundler.generateImportPath(source_dir, resolve_result.path_pair.primary.text); } pub fn buildWithResolveResult(bundler: *Bundler, resolve_result: Resolver.Resolver.Result) !?options.OutputFile { @@ -190,8 +195,9 @@ pub const Bundler = struct { // Step 1. Parse & scan const loader = bundler.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file; var file_path = resolve_result.path_pair.primary; + file_path.pretty = relative_paths_list.append(bundler.fs.relativeTo(file_path.text)) catch unreachable; - var result = bundler.parse(file_path, loader) orelse return null; + var result = bundler.parse(file_path, loader, resolve_result.dirname_fd) orelse return null; switch (result.loader) { .jsx, .js, .ts, .tsx => { @@ -304,7 +310,7 @@ pub const Bundler = struct { ast: js_ast.Ast, }; pub var tracing_start: i128 = if (enableTracing) 0 else undefined; - pub fn parse(bundler: *Bundler, path: Fs.Path, loader: options.Loader) ?ParseResult { + pub fn parse(bundler: *Bundler, path: Fs.Path, loader: options.Loader, dirname_fd: StoredFileDescriptorType) ?ParseResult { if (enableTracing) { tracing_start = std.time.nanoTimestamp(); } @@ -314,7 +320,7 @@ pub const Bundler = struct { } } var result: ParseResult = undefined; - const entry = bundler.resolver.caches.fs.readFile(bundler.fs, path.text) catch return null; + const entry = bundler.resolver.caches.fs.readFile(bundler.fs, path.text, dirname_fd) catch return null; const source = logger.Source.initFile(Fs.File{ .path = path, .contents = entry.contents }, bundler.allocator) catch return null; switch (loader) { @@ -498,6 +504,11 @@ pub const Bundler = struct { ) !options.TransformResult { var bundler = try Bundler.init(allocator, log, opts); + // 100.00 µs std.fifo.LinearFifo(resolver.resolver.Result,std.fifo.LinearFifoBufferType { .Dynamic = {}}).writeItemAssumeCapacity + if (bundler.options.resolve_mode != .lazy) { + try bundler.resolve_queue.ensureUnusedCapacity(1000); + } + var entry_points = try allocator.alloc(Resolver.Resolver.Result, bundler.options.entry_points.len); if (isDebug) { @@ -660,6 +671,11 @@ pub const Transformer = struct { var arena: std.heap.ArenaAllocator = undefined; const use_arenas = opts.entry_points.len > 8; + js_ast.Expr.Data.Store.create(allocator); + js_ast.Stmt.Data.Store.create(allocator); + + var ulimit: usize = Fs.FileSystem.RealFS.adjustUlimit(); + var care_about_closing_files = !(FeatureFlags.store_file_descriptors and opts.entry_points.len * 2 < ulimit); for (opts.entry_points) |entry_point, i| { if (use_arenas) { arena = std.heap.ArenaAllocator.init(allocator); @@ -674,19 +690,23 @@ pub const Transformer = struct { var _log = logger.Log.init(allocator); var __log = &_log; - var paths = [_]string{ cwd, entry_point }; - const absolutePath = try std.fs.path.resolve(chosen_alloc, &paths); + const absolutePath = resolve_path.joinAbs(cwd, .auto, entry_point); const file = try std.fs.openFileAbsolute(absolutePath, std.fs.File.OpenFlags{ .read = true }); - defer file.close(); + defer { + if (care_about_closing_files) { + file.close(); + } + } + const stat = try file.stat(); + // 1 byte sentinel const code = try file.readToEndAlloc(allocator, stat.size); defer { if (_log.msgs.items.len == 0) { allocator.free(code); } - chosen_alloc.free(absolutePath); _log.appendTo(log) catch {}; } const _file = Fs.File{ .path = Fs.Path.init(entry_point), .contents = code }; @@ -710,6 +730,8 @@ pub const Transformer = struct { const relative_path = resolve_path.relative(cwd, absolutePath); const out_path = resolve_path.joinAbs2(cwd, .auto, absolutePath, relative_path); try output_files.append(options.OutputFile{ .path = allocator.dupe(u8, out_path) catch continue, .contents = res.js }); + js_ast.Expr.Data.Store.reset(); + js_ast.Stmt.Data.Store.reset(); } return try options.TransformResult.init(output_dir, output_files.toOwnedSlice(), log, allocator); diff --git a/src/cache.zig b/src/cache.zig index 22953a3a2..56a706c5c 100644 --- a/src/cache.zig +++ b/src/cache.zig @@ -37,6 +37,7 @@ pub const Cache = struct { pub const Entry = struct { contents: string, + fd: StoredFileDescriptorType = 0, // Null means its not usable mod_key: ?fs.FileSystem.Implementation.ModKey = null, @@ -56,7 +57,7 @@ pub const Cache = struct { c.entries.deinit(); } - pub fn readFile(c: *Fs, _fs: *fs.FileSystem, path: string) !Entry { + pub fn readFile(c: *Fs, _fs: *fs.FileSystem, path: string, dirname_fd: StoredFileDescriptorType) !Entry { var rfs = _fs.fs; { @@ -67,9 +68,23 @@ pub const Cache = struct { } } + var file_handle: std.fs.File = undefined; + + 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 }); + } + + defer { + if (rfs.needToCloseFiles()) { + file_handle.close(); + } + } + // If the file's modification key hasn't changed since it was cached, assume // the contents of the file are also the same and skip reading the file. - var mod_key: ?fs.FileSystem.Implementation.ModKey = rfs.modKey(path) catch |err| handler: { + var mod_key: ?fs.FileSystem.Implementation.ModKey = rfs.modKeyWithFile(path, file_handle) catch |err| handler: { switch (err) { error.FileNotFound, error.AccessDenied => { return err; @@ -85,14 +100,14 @@ pub const Cache = struct { var file: fs.File = undefined; if (mod_key) |modk| { - file = rfs.readFile(path, modk.size) catch |err| { + file = rfs.readFileWithHandle(path, modk.size, file_handle) catch |err| { if (isDebug) { Output.printError("{s}: readFile error -- {s}", .{ path, @errorName(err) }); } return err; }; } else { - file = rfs.readFile(path, null) catch |err| { + file = rfs.readFileWithHandle(path, null, file_handle) catch |err| { if (isDebug) { Output.printError("{s}: readFile error -- {s}", .{ path, @errorName(err) }); } @@ -103,6 +118,7 @@ pub const Cache = struct { const entry = Entry{ .contents = file.contents, .mod_key = mod_key, + .fd = if (FeatureFlags.store_file_descriptors) file_handle.handle else 0, }; c.mutex.lock(); diff --git a/src/cli.zig b/src/cli.zig index 7668fddab..002eee57a 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -21,6 +21,8 @@ const clap = @import("clap"); const bundler = @import("bundler.zig"); +const fs = @import("fs.zig"); + pub fn constStrToU8(s: string) []u8 { return @intToPtr([*]u8, @ptrToInt(s.ptr))[0..s.len]; } @@ -319,17 +321,19 @@ pub const Cli = struct { var did_write = false; var writer = stdout.writer(); - + var open_file_limit: usize = 32; if (args.write) |write| { if (write) { - var open_file_limit: usize = 32; - if (std.os.getrlimit(.NOFILE)) |limit| { open_file_limit = limit.cur; } else |err| {} - const do_we_need_to_close = open_file_limit > result.output_files.len * 2; + did_write = true; var root_dir = try std.fs.openDirAbsolute(result.outbase, std.fs.Dir.OpenDirOptions{}); + // On posix, file handles automatically close on process exit by the OS + // Closing files shows up in profiling. + // So don't do that unless we actually need to. + const do_we_need_to_close = !FeatureFlags.store_file_descriptors or (@intCast(usize, root_dir.fd) + open_file_limit) < result.output_files.len; defer { if (do_we_need_to_close) { @@ -390,6 +394,11 @@ pub const Cli = struct { if (isDebug) { Output.println("Expr count: {d}", .{js_ast.Expr.icount}); Output.println("Stmt count: {d}", .{js_ast.Stmt.icount}); + + Output.println("File Descriptors: {d} / {d}", .{ + fs.FileSystem.max_fd, + open_file_limit, + }); } if (!did_write) { @@ -415,8 +424,10 @@ pub const Cli = struct { const duration = std.time.nanoTimestamp() - start_time; if (did_write and duration < @as(i128, @as(i128, std.time.ns_per_s) * @as(i128, 2))) { - var elapsed = @divFloor(duration, @as(i128, std.time.ns_per_ms)); + var elapsed = @divExact(duration, @as(i128, std.time.ns_per_ms)); try writer.print("\nCompleted in {d}ms", .{elapsed}); } + + std.os.exit(0); } }; diff --git a/src/fs.zig b/src/fs.zig index 32961d171..d35eacff3 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -28,6 +28,17 @@ pub const FileSystem = struct { dirname_store: *DirnameStore, filename_store: *FilenameStore, + + pub var max_fd: FileDescriptorType = 0; + + pub inline fn setMaxFd(fd: anytype) void { + if (!FeatureFlags.store_file_descriptors) { + return; + } + + max_fd = std.math.max(fd, max_fd); + } + pub var instance: FileSystem = undefined; pub const DirnameStore = allocators.BSSStringList(Preallocate.Counts.dir_entry, 256); @@ -73,6 +84,7 @@ pub const FileSystem = struct { pub const EntryMap = std.StringHashMap(EntryStore.ListIndex); pub const EntryStore = allocators.BSSList(Entry, Preallocate.Counts.files); dir: string, + fd: StoredFileDescriptorType = 0, data: EntryMap, pub fn addEntry(dir: *DirEntry, entry: std.fs.Dir.Entry) !void { @@ -153,7 +165,7 @@ pub const FileSystem = struct { d.data.deinit(); } - pub fn get(entry: *DirEntry, _query: string) ?Entry.Lookup { + pub fn get(entry: *const DirEntry, _query: string) ?Entry.Lookup { if (_query.len == 0) return null; var end: usize = 0; @@ -354,6 +366,17 @@ pub const FileSystem = struct { file_limit: usize = 32, file_quota: usize = 32, + pub fn needToCloseFiles(rfs: *const RealFS) bool { + // On Windows, we must always close open file handles + // Windows locks files + if (!FeatureFlags.store_file_descriptors) { + return true; + } + + // If we're not near the max amount of open files, don't worry about it. + return !(rfs.file_limit > 254 and rfs.file_limit > (FileSystem.max_fd + 1) * 2); + } + // Always try to max out how many files we can keep open pub fn adjustUlimit() usize { var limit = std.os.getrlimit(.NOFILE) catch return 32; @@ -375,7 +398,7 @@ pub const FileSystem = struct { .cwd = cwd, .file_limit = file_limit, .file_quota = file_limit, - .limiter = Limiter.init(allocator), + .limiter = Limiter.init(allocator, file_limit), .watcher = if (enable_watcher) std.StringHashMap(WatchData).init(allocator) else null, }; } @@ -389,9 +412,7 @@ pub const FileSystem = struct { mtime: i128 = 0, mode: std.fs.File.Mode = 0, - pub fn generate(fs: *RealFS, path: string) anyerror!ModKey { - var file = try std.fs.openFileAbsolute(path, std.fs.File.OpenFlags{ .read = true }); - defer file.close(); + pub fn generate(fs: *RealFS, path: string, file: std.fs.File) anyerror!ModKey { const stat = try file.stat(); const seconds = @divTrunc(stat.mtime, @as(@TypeOf(stat.mtime), std.time.ns_per_s)); @@ -437,11 +458,8 @@ pub const FileSystem = struct { } } - pub fn modKey(fs: *RealFS, path: string) anyerror!ModKey { - fs.limiter.before(); - defer fs.limiter.after(); - - const key = ModKey.generate(fs, path) catch |err| { + pub fn modKeyWithFile(fs: *RealFS, path: string, file: anytype) anyerror!ModKey { + const key = ModKey.generate(fs, path, file) catch |err| { fs.modKeyError(path, err); return err; }; @@ -457,6 +475,18 @@ pub const FileSystem = struct { return key; } + pub fn modKey(fs: *RealFS, path: string) anyerror!ModKey { + fs.limiter.before(); + defer fs.limiter.after(); + var file = try std.fs.openFileAbsolute(path, std.fs.File.OpenFlags{ .read = true }); + defer { + if (fs.needToCloseFiles()) { + file.close(); + } + } + return try fs.modKeyWithFile(path, file); + } + pub const WatchData = struct { dir_entries: []string = &([_]string{}), file_contents: string = "", @@ -493,9 +523,9 @@ pub const FileSystem = struct { // Limit the number of files open simultaneously to avoid ulimit issues pub const Limiter = struct { semaphore: Semaphore, - pub fn init(allocator: *std.mem.Allocator) Limiter { + pub fn init(allocator: *std.mem.Allocator, limit: usize) Limiter { return Limiter{ - .semaphore = Semaphore.init(32), + .semaphore = Semaphore.init(limit), // .counter = std.atomic.Int(u8).init(0), // .lock = std.Thread.Mutex.init(), }; @@ -532,6 +562,12 @@ pub const FileSystem = struct { var iter: std.fs.Dir.Iterator = handle.iterate(); var dir = DirEntry.init(_dir, fs.allocator); errdefer dir.deinit(); + + if (FeatureFlags.store_file_descriptors) { + FileSystem.setMaxFd(handle.fd); + dir.fd = handle.fd; + } + while (try iter.next()) |_entry| { try dir.addEntry(_entry); } @@ -585,7 +621,7 @@ pub const FileSystem = struct { var handle = _handle orelse try fs.openDir(dir); defer { - if (_handle == null) { + if (_handle == null and fs.needToCloseFiles()) { handle.close(); } } @@ -596,7 +632,7 @@ pub const FileSystem = struct { } // Cache miss: read the directory entries - const entries = fs.readdir( + var entries = fs.readdir( dir, handle, ) catch |err| { @@ -643,15 +679,8 @@ pub const FileSystem = struct { } } - pub fn readFile(fs: *RealFS, path: string, _size: ?usize) !File { - fs.limiter.before(); - defer fs.limiter.after(); - - const file: std.fs.File = std.fs.openFileAbsolute(path, std.fs.File.OpenFlags{ .read = true, .write = false }) catch |err| { - fs.readFileError(path, err); - return err; - }; - defer file.close(); + pub fn readFileWithHandle(fs: *RealFS, path: string, _size: ?usize, file: std.fs.File) !File { + FileSystem.setMaxFd(file.handle); // Skip the extra file.stat() call when possible var size = _size orelse (file.getEndPos() catch |err| { @@ -675,6 +704,26 @@ pub const FileSystem = struct { return File{ .path = Path.init(path), .contents = file_contents }; } + pub fn readFile( + fs: *RealFS, + path: string, + _size: ?usize, + ) !File { + fs.limiter.before(); + defer fs.limiter.after(); + const file: std.fs.File = std.fs.openFileAbsolute(path, std.fs.File.OpenFlags{ .read = true, .write = false }) catch |err| { + fs.readFileError(path, err); + return err; + }; + defer { + if (fs.needToCloseFiles()) { + file.close(); + } + } + + return try fs.readFileWithHandle(path, _size, file); + } + pub fn kind(fs: *RealFS, _dir: string, base: string) !Entry.Cache { var dir = _dir; var combo = [2]string{ dir, base }; @@ -684,7 +733,11 @@ pub const FileSystem = struct { defer fs.limiter.after(); const file = try std.fs.openFileAbsolute(entry_path, .{ .read = true, .write = false }); - defer file.close(); + defer { + if (fs.needToCloseFiles()) { + file.close(); + } + } var stat = try file.stat(); var _kind = stat.kind; @@ -711,6 +764,7 @@ pub const FileSystem = struct { symlink = link; const file2 = std.fs.openFileAbsolute(symlink, std.fs.File.OpenFlags{ .read = true, .write = false }) catch return cache; + // These ones we always close defer file2.close(); const stat2 = file2.stat() catch return cache; @@ -756,18 +810,6 @@ pub const FileSystem = struct { }; }; -pub const FileSystemEntry = union(FileSystemEntry.Kind) { - file: File, - directory: Directory, - not_found: FileNotFound, - - pub const Kind = enum(u8) { - file, - directory, - not_found, - }; -}; - pub const Directory = struct { path: Path, contents: []string }; pub const File = struct { path: Path, contents: string }; diff --git a/src/global.zig b/src/global.zig index 81263e658..0009e1a4c 100644 --- a/src/global.zig +++ b/src/global.zig @@ -29,6 +29,8 @@ pub const FeatureFlags = struct { pub const print_ast = false; pub const disable_printing_null = false; + + pub const store_file_descriptors = !isWindows and !isBrowser; }; pub const enableTracing = true; @@ -135,3 +137,10 @@ pub const Global = struct { Global.panic("Not implemented yet!!!!!", .{}); } }; + +pub const FileDescriptorType = if (isBrowser) u0 else std.os.fd_t; + +// When we are on a computer with an absurdly high number of max open file handles +// such is often the case with macOS +// As a useful optimization, we can store file descriptors and just keep them open...forever +pub const StoredFileDescriptorType = if (isWindows or isBrowser) u0 else std.os.fd_t; diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig index a9952c8b1..053e00a90 100644 --- a/src/resolver/package_json.zig +++ b/src/resolver/package_json.zig @@ -44,11 +44,13 @@ pub const PackageJSON = struct { // browser_map: BrowserMap, - pub fn parse(r: *resolver.Resolver, input_path: string) ?PackageJSON { + pub fn parse(r: *resolver.Resolver, input_path: string, dirname_fd: StoredFileDescriptorType) ?PackageJSON { const parts = [_]string{ input_path, "package.json" }; - const package_json_path = r.fs.join(&parts); - const entry = r.caches.fs.readFile(r.fs, input_path) catch |err| { + const package_json_path_ = r.fs.abs(&parts); + const package_json_path = r.fs.filename_store.append(package_json_path_) catch unreachable; + + const entry = r.caches.fs.readFile(r.fs, package_json_path, dirname_fd) catch |err| { if (err != error.IsDir) { r.log.addErrorFmt(null, logger.Loc.Empty, r.allocator, "Cannot read file \"{s}\": {s}", .{ r.prettyPath(fs.Path.init(input_path)), @errorName(err) }) catch unreachable; } @@ -60,7 +62,7 @@ pub const PackageJSON = struct { debug.addNoteFmt("The file \"{s}\" exists", .{package_json_path}) catch unreachable; } - const key_path = fs.Path.init(r.allocator.dupe(u8, package_json_path) catch unreachable); + const key_path = fs.Path.init(package_json_path); var json_source = logger.Source.initPathString(key_path.text, entry.contents); json_source.path.pretty = r.prettyPath(json_source.path); diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index b28a28e4b..929fdb27c 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -45,7 +45,19 @@ pub const DirInfo = struct { tsconfig_json: ?*TSConfigJSON = null, // Is there a "tsconfig.json" file in this directory or a parent directory? abs_real_path: string = "", // If non-empty, this is the real absolute path resolving any symlinks - pub fn getEntries(dirinfo: *DirInfo) ?*Fs.FileSystem.DirEntry { + pub fn getFileDescriptor(dirinfo: *const DirInfo) StoredFileDescriptorType { + if (!FeatureFlags.store_file_descriptors) { + return 0; + } + + if (dirinfo.getEntries()) |entries| { + return entries.fd; + } else { + return 0; + } + } + + pub fn getEntries(dirinfo: *const DirInfo) ?*Fs.FileSystem.DirEntry { var entries_ptr = Fs.FileSystem.instance.fs.entries.atIndex(dirinfo.entries) orelse return null; switch (entries_ptr.*) { .entries => |entr| { @@ -57,10 +69,10 @@ pub const DirInfo = struct { } } - pub fn getParent(i: *DirInfo) ?*DirInfo { + pub fn getParent(i: *const DirInfo) ?*DirInfo { return HashMap.instance.atIndex(i.parent); } - pub fn getEnclosingBrowserScope(i: *DirInfo) ?*DirInfo { + pub fn getEnclosingBrowserScope(i: *const DirInfo) ?*DirInfo { return HashMap.instance.atIndex(i.enclosing_browser_scope); } @@ -250,6 +262,9 @@ pub const Resolver = struct { debug_meta: ?DebugMeta = null, + dirname_fd: StoredFileDescriptorType = 0, + file_fd: StoredFileDescriptorType = 0, + // Most NPM modules are CommonJS // If unspecified, assume CommonJS. // If internal app code, assume ESM. Since this is designed for ESM.` @@ -428,6 +443,7 @@ pub const Resolver = struct { return Result{ .path_pair = res.path_pair, .diff_case = res.diff_case, + .dirname_fd = dir_info.getFileDescriptor(), .is_from_node_modules = res.is_node_module, }; } @@ -452,7 +468,12 @@ pub const Resolver = struct { // Run node's resolution rules (e.g. adding ".js") if (r.loadAsFileOrDirectory(import_path, kind)) |entry| { - return Result{ .path_pair = entry.path_pair, .diff_case = entry.diff_case, .is_from_node_modules = entry.is_node_module }; + return Result{ + .dirname_fd = entry.dirname_fd, + .path_pair = entry.path_pair, + .diff_case = entry.diff_case, + .is_from_node_modules = entry.is_node_module, + }; } return null; @@ -508,6 +529,7 @@ pub const Resolver = struct { .diff_case = _result.diff_case, .is_from_node_modules = _result.is_node_module, .module_type = pkg.module_type, + .dirname_fd = _result.dirname_fd, }; check_relative = false; check_package = false; @@ -524,6 +546,7 @@ pub const Resolver = struct { .path_pair = res.path_pair, .diff_case = res.diff_case, .is_from_node_modules = res.is_node_module, + .dirname_fd = res.dirname_fd, }; } else if (!check_package) { return null; @@ -569,6 +592,7 @@ pub const Resolver = struct { } return Result{ .path_pair = pair, + .dirname_fd = node_module.dirname_fd, .diff_case = node_module.diff_case, .is_from_node_modules = true, }; @@ -591,6 +615,7 @@ pub const Resolver = struct { .path_pair = res.path_pair, .diff_case = res.diff_case, .is_from_node_modules = res.is_node_module, + .dirname_fd = res.dirname_fd, }; } else { // Note: node's "self references" are not currently supported @@ -611,9 +636,11 @@ pub const Resolver = struct { path.is_disabled = true; } else if (r.resolveWithoutRemapping(dir_info, remapped, kind)) |remapped_result| { result.is_from_node_modules = remapped_result.is_node_module; + switch (iter.index) { 0 => { result.path_pair.primary = remapped_result.path_pair.primary; + result.dirname_fd = remapped_result.dirname_fd; }, else => { result.path_pair.secondary = remapped_result.path_pair.primary; @@ -728,7 +755,7 @@ pub const Resolver = struct { // // if (!strings.eql(std.fs.path.basename(current), "node_modules")) { // // var paths1 = [_]string{ current, "node_modules", extends }; // // var join1 = r.fs.absAlloc(ctx.r.allocator, &paths1) catch unreachable; - // // const res = ctx.r.parseTSConfig(join1, ctx.visited) catch |err| { + // // const res = ctx.r.parseTSConfig(join1, ctx.1) catch |err| { // // if (err == error.ENOENT) { // // continue; // // } else if (err == error.ParseErrorImportCycle) {} else if (err != error.ParseErrorAlreadyLogged) {} @@ -743,12 +770,16 @@ pub const Resolver = struct { }; threadlocal var tsconfig_base_url_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - pub fn parseTSConfig(r: *Resolver, file: string, visited: *StringBoolMap) !?*TSConfigJSON { - if (visited.contains(file)) { - return error.ParseErrorImportCycle; - } - visited.put(file, true) catch unreachable; - const entry = try r.caches.fs.readFile(r.fs, file); + pub fn parseTSConfig( + r: *Resolver, + file: string, + dirname_fd: StoredFileDescriptorType, + ) !?*TSConfigJSON { + const entry = try r.caches.fs.readFile( + r.fs, + file, + dirname_fd, + ); const key_path = Path.init(file); const source = logger.Source.initPathString(key_path.text, entry.contents); @@ -778,8 +809,8 @@ pub const Resolver = struct { return path.text; } - pub fn parsePackageJSON(r: *Resolver, file: string) !?*PackageJSON { - const pkg = PackageJSON.parse(r, file) orelse return null; + pub fn parsePackageJSON(r: *Resolver, file: string, dirname_fd: StoredFileDescriptorType) !?*PackageJSON { + const pkg = PackageJSON.parse(r, file, dirname_fd) orelse return null; var _pkg = try r.allocator.create(PackageJSON); _pkg.* = pkg; return _pkg; @@ -846,7 +877,7 @@ pub const Resolver = struct { defer { // Anything - if (open_dir_count > 0) { + if (open_dir_count > 0 and r.fs.fs.needToCloseFiles()) { var open_dirs: []std.fs.Dir = _open_dirs[0..open_dir_count]; for (open_dirs) |*open_dir| { open_dir.close(); @@ -928,7 +959,7 @@ pub const Resolver = struct { return null; }; - + Fs.FileSystem.setMaxFd(open_dir.fd); // these objects mostly just wrap the file descriptor, so it's fine to keep it. _open_dirs[open_dir_count] = open_dir; open_dir_count += 1; @@ -959,6 +990,12 @@ pub const Resolver = struct { dir_entries_option = try rfs.entries.put(&cached_dir_entry_result, .{ .entries = Fs.FileSystem.DirEntry.init(dir_path, r.fs.allocator), }); + + if (FeatureFlags.store_file_descriptors) { + Fs.FileSystem.setMaxFd(open_dir.fd); + dir_entries_option.entries.fd = open_dir.fd; + } + has_dir_entry_result = true; } @@ -974,6 +1011,7 @@ pub const Resolver = struct { cached_dir_entry_result.index, r.dir_cache.atIndex(top_parent.index), top_parent.index, + open_dir.fd, ); var dir_info_ptr = try r.dir_cache.put(&queue_top.result, dir_info); @@ -998,6 +1036,8 @@ pub const Resolver = struct { pub const MatchResult = struct { path_pair: PathPair, + dirname_fd: StoredFileDescriptorType = 0, + file_fd: StoredFileDescriptorType = 0, is_node_module: bool = false, diff_case: ?Fs.FileSystem.Entry.Lookup.DifferentCase = null, }; @@ -1137,6 +1177,7 @@ pub const Resolver = struct { pub const LoadResult = struct { path: string, diff_case: ?Fs.FileSystem.Entry.Lookup.DifferentCase, + dirname_fd: StoredFileDescriptorType = 0, }; pub fn checkBrowserMap(r: *Resolver, pkg: *PackageJSON, input_path: string) ?string { @@ -1259,7 +1300,11 @@ pub const Resolver = struct { debug.addNoteFmt("Found file: \"{s}\"", .{out_buf}) catch unreachable; } - return MatchResult{ .path_pair = .{ .primary = Path.init(out_buf) }, .diff_case = lookup.diff_case }; + return MatchResult{ + .path_pair = .{ .primary = Path.init(out_buf) }, + .diff_case = lookup.diff_case, + .dirname_fd = dir_info.getFileDescriptor(), + }; } } } @@ -1296,7 +1341,7 @@ pub const Resolver = struct { // Is this a file if (r.loadAsFile(remapped_abs, extension_order)) |file_result| { - return MatchResult{ .path_pair = .{ .primary = Path.init(file_result.path) }, .diff_case = file_result.diff_case }; + return MatchResult{ .dirname_fd = file_result.dirname_fd, .path_pair = .{ .primary = Path.init(file_result.path) }, .diff_case = file_result.diff_case }; } // Is it a directory with an index? @@ -1319,7 +1364,11 @@ pub const Resolver = struct { // Is this a file? if (r.loadAsFile(path, extension_order)) |file| { - return MatchResult{ .path_pair = .{ .primary = Path.init(file.path) }, .diff_case = file.diff_case }; + return MatchResult{ + .path_pair = .{ .primary = Path.init(file.path) }, + .diff_case = file.diff_case, + .dirname_fd = file.dirname_fd, + }; } // Is this a directory? @@ -1396,6 +1445,7 @@ pub const Resolver = struct { .secondary = _result.path_pair.primary, }, .diff_case = auto_main_result.diff_case, + .dirname_fd = auto_main_result.dirname_fd, }; } else { if (r.debug_logs) |*debug| { @@ -1453,7 +1503,7 @@ pub const Resolver = struct { return null; } - var entries = dir_entry.entries; + const entries = dir_entry.entries; const base = std.fs.path.basename(path); @@ -1470,7 +1520,11 @@ pub const Resolver = struct { const abs_path_parts = [_]string{ query.entry.dir, query.entry.base }; const abs_path = r.fs.filename_store.append(r.fs.joinBuf(&abs_path_parts, &TemporaryBuffer.ExtensionPathBuf)) catch unreachable; - return LoadResult{ .path = abs_path, .diff_case = query.diff_case }; + return LoadResult{ + .path = abs_path, + .diff_case = query.diff_case, + .dirname_fd = entries.fd, + }; } } @@ -1496,6 +1550,7 @@ pub const Resolver = struct { return LoadResult{ .path = r.fs.filename_store.append(buffer) catch unreachable, .diff_case = query.diff_case, + .dirname_fd = entries.fd, }; } } @@ -1537,6 +1592,7 @@ pub const Resolver = struct { return LoadResult{ .path = r.fs.filename_store.append(buffer) catch unreachable, .diff_case = query.diff_case, + .dirname_fd = entries.fd, }; } } @@ -1562,6 +1618,7 @@ pub const Resolver = struct { dir_entry_index: allocators.IndexType, parent: ?*DirInfo, parent_index: allocators.IndexType, + fd: FileDescriptorType, ) anyerror!DirInfo { var result = _result; @@ -1621,7 +1678,7 @@ pub const Resolver = struct { if (entries.get("package.json")) |lookup| { const entry = lookup.entry; if (entry.kind(rfs) == .file) { - info.package_json = r.parsePackageJSON(path) catch null; + info.package_json = r.parsePackageJSON(path, if (FeatureFlags.store_file_descriptors) fd else 0) catch null; if (info.package_json) |pkg| { if (pkg.browser_map.count() > 0) { @@ -1663,9 +1720,10 @@ pub const Resolver = struct { } if (tsconfig_path) |tsconfigpath| { - var visited = std.StringHashMap(bool).init(r.allocator); - defer visited.deinit(); - info.tsconfig_json = r.parseTSConfig(tsconfigpath, &visited) catch |err| brk: { + info.tsconfig_json = r.parseTSConfig( + tsconfigpath, + if (FeatureFlags.store_file_descriptors) fd else 0, + ) catch |err| brk: { const pretty = r.prettyPath(Path.init(tsconfigpath)); if (err == error.ENOENT) { diff --git a/src/test/fixtures/await.ts b/src/test/fixtures/await.ts index 802b3b3f0..f927d2c38 100644 --- a/src/test/fixtures/await.ts +++ b/src/test/fixtures/await.ts @@ -1 +1,3 @@ -const init: (VariableDeclaration | AnyExpression) = true; +function hey() { + const foo = process.env.node_eNV; +} |