Compare commits

...

3 Commits

Author SHA1 Message Date
Jarred Sumner
0d7e104bb1 Merge branch 'main' into jarred/shrink-comptimestringmap 2025-07-16 23:04:15 -07:00
Jarred Sumner
b39a3726ea Try this 2025-07-13 00:03:04 -07:00
Jarred Sumner
d4dc50e238 Shrink ComptimeStringMap slightly 2025-07-12 23:14:15 -07:00
6 changed files with 201 additions and 99 deletions

View File

@@ -1310,12 +1310,14 @@ pub const JSValue = enum(i64) {
cmd,
pub fn has(property: []const u8) bool {
return bun.ComptimeEnumMap(BuiltinName).has(property);
return map.has(property);
}
pub fn get(property: []const u8) ?BuiltinName {
return bun.ComptimeEnumMap(BuiltinName).get(property);
return map.get(property);
}
pub const map = bun.ComptimeEnumMap(BuiltinName);
};
pub fn fastGetOrElse(this: JSValue, global: *JSGlobalObject, builtin_name: BuiltinName, alternate: ?JSC.JSValue) ?JSValue {
@@ -1526,7 +1528,7 @@ pub const JSValue = enum(i64) {
// TODO: replace calls to this function with `getOptional`
pub fn getOwnTruthyComptime(this: JSValue, global: *JSGlobalObject, comptime property: []const u8) ?JSValue {
if (comptime bun.ComptimeEnumMap(BuiltinName).has(property)) {
if (comptime BuiltinName.map.has(property)) {
return fastGetOwn(this, global, @field(BuiltinName, property));
}

View File

@@ -183,8 +183,6 @@ pub const Progress = @import("./Progress.zig");
/// Modified version of Zig's ComptimeStringMap
pub const comptime_string_map = @import("./comptime_string_map.zig");
pub const ComptimeStringMap = comptime_string_map.ComptimeStringMap;
pub const ComptimeStringMap16 = comptime_string_map.ComptimeStringMap16;
pub const ComptimeStringMapWithKeyType = comptime_string_map.ComptimeStringMapWithKeyType;
pub const glob = @import("./glob.zig");
pub const patch = @import("./patch.zig");
@@ -1235,14 +1233,24 @@ pub fn enumMap(comptime T: type, comptime args: anytype) (fn (T) [:0]const u8) {
return Map.get;
}
pub fn ComptimeEnumMap(comptime T: type) type {
var entries: [std.enums.values(T).len]struct { [:0]const u8, T } = undefined;
for (std.enums.values(T), &entries) |value, *entry| {
entry.* = .{ .@"0" = @tagName(value), .@"1" = value };
}
return ComptimeStringMap(T, entries);
fn ComptimeEnumValueMapList(comptime T: type) *const [std.enums.values(T).len]struct { []const u8, T } {
const Memoized = struct {
pub const result_buffer = brk: {
var entries: [std.enums.values(T).len]struct { []const u8, T } = undefined;
for (std.enums.values(T), &entries) |value, *entry| {
entry.* = .{ .@"0" = @tagName(value), .@"1" = value };
}
break :brk entries;
};
pub const result = &result_buffer;
};
return Memoized.result;
}
pub fn ComptimeEnumMap(comptime T: type) type {
return ComptimeStringMap(T, comptime ComptimeEnumValueMapList(T));
}
/// Write 0's for every byte in Type
/// Ignores default struct values.
pub fn zero(comptime Type: type) Type {

View File

@@ -12,78 +12,160 @@ const strings = @import("./string_immutable.zig");
/// where `.@"0"` is the `[]const u8` key and `.@"1"` is the associated value of type `V`.
/// TODO: https://github.com/ziglang/zig/issues/4335
pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, comptime kvs_list: anytype) type {
const KV = struct {
key: []const KeyType,
value: V,
};
const precomputed = comptime blk: {
@setEvalBranchQuota(99999);
var sorted_kvs: [kvs_list.len]KV = undefined;
var sorted_keys: [kvs_list.len][]const u8 = undefined;
var sorted_values: if (V != void) [kvs_list.len]V else void = undefined;
for (kvs_list, 0..) |kv, i| {
sorted_keys[i] = kv.@"0";
if (V != void) {
sorted_values[i] = kv.@"1";
}
}
const lenAsc = (struct {
fn lenAsc(context: void, a: KV, b: KV) bool {
fn lenAsc(context: void, a: []const u8, b: []const u8) bool {
_ = context;
if (a.key.len != b.key.len) {
return a.key.len < b.key.len;
if (a.len != b.len) {
return a.len < b.len;
}
// https://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-processing-an-unsorted-array
@setEvalBranchQuota(999999);
return std.mem.order(KeyType, a.key, b.key) == .lt;
return std.mem.order(KeyType, a, b) == .lt;
}
}).lenAsc;
if (KeyType == u8) {
for (kvs_list, 0..) |kv, i| {
if (V != void) {
sorted_kvs[i] = .{ .key = kv.@"0", .value = kv.@"1" };
} else {
sorted_kvs[i] = .{ .key = kv.@"0", .value = {} };
const Context = struct {
keys: [][]const u8,
values: if (V != void) []V else void,
pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
return lenAsc({}, ctx.keys[a], ctx.keys[b]);
}
}
pub fn swap(ctx: @This(), a: usize, b: usize) void {
std.mem.swap([]const u8, &ctx.keys[a], &ctx.keys[b]);
if (V != void) {
std.mem.swap(V, &ctx.values[a], &ctx.values[b]);
}
}
};
std.sort.pdqContext(0, sorted_keys.len, Context{ .keys = &sorted_keys, .values = if (V != void) &sorted_values else undefined });
} else {
@compileError("Not implemented for this key type");
}
std.sort.pdq(KV, &sorted_kvs, {}, lenAsc);
const min_len = sorted_kvs[0].key.len;
const max_len = sorted_kvs[sorted_kvs.len - 1].key.len;
const min_len = sorted_keys[0].len;
const max_len = sorted_keys[sorted_keys.len - 1].len;
var len_indexes: [max_len + 1]usize = undefined;
var len: usize = 0;
var i: usize = 0;
var max_len_index: usize = 0;
while (len <= max_len) : (len += 1) {
@setEvalBranchQuota(99999);
// find the first keyword len == len
while (len > sorted_kvs[i].key.len) {
while (len > sorted_keys[i].len) {
i += 1;
}
len_indexes[len] = i;
max_len_index = @max(max_len_index, i);
}
switch (max_len_index) {
0...std.math.maxInt(u8) => {
break :blk .{
.min_len = min_len,
.max_len = max_len,
.sorted_keys = brk: {
var b: [sorted_keys.len][]const u8 = undefined;
@memcpy(b[0..sorted_keys.len], sorted_keys[0..sorted_keys.len]);
break :brk b;
},
.sorted_values = if (V != void) sorted_values,
.len_indexes = brk: {
var b: [len_indexes.len]u8 = undefined;
for (len_indexes, &b) |v, *ptr| {
ptr.* = @intCast(v);
}
break :brk b;
},
};
},
std.math.maxInt(u8) + 1...std.math.maxInt(u16) => {
break :blk .{
.min_len = min_len,
.max_len = max_len,
.sorted_keys = brk: {
var b: [sorted_keys.len][]const u8 = undefined;
@memcpy(b[0..sorted_keys.len], sorted_keys[0..sorted_keys.len]);
break :brk b;
},
.sorted_values = if (V != void) sorted_values,
.len_indexes = brk: {
var b: [len_indexes.len]u16 = undefined;
for (len_indexes, &b) |v, *ptr| {
ptr.* = @intCast(v);
}
break :brk b;
},
};
},
std.math.maxInt(u16) + 1...std.math.maxInt(u32) => {
break :blk .{
.min_len = min_len,
.max_len = max_len,
.sorted_keys = brk: {
var b: [sorted_keys.len][]const u8 = undefined;
@memcpy(b[0..sorted_keys.len], sorted_keys[0..sorted_keys.len]);
break :brk b;
},
.sorted_values = if (V != void) sorted_values,
.len_indexes = brk: {
var b: [len_indexes.len]u32 = undefined;
for (len_indexes, &b) |v, *ptr| {
ptr.* = @intCast(v);
}
break :brk b;
},
};
},
std.math.maxInt(u32) + 1...std.math.maxInt(u64) => {
break :blk .{
.min_len = min_len,
.max_len = max_len,
.sorted_keys = brk: {
var b: [sorted_keys.len][]const u8 = undefined;
@memcpy(b[0..sorted_keys.len], sorted_keys[0..sorted_keys.len]);
break :brk b;
},
.sorted_values = if (V != void) sorted_values,
.len_indexes = len_indexes,
};
},
}
break :blk .{
.min_len = min_len,
.max_len = max_len,
.sorted_kvs = sorted_kvs,
.len_indexes = len_indexes,
};
};
return struct {
const len_indexes = precomputed.len_indexes;
pub const kvs = precomputed.sorted_kvs;
const keys_list: []const []const KeyType = blk: {
var k: [kvs.len][]const KeyType = undefined;
for (kvs, 0..) |kv, i| {
k[i] = kv.key;
}
const final = k;
break :blk &final;
};
const keys_list = precomputed.sorted_keys;
const values_slice_list = if (V != void) precomputed.sorted_values else undefined;
const values_list = if (V != void) &values_slice_list else undefined;
pub const Value = V;
pub fn keys() []const []const KeyType {
return keys_list;
const keys_list_ptr = struct {
pub const keys_list_slice_to_avoid_keeping_keys_list_in_text_section_unnecessarily = &keys_list;
};
return keys_list_ptr.keys_list_slice_to_avoid_keeping_keys_list_in_text_section_unnecessarily;
}
pub fn values() []const V {
return values_list;
}
pub fn has(str: []const KeyType) bool {
@@ -95,7 +177,7 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
var i = len_indexes[len];
@setEvalBranchQuota(99999);
while (i < kvs.len and kvs[i].key.len == len) : (i += 1) {}
while (i < keys_list.len and keys_list[i].len == len) : (i += 1) {}
break :brk i;
};
@@ -103,8 +185,11 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
// This benchmarked faster for both small and large lists of strings than using a big switch statement
// But only so long as the keys are a sorted list.
inline for (len_indexes[len]..end) |i| {
if (strings.eqlComptimeCheckLenWithType(KeyType, str, kvs[i].key, false)) {
return kvs[i].value;
if (strings.eqlComptimeCheckLenWithType(KeyType, str, comptime keys_list[i], false)) {
if (comptime V == void) {
return {};
}
return values_slice_list[i];
}
}
@@ -116,7 +201,9 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
var i = len_indexes[len];
@setEvalBranchQuota(99999);
while (i < kvs.len and kvs[i].key.len == len) : (i += 1) {}
while (i < keys_list.len and
keys_list[i].len == len) : (i += 1)
{}
break :brk i;
};
@@ -124,8 +211,12 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
// This benchmarked faster for both small and large lists of strings than using a big switch statement
// But only so long as the keys are a sorted list.
inline for (len_indexes[len]..end) |i| {
if (eqls(str, kvs[i].key)) {
return kvs[i].value;
if (eqls(str, comptime keys_list[i])) {
if (comptime V == void) {
return {};
}
return values_slice_list[i];
}
}
@@ -137,7 +228,7 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
var i = len_indexes[len];
@setEvalBranchQuota(99999);
while (i < kvs.len and kvs[i].key.len == len) : (i += 1) {}
while (i < keys_list.len and keys_list[i].len == len) : (i += 1) {}
break :brk i;
};
@@ -145,7 +236,11 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
const start = comptime len_indexes[len];
const range = comptime keys()[start..end];
if (eqls(str, range)) |k| {
return kvs[start + k].value;
if (comptime V == void) {
return {};
}
return values_slice_list[start + k];
}
return null;
@@ -177,7 +272,7 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
var i = len_indexes[len];
@setEvalBranchQuota(99999);
while (i < kvs.len and kvs[i].key.len == len) : (i += 1) {}
while (i < keys_list.len and keys_list[i].len == len) : (i += 1) {}
break :brk i;
};
@@ -185,7 +280,7 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
// This benchmarked faster for both small and large lists of strings than using a big switch statement
// But only so long as the keys are a sorted list.
inline for (len_indexes[len]..end) |i| {
if (strings.eqlComptimeCheckLenWithType(KeyType, str, kvs[i].key, false)) {
if (strings.eqlComptimeCheckLenWithType(KeyType, str, comptime keys_list[i], false)) {
return i;
}
}
@@ -324,10 +419,6 @@ pub fn ComptimeStringMap(comptime V: type, comptime kvs_list: anytype) type {
return ComptimeStringMapWithKeyType(u8, V, kvs_list);
}
pub fn ComptimeStringMap16(comptime V: type, comptime kvs_list: anytype) type {
return ComptimeStringMapWithKeyType(u16, V, kvs_list);
}
const TestEnum = enum {
A,
B,

View File

@@ -157,6 +157,30 @@ pub const Browsers = struct {
safari: ?u32 = null,
samsung: ?u32 = null,
const Browser = enum {
chrome,
edge,
firefox,
ie,
ios_saf,
opera,
safari,
no_mapping,
};
// Hoisting this reduces the number of instantiations
const Map = bun.ComptimeStringMap(Browser, .{
.{ "chrome", Browser.chrome },
.{ "edge", Browser.edge },
.{ "firefox", Browser.firefox },
.{ "hermes", Browser.no_mapping },
.{ "ie", Browser.ie },
.{ "ios", Browser.ios_saf },
.{ "node", Browser.no_mapping },
.{ "opera", Browser.opera },
.{ "rhino", Browser.no_mapping },
.{ "safari", Browser.safari },
});
pub const browserDefault = convertFromString(&.{
"es2020", // support import.meta.url
"edge88",
@@ -245,28 +269,6 @@ pub const Browsers = struct {
};
if (maybe_idx) |idx| {
const Browser = enum {
chrome,
edge,
firefox,
ie,
ios_saf,
opera,
safari,
no_mapping,
};
const Map = bun.ComptimeStringMap(Browser, .{
.{ "chrome", Browser.chrome },
.{ "edge", Browser.edge },
.{ "firefox", Browser.firefox },
.{ "hermes", Browser.no_mapping },
.{ "ie", Browser.ie },
.{ "ios", Browser.ios_saf },
.{ "node", Browser.no_mapping },
.{ "opera", Browser.opera },
.{ "rhino", Browser.no_mapping },
.{ "safari", Browser.safari },
});
const browser = Map.get(entry[0..idx]);
if (browser == null or browser.? == .no_mapping) continue; // No mapping available

View File

@@ -294,9 +294,9 @@ pub fn jsonStringify(this: *const Lockfile, w: anytype) !void {
try w.beginArray();
defer w.endArray() catch {};
for (Npm.Architecture.NameMap.kvs) |kv| {
if (pkg.meta.arch.has(kv.value)) {
try w.write(kv.key);
for (Npm.Architecture.NameMap.keys(), Npm.Architecture.NameMap.values()) |key, value| {
if (pkg.meta.arch.has(value)) {
try w.write(key);
}
}
}
@@ -306,9 +306,9 @@ pub fn jsonStringify(this: *const Lockfile, w: anytype) !void {
try w.beginArray();
defer w.endArray() catch {};
for (Npm.OperatingSystem.NameMap.kvs) |kv| {
if (pkg.meta.os.has(kv.value)) {
try w.write(kv.key);
for (Npm.OperatingSystem.NameMap.keys(), Npm.OperatingSystem.NameMap.values()) |key, value| {
if (pkg.meta.os.has(value)) {
try w.write(key);
}
}
}

View File

@@ -627,15 +627,14 @@ pub fn Negatable(comptime T: type) type {
return;
}
const kvs = T.NameMap.kvs;
var removed: u8 = 0;
for (kvs) |kv| {
if (!field.has(kv.value)) {
for (T.NameMap.values()) |value| {
if (!field.has(value)) {
removed += 1;
}
}
const included = kvs.len - removed;
const print_included = removed > kvs.len - removed;
const included = T.NameMap.keys().len - removed;
const print_included = removed > T.NameMap.keys().len - removed;
const one = (print_included and included == 1) or (!print_included and removed == 1);
@@ -643,18 +642,18 @@ pub fn Negatable(comptime T: type) type {
try writer.writeAll("[ ");
}
for (kvs) |kv| {
const has = field.has(kv.value);
for (T.NameMap.keys(), T.NameMap.values()) |key, value| {
const has = field.has(value);
if (has and print_included) {
try writer.print(
\\"{s}"
, .{kv.key});
, .{key});
if (one) return;
try writer.writeAll(", ");
} else if (!has and !print_included) {
try writer.print(
\\"!{s}"
, .{kv.key});
, .{key});
if (one) return;
try writer.writeAll(", ");
}