Commit 8af59d1f98

Travis Staloch <1562827+travisstaloch@users.noreply.github.com>
2024-04-21 08:14:39
ComptimeStringMap: return a regular struct and optimize
this patch renames ComptimeStringMap to StaticStringMap, makes it accept only a single type parameter, and return a known struct type instead of an anonymous struct. initial motivation for these changes was to reduce the 'very long type names' issue described here https://github.com/ziglang/zig/pull/19682. this breaks the previous API. users will now need to write: `const map = std.StaticStringMap(T).initComptime(kvs_list);` * move `kvs_list` param from type param to an `initComptime()` param * new public methods * `keys()`, `values()` helpers * `init(allocator)`, `deinit(allocator)` for runtime data * `getLongestPrefix(str)`, `getLongestPrefixIndex(str)` - i'm not sure these belong but have left in for now incase they are deemed useful * performance notes: * i posted some benchmarking results here: https://github.com/travisstaloch/comptime-string-map-revised/issues/1 * i noticed a speedup reducing the size of the struct from 48 to 32 bytes and thus use u32s instead of usize for all length fields * i noticed speedup storing KVs as a struct of arrays * latest benchmark shows these wall_time improvements for debug/safe/small/fast builds: -6.6% / -10.2% / -19.1% / -8.9%. full output in link above.
1 parent fefdbca
lib/compiler/aro/aro/LangOpts.zig
@@ -47,7 +47,7 @@ pub const Standard = enum {
     /// Working Draft for ISO C23 with GNU extensions
     gnu23,
 
-    const NameMap = std.ComptimeStringMap(Standard, .{
+    const NameMap = std.StaticStringMap(Standard).initComptime(.{
         .{ "c89", .c89 },                .{ "c90", .c89 },          .{ "iso9899:1990", .c89 },
         .{ "iso9899:199409", .iso9899 }, .{ "gnu89", .gnu89 },      .{ "gnu90", .gnu89 },
         .{ "c99", .c99 },                .{ "iso9899:1999", .c99 }, .{ "c9x", .c99 },
lib/compiler/aro/aro/Preprocessor.zig
@@ -1709,7 +1709,7 @@ fn expandFuncMacro(
                     }
                     if (!pp.comp.langopts.standard.atLeast(.c23)) break :res not_found;
 
-                    const attrs = std.ComptimeStringMap([]const u8, .{
+                    const attrs = std.StaticStringMap([]const u8).initComptime(.{
                         .{ "deprecated", "201904L\n" },
                         .{ "fallthrough", "201904L\n" },
                         .{ "maybe_unused", "201904L\n" },
lib/compiler/aro/aro/Tokenizer.zig
@@ -872,7 +872,7 @@ pub const Token = struct {
         };
     }
 
-    const all_kws = std.ComptimeStringMap(Id, .{
+    const all_kws = std.StaticStringMap(Id).initComptime(.{
         .{ "auto", auto: {
             @setEvalBranchQuota(3000);
             break :auto .keyword_auto;
lib/compiler/resinator/errors.zig
@@ -240,7 +240,7 @@ pub const ErrorDetails = struct {
         //       see https://github.com/ziglang/zig/issues/15395
         _: u26 = 0,
 
-        pub const strings = std.ComptimeStringMap([]const u8, .{
+        pub const strings = std.StaticStringMap([]const u8).initComptime(.{
             .{ "number", "number" },
             .{ "number_expression", "number expression" },
             .{ "string_literal", "quoted string literal" },
lib/compiler/resinator/rc.zig
@@ -47,7 +47,10 @@ pub const Resource = enum {
     fontdir_num,
     manifest_num,
 
-    const map = std.ComptimeStringMapWithEql(Resource, .{
+    const map = std.StaticStringMapWithEql(
+        Resource,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "ACCELERATORS", .accelerators },
         .{ "BITMAP", .bitmap },
         .{ "CURSOR", .cursor },
@@ -67,7 +70,7 @@ pub const Resource = enum {
         .{ "TOOLBAR", .toolbar },
         .{ "VERSIONINFO", .versioninfo },
         .{ "VXD", .vxd },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 
     pub fn fromString(bytes: SourceBytes) Resource {
         const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes);
@@ -157,20 +160,26 @@ pub const OptionalStatements = enum {
     menu,
     style,
 
-    pub const map = std.ComptimeStringMapWithEql(OptionalStatements, .{
+    pub const map = std.StaticStringMapWithEql(
+        OptionalStatements,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "CHARACTERISTICS", .characteristics },
         .{ "LANGUAGE", .language },
         .{ "VERSION", .version },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 
-    pub const dialog_map = std.ComptimeStringMapWithEql(OptionalStatements, .{
+    pub const dialog_map = std.StaticStringMapWithEql(
+        OptionalStatements,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "CAPTION", .caption },
         .{ "CLASS", .class },
         .{ "EXSTYLE", .exstyle },
         .{ "FONT", .font },
         .{ "MENU", .menu },
         .{ "STYLE", .style },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 };
 
 pub const Control = enum {
@@ -197,7 +206,10 @@ pub const Control = enum {
     state3,
     userbutton,
 
-    pub const map = std.ComptimeStringMapWithEql(Control, .{
+    pub const map = std.StaticStringMapWithEql(
+        Control,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "AUTO3STATE", .auto3state },
         .{ "AUTOCHECKBOX", .autocheckbox },
         .{ "AUTORADIOBUTTON", .autoradiobutton },
@@ -220,7 +232,7 @@ pub const Control = enum {
         .{ "SCROLLBAR", .scrollbar },
         .{ "STATE3", .state3 },
         .{ "USERBUTTON", .userbutton },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 
     pub fn hasTextParam(control: Control) bool {
         switch (control) {
@@ -231,14 +243,17 @@ pub const Control = enum {
 };
 
 pub const ControlClass = struct {
-    pub const map = std.ComptimeStringMapWithEql(res.ControlClass, .{
+    pub const map = std.StaticStringMapWithEql(
+        res.ControlClass,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "BUTTON", .button },
         .{ "EDIT", .edit },
         .{ "STATIC", .static },
         .{ "LISTBOX", .listbox },
         .{ "SCROLLBAR", .scrollbar },
         .{ "COMBOBOX", .combobox },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 
     /// Like `map.get` but works on WTF16 strings, for use with parsed
     /// string literals ("BUTTON", or even "\x42UTTON")
@@ -280,10 +295,13 @@ pub const MenuItem = enum {
     menuitem,
     popup,
 
-    pub const map = std.ComptimeStringMapWithEql(MenuItem, .{
+    pub const map = std.StaticStringMapWithEql(
+        MenuItem,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "MENUITEM", .menuitem },
         .{ "POPUP", .popup },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 
     pub fn isSeparator(bytes: []const u8) bool {
         return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR");
@@ -297,14 +315,17 @@ pub const MenuItem = enum {
         menubarbreak,
         menubreak,
 
-        pub const map = std.ComptimeStringMapWithEql(Option, .{
+        pub const map = std.StaticStringMapWithEql(
+            Option,
+            std.static_string_map.eqlAsciiIgnoreCase,
+        ).initComptime(.{
             .{ "CHECKED", .checked },
             .{ "GRAYED", .grayed },
             .{ "HELP", .help },
             .{ "INACTIVE", .inactive },
             .{ "MENUBARBREAK", .menubarbreak },
             .{ "MENUBREAK", .menubreak },
-        }, std.comptime_string_map.eqlAsciiIgnoreCase);
+        });
     };
 };
 
@@ -312,10 +333,13 @@ pub const ToolbarButton = enum {
     button,
     separator,
 
-    pub const map = std.ComptimeStringMapWithEql(ToolbarButton, .{
+    pub const map = std.StaticStringMapWithEql(
+        ToolbarButton,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "BUTTON", .button },
         .{ "SEPARATOR", .separator },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 };
 
 pub const VersionInfo = enum {
@@ -327,7 +351,10 @@ pub const VersionInfo = enum {
     file_type,
     file_subtype,
 
-    pub const map = std.ComptimeStringMapWithEql(VersionInfo, .{
+    pub const map = std.StaticStringMapWithEql(
+        VersionInfo,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "FILEVERSION", .file_version },
         .{ "PRODUCTVERSION", .product_version },
         .{ "FILEFLAGSMASK", .file_flags_mask },
@@ -335,17 +362,20 @@ pub const VersionInfo = enum {
         .{ "FILEOS", .file_os },
         .{ "FILETYPE", .file_type },
         .{ "FILESUBTYPE", .file_subtype },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 };
 
 pub const VersionBlock = enum {
     block,
     value,
 
-    pub const map = std.ComptimeStringMapWithEql(VersionBlock, .{
+    pub const map = std.StaticStringMapWithEql(
+        VersionBlock,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "BLOCK", .block },
         .{ "VALUE", .value },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 };
 
 /// Keywords that are be the first token in a statement and (if so) dictate how the rest
@@ -356,12 +386,15 @@ pub const TopLevelKeywords = enum {
     characteristics,
     stringtable,
 
-    pub const map = std.ComptimeStringMapWithEql(TopLevelKeywords, .{
+    pub const map = std.StaticStringMapWithEql(
+        TopLevelKeywords,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "LANGUAGE", .language },
         .{ "VERSION", .version },
         .{ "CHARACTERISTICS", .characteristics },
         .{ "STRINGTABLE", .stringtable },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 };
 
 pub const CommonResourceAttributes = enum {
@@ -375,7 +408,10 @@ pub const CommonResourceAttributes = enum {
     shared,
     nonshared,
 
-    pub const map = std.ComptimeStringMapWithEql(CommonResourceAttributes, .{
+    pub const map = std.StaticStringMapWithEql(
+        CommonResourceAttributes,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "PRELOAD", .preload },
         .{ "LOADONCALL", .loadoncall },
         .{ "FIXED", .fixed },
@@ -385,7 +421,7 @@ pub const CommonResourceAttributes = enum {
         .{ "IMPURE", .impure },
         .{ "SHARED", .shared },
         .{ "NONSHARED", .nonshared },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 };
 
 pub const AcceleratorTypeAndOptions = enum {
@@ -396,12 +432,15 @@ pub const AcceleratorTypeAndOptions = enum {
     shift,
     control,
 
-    pub const map = std.ComptimeStringMapWithEql(AcceleratorTypeAndOptions, .{
+    pub const map = std.StaticStringMapWithEql(
+        AcceleratorTypeAndOptions,
+        std.static_string_map.eqlAsciiIgnoreCase,
+    ).initComptime(.{
         .{ "VIRTKEY", .virtkey },
         .{ "ASCII", .ascii },
         .{ "NOINVERT", .noinvert },
         .{ "ALT", .alt },
         .{ "SHIFT", .shift },
         .{ "CONTROL", .control },
-    }, std.comptime_string_map.eqlAsciiIgnoreCase);
+    });
 };
lib/std/crypto/Certificate.zig
@@ -19,7 +19,7 @@ pub const Algorithm = enum {
     md5WithRSAEncryption,
     curveEd25519,
 
-    pub const map = std.ComptimeStringMap(Algorithm, .{
+    pub const map = std.StaticStringMap(Algorithm).initComptime(.{
         .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
         .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
         .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
@@ -52,7 +52,7 @@ pub const AlgorithmCategory = enum {
     X9_62_id_ecPublicKey,
     curveEd25519,
 
-    pub const map = std.ComptimeStringMap(AlgorithmCategory, .{
+    pub const map = std.StaticStringMap(AlgorithmCategory).initComptime(.{
         .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
         .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
         .{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
@@ -73,7 +73,7 @@ pub const Attribute = enum {
     pkcs9_emailAddress,
     domainComponent,
 
-    pub const map = std.ComptimeStringMap(Attribute, .{
+    pub const map = std.StaticStringMap(Attribute).initComptime(.{
         .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
         .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
         .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
@@ -94,7 +94,7 @@ pub const NamedCurve = enum {
     secp521r1,
     X9_62_prime256v1,
 
-    pub const map = std.ComptimeStringMap(NamedCurve, .{
+    pub const map = std.StaticStringMap(NamedCurve).initComptime(.{
         .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
         .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 },
         .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
@@ -130,7 +130,7 @@ pub const ExtensionId = enum {
     netscape_cert_type,
     netscape_comment,
 
-    pub const map = std.ComptimeStringMap(ExtensionId, .{
+    pub const map = std.StaticStringMap(ExtensionId).initComptime(.{
         .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
         .{ &[_]u8{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },
         .{ &[_]u8{ 0x55, 0x1D, 0x07 }, .subject_alt_name },
lib/std/fs/test.zig
@@ -1641,7 +1641,7 @@ test "walker" {
 
     // iteration order of walker is undefined, so need lookup maps to check against
 
-    const expected_paths = std.ComptimeStringMap(void, .{
+    const expected_paths = std.StaticStringMap(void).initComptime(.{
         .{"dir1"},
         .{"dir2"},
         .{"dir3"},
@@ -1651,7 +1651,7 @@ test "walker" {
         .{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"},
     });
 
-    const expected_basenames = std.ComptimeStringMap(void, .{
+    const expected_basenames = std.StaticStringMap(void).initComptime(.{
         .{"dir1"},
         .{"dir2"},
         .{"dir3"},
@@ -1661,8 +1661,8 @@ test "walker" {
         .{"subsub1"},
     });
 
-    for (expected_paths.kvs) |kv| {
-        try tmp.dir.makePath(kv.key);
+    for (expected_paths.keys()) |key| {
+        try tmp.dir.makePath(key);
     }
 
     var walker = try tmp.dir.walk(testing.allocator);
lib/std/http/Client.zig
@@ -1570,7 +1570,7 @@ pub const RequestOptions = struct {
 };
 
 fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
-    const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{
+    const protocol_map = std.StaticStringMap(Connection.Protocol).initComptime(.{
         .{ "http", .plain },
         .{ "ws", .plain },
         .{ "https", .tls },
lib/std/zig/AstGen.zig
@@ -10125,7 +10125,7 @@ fn calleeExpr(
     }
 }
 
-const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
+const primitive_instrs = std.StaticStringMap(Zir.Inst.Ref).initComptime(.{
     .{ "anyerror", .anyerror_type },
     .{ "anyframe", .anyframe_type },
     .{ "anyopaque", .anyopaque_type },
@@ -10173,14 +10173,14 @@ const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
 comptime {
     // These checks ensure that std.zig.primitives stays in sync with the primitive->Zir map.
     const primitives = std.zig.primitives;
-    for (primitive_instrs.kvs) |kv| {
-        if (!primitives.isPrimitive(kv.key)) {
-            @compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(kv.value) ++ "'");
+    for (primitive_instrs.keys(), primitive_instrs.values()) |key, value| {
+        if (!primitives.isPrimitive(key)) {
+            @compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(value) ++ "'");
         }
     }
-    for (primitives.names.kvs) |kv| {
-        if (primitive_instrs.get(kv.key) == null) {
-            @compileError("std.zig.primitives entry '" ++ kv.key ++ "' does not have a corresponding Zir instr");
+    for (primitives.names.keys()) |key| {
+        if (primitive_instrs.get(key) == null) {
+            @compileError("std.zig.primitives entry '" ++ key ++ "' does not have a corresponding Zir instr");
         }
     }
 }
lib/std/zig/BuiltinFn.zig
@@ -160,7 +160,7 @@ param_count: ?u8,
 
 pub const list = list: {
     @setEvalBranchQuota(3000);
-    break :list std.ComptimeStringMap(@This(), .{
+    break :list std.StaticStringMap(@This()).initComptime(.{
         .{
             "@addWithOverflow",
             .{
lib/std/zig/primitives.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 /// Set of primitive type and value names.
 /// Does not include `_` or integer type names.
-pub const names = std.ComptimeStringMap(void, .{
+pub const names = std.StaticStringMap(void).initComptime(.{
     .{"anyerror"},
     .{"anyframe"},
     .{"anyopaque"},
lib/std/zig/render.zig
@@ -2886,11 +2886,11 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote
     // If we read the whole thing, we have to do further checks.
     const longest_keyword_or_primitive_len = comptime blk: {
         var longest = 0;
-        for (primitives.names.kvs) |kv| {
-            if (kv.key.len > longest) longest = kv.key.len;
+        for (primitives.names.keys()) |key| {
+            if (key.len > longest) longest = key.len;
         }
-        for (std.zig.Token.keywords.kvs) |kv| {
-            if (kv.key.len > longest) longest = kv.key.len;
+        for (std.zig.Token.keywords.keys()) |key| {
+            if (key.len > longest) longest = key.len;
         }
         break :blk longest;
     };
lib/std/zig/tokenizer.zig
@@ -9,7 +9,7 @@ pub const Token = struct {
         end: usize,
     };
 
-    pub const keywords = std.ComptimeStringMap(Tag, .{
+    pub const keywords = std.StaticStringMap(Tag).initComptime(.{
         .{ "addrspace", .keyword_addrspace },
         .{ "align", .keyword_align },
         .{ "allowzero", .keyword_allowzero },
lib/std/comptime_string_map.zig
@@ -1,320 +0,0 @@
-const std = @import("std.zig");
-const mem = std.mem;
-
-/// Comptime string map optimized for small sets of disparate string keys.
-/// Works by separating the keys by length at comptime and only checking strings of
-/// equal length at runtime.
-///
-/// `kvs_list` expects a list of `struct { []const u8, V }` (key-value pair) tuples.
-/// You can pass `struct { []const u8 }` (only keys) tuples if `V` is `void`.
-pub fn ComptimeStringMap(
-    comptime V: type,
-    comptime kvs_list: anytype,
-) type {
-    return ComptimeStringMapWithEql(V, kvs_list, defaultEql);
-}
-
-/// Like `std.mem.eql`, but takes advantage of the fact that the lengths
-/// of `a` and `b` are known to be equal.
-pub fn defaultEql(a: []const u8, b: []const u8) bool {
-    if (a.ptr == b.ptr) return true;
-    for (a, b) |a_elem, b_elem| {
-        if (a_elem != b_elem) return false;
-    }
-    return true;
-}
-
-/// Like `std.ascii.eqlIgnoreCase` but takes advantage of the fact that
-/// the lengths of `a` and `b` are known to be equal.
-pub fn eqlAsciiIgnoreCase(a: []const u8, b: []const u8) bool {
-    if (a.ptr == b.ptr) return true;
-    for (a, b) |a_c, b_c| {
-        if (std.ascii.toLower(a_c) != std.ascii.toLower(b_c)) return false;
-    }
-    return true;
-}
-
-/// ComptimeStringMap, but accepts an equality function (`eql`).
-/// The `eql` function is only called to determine the equality
-/// of equal length strings. Any strings that are not equal length
-/// are never compared using the `eql` function.
-pub fn ComptimeStringMapWithEql(
-    comptime V: type,
-    comptime kvs_list: anytype,
-    comptime eql: fn (a: []const u8, b: []const u8) bool,
-) type {
-    const empty_list = kvs_list.len == 0;
-    const precomputed = blk: {
-        @setEvalBranchQuota(1500);
-        const KV = struct {
-            key: []const u8,
-            value: V,
-        };
-        if (empty_list)
-            break :blk .{};
-        var sorted_kvs: [kvs_list.len]KV = undefined;
-        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 SortContext = struct {
-            kvs: []KV,
-
-            pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
-                return ctx.kvs[a].key.len < ctx.kvs[b].key.len;
-            }
-
-            pub fn swap(ctx: @This(), a: usize, b: usize) void {
-                return std.mem.swap(KV, &ctx.kvs[a], &ctx.kvs[b]);
-            }
-        };
-        mem.sortUnstableContext(0, sorted_kvs.len, SortContext{ .kvs = &sorted_kvs });
-
-        const min_len = sorted_kvs[0].key.len;
-        const max_len = sorted_kvs[sorted_kvs.len - 1].key.len;
-        var len_indexes: [max_len + 1]usize = undefined;
-        var len: usize = 0;
-        var i: usize = 0;
-        while (len <= max_len) : (len += 1) {
-            // find the first keyword len == len
-            while (len > sorted_kvs[i].key.len) {
-                i += 1;
-            }
-            len_indexes[len] = i;
-        }
-        break :blk .{
-            .min_len = min_len,
-            .max_len = max_len,
-            .sorted_kvs = sorted_kvs,
-            .len_indexes = len_indexes,
-        };
-    };
-
-    return struct {
-        /// Array of `struct { key: []const u8, value: V }` where `value` is `void{}` if `V` is `void`.
-        /// Sorted by `key` length.
-        pub const kvs = precomputed.sorted_kvs;
-
-        /// Checks if the map has a value for the key.
-        pub fn has(str: []const u8) bool {
-            return get(str) != null;
-        }
-
-        /// Returns the value for the key if any, else null.
-        pub fn get(str: []const u8) ?V {
-            if (empty_list)
-                return null;
-
-            return precomputed.sorted_kvs[getIndex(str) orelse return null].value;
-        }
-
-        pub fn getIndex(str: []const u8) ?usize {
-            if (empty_list)
-                return null;
-
-            if (str.len < precomputed.min_len or str.len > precomputed.max_len)
-                return null;
-
-            var i = precomputed.len_indexes[str.len];
-            while (true) {
-                const kv = precomputed.sorted_kvs[i];
-                if (kv.key.len != str.len)
-                    return null;
-                if (eql(kv.key, str))
-                    return i;
-                i += 1;
-                if (i >= precomputed.sorted_kvs.len)
-                    return null;
-            }
-        }
-    };
-}
-
-const TestEnum = enum {
-    A,
-    B,
-    C,
-    D,
-    E,
-};
-
-test "list literal of list literals" {
-    const map = ComptimeStringMap(TestEnum, .{
-        .{ "these", .D },
-        .{ "have", .A },
-        .{ "nothing", .B },
-        .{ "incommon", .C },
-        .{ "samelen", .E },
-    });
-
-    try testMap(map);
-
-    // Default comparison is case sensitive
-    try std.testing.expect(null == map.get("NOTHING"));
-}
-
-test "array of structs" {
-    const KV = struct { []const u8, TestEnum };
-    const map = ComptimeStringMap(TestEnum, [_]KV{
-        .{ "these", .D },
-        .{ "have", .A },
-        .{ "nothing", .B },
-        .{ "incommon", .C },
-        .{ "samelen", .E },
-    });
-
-    try testMap(map);
-}
-
-test "slice of structs" {
-    const KV = struct { []const u8, TestEnum };
-    const slice: []const KV = &[_]KV{
-        .{ "these", .D },
-        .{ "have", .A },
-        .{ "nothing", .B },
-        .{ "incommon", .C },
-        .{ "samelen", .E },
-    };
-    const map = ComptimeStringMap(TestEnum, slice);
-
-    try testMap(map);
-}
-
-fn testMap(comptime map: anytype) !void {
-    try std.testing.expectEqual(TestEnum.A, map.get("have").?);
-    try std.testing.expectEqual(TestEnum.B, map.get("nothing").?);
-    try std.testing.expect(null == map.get("missing"));
-    try std.testing.expectEqual(TestEnum.D, map.get("these").?);
-    try std.testing.expectEqual(TestEnum.E, map.get("samelen").?);
-
-    try std.testing.expect(!map.has("missing"));
-    try std.testing.expect(map.has("these"));
-
-    try std.testing.expect(null == map.get(""));
-    try std.testing.expect(null == map.get("averylongstringthathasnomatches"));
-}
-
-test "void value type, slice of structs" {
-    const KV = struct { []const u8 };
-    const slice: []const KV = &[_]KV{
-        .{"these"},
-        .{"have"},
-        .{"nothing"},
-        .{"incommon"},
-        .{"samelen"},
-    };
-    const map = ComptimeStringMap(void, slice);
-
-    try testSet(map);
-
-    // Default comparison is case sensitive
-    try std.testing.expect(null == map.get("NOTHING"));
-}
-
-test "void value type, list literal of list literals" {
-    const map = ComptimeStringMap(void, .{
-        .{"these"},
-        .{"have"},
-        .{"nothing"},
-        .{"incommon"},
-        .{"samelen"},
-    });
-
-    try testSet(map);
-}
-
-fn testSet(comptime map: anytype) !void {
-    try std.testing.expectEqual({}, map.get("have").?);
-    try std.testing.expectEqual({}, map.get("nothing").?);
-    try std.testing.expect(null == map.get("missing"));
-    try std.testing.expectEqual({}, map.get("these").?);
-    try std.testing.expectEqual({}, map.get("samelen").?);
-
-    try std.testing.expect(!map.has("missing"));
-    try std.testing.expect(map.has("these"));
-
-    try std.testing.expect(null == map.get(""));
-    try std.testing.expect(null == map.get("averylongstringthathasnomatches"));
-}
-
-test "ComptimeStringMapWithEql" {
-    const map = ComptimeStringMapWithEql(TestEnum, .{
-        .{ "these", .D },
-        .{ "have", .A },
-        .{ "nothing", .B },
-        .{ "incommon", .C },
-        .{ "samelen", .E },
-    }, eqlAsciiIgnoreCase);
-
-    try testMap(map);
-    try std.testing.expectEqual(TestEnum.A, map.get("HAVE").?);
-    try std.testing.expectEqual(TestEnum.E, map.get("SameLen").?);
-    try std.testing.expect(null == map.get("SameLength"));
-
-    try std.testing.expect(map.has("ThESe"));
-}
-
-test "empty" {
-    const m1 = ComptimeStringMap(usize, .{});
-    try std.testing.expect(null == m1.get("anything"));
-
-    const m2 = ComptimeStringMapWithEql(usize, .{}, eqlAsciiIgnoreCase);
-    try std.testing.expect(null == m2.get("anything"));
-}
-
-test "redundant entries" {
-    const map = ComptimeStringMap(TestEnum, .{
-        .{ "redundant", .D },
-        .{ "theNeedle", .A },
-        .{ "redundant", .B },
-        .{ "re" ++ "dundant", .C },
-        .{ "redun" ++ "dant", .E },
-    });
-
-    // No promises about which one you get:
-    try std.testing.expect(null != map.get("redundant"));
-
-    // Default map is not case sensitive:
-    try std.testing.expect(null == map.get("REDUNDANT"));
-
-    try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
-}
-
-test "redundant insensitive" {
-    const map = ComptimeStringMapWithEql(TestEnum, .{
-        .{ "redundant", .D },
-        .{ "theNeedle", .A },
-        .{ "redundanT", .B },
-        .{ "RE" ++ "dundant", .C },
-        .{ "redun" ++ "DANT", .E },
-    }, eqlAsciiIgnoreCase);
-
-    // No promises about which result you'll get ...
-    try std.testing.expect(null != map.get("REDUNDANT"));
-    try std.testing.expect(null != map.get("ReDuNdAnT"));
-
-    try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
-}
-
-test "comptime-only value" {
-    const map = std.ComptimeStringMap(type, .{
-        .{ "a", struct {
-            pub const foo = 1;
-        } },
-        .{ "b", struct {
-            pub const foo = 2;
-        } },
-        .{ "c", struct {
-            pub const foo = 3;
-        } },
-    });
-
-    try std.testing.expect(map.get("a").?.foo == 1);
-    try std.testing.expect(map.get("b").?.foo == 2);
-    try std.testing.expect(map.get("c").?.foo == 3);
-    try std.testing.expect(map.get("d") == null);
-}
lib/std/meta.zig
@@ -19,7 +19,7 @@ pub const isTag = @compileError("deprecated; use 'tagged_value == @field(E, tag_
 
 /// Returns the variant of an enum type, `T`, which is named `str`, or `null` if no such variant exists.
 pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
-    // Using ComptimeStringMap here is more performant, but it will start to take too
+    // Using StaticStringMap here is more performant, but it will start to take too
     // long to compile if the enum is large enough, due to the current limits of comptime
     // performance when doing things like constructing lookup maps at comptime.
     // TODO The '100' here is arbitrary and should be increased when possible:
@@ -34,7 +34,7 @@ pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
             }
             break :build_kvs kvs_array[0..];
         };
-        const map = std.ComptimeStringMap(T, kvs);
+        const map = std.StaticStringMap(T).initComptime(kvs);
         return map.get(str);
     } else {
         inline for (@typeInfo(T).Enum.fields) |enumField| {
lib/std/static_string_map.zig
@@ -0,0 +1,502 @@
+const std = @import("std.zig");
+const mem = std.mem;
+
+/// Static string map optimized for small sets of disparate string keys.
+/// Works by separating the keys by length at initialization and only checking
+/// strings of equal length at runtime.
+pub fn StaticStringMap(comptime V: type) type {
+    return StaticStringMapWithEql(V, defaultEql);
+}
+
+/// Like `std.mem.eql`, but takes advantage of the fact that the lengths
+/// of `a` and `b` are known to be equal.
+pub fn defaultEql(a: []const u8, b: []const u8) bool {
+    if (a.ptr == b.ptr) return true;
+    for (a, b) |a_elem, b_elem| {
+        if (a_elem != b_elem) return false;
+    }
+    return true;
+}
+
+/// Like `std.ascii.eqlIgnoreCase` but takes advantage of the fact that
+/// the lengths of `a` and `b` are known to be equal.
+pub fn eqlAsciiIgnoreCase(a: []const u8, b: []const u8) bool {
+    if (a.ptr == b.ptr) return true;
+    for (a, b) |a_c, b_c| {
+        if (std.ascii.toLower(a_c) != std.ascii.toLower(b_c)) return false;
+    }
+    return true;
+}
+
+/// StaticStringMap, but accepts an equality function (`eql`).
+/// The `eql` function is only called to determine the equality
+/// of equal length strings. Any strings that are not equal length
+/// are never compared using the `eql` function.
+pub fn StaticStringMapWithEql(
+    comptime V: type,
+    comptime eql: fn (a: []const u8, b: []const u8) bool,
+) type {
+    return struct {
+        kvs: *const KVs = &empty_kvs,
+        len_indexes: [*]const u32 = &empty_len_indexes,
+        len_indexes_len: u32 = 0,
+        min_len: u32 = std.math.maxInt(u32),
+        max_len: u32 = 0,
+
+        pub const KV = struct {
+            key: []const u8,
+            value: V,
+        };
+
+        const Self = @This();
+        const KVs = struct {
+            keys: [*]const []const u8,
+            values: [*]const V,
+            len: u32,
+        };
+        const empty_kvs = KVs{
+            .keys = &empty_keys,
+            .values = &empty_vals,
+            .len = 0,
+        };
+        const empty_len_indexes = [0]u32{};
+        const empty_keys = [0][]const u8{};
+        const empty_vals = [0]V{};
+
+        /// Returns a map backed by static, comptime allocated memory.
+        ///
+        /// `kvs_list` must be either a list of `struct { []const u8, V }`
+        /// (key-value pair) tuples, or a list of `struct { []const u8 }`
+        /// (only keys) tuples if `V` is `void`.
+        pub inline fn initComptime(comptime kvs_list: anytype) Self {
+            comptime {
+                @setEvalBranchQuota(30 * kvs_list.len);
+                var self = Self{};
+                if (kvs_list.len == 0)
+                    return self;
+
+                var sorted_keys: [kvs_list.len][]const u8 = undefined;
+                var sorted_vals: [kvs_list.len]V = undefined;
+
+                self.initSortedKVs(kvs_list, &sorted_keys, &sorted_vals);
+                const final_keys = sorted_keys;
+                const final_vals = sorted_vals;
+                self.kvs = &.{
+                    .keys = &final_keys,
+                    .values = &final_vals,
+                    .len = @intCast(kvs_list.len),
+                };
+
+                var len_indexes: [self.max_len + 1]u32 = undefined;
+                self.initLenIndexes(&len_indexes);
+                const final_len_indexes = len_indexes;
+                self.len_indexes = &final_len_indexes;
+                self.len_indexes_len = @intCast(len_indexes.len);
+                return self;
+            }
+        }
+
+        /// Returns a map backed by memory allocated with `allocator`.
+        ///
+        /// Handles `kvs_list` the same way as `initComptime()`.
+        pub fn init(kvs_list: anytype, allocator: mem.Allocator) !Self {
+            var self = Self{};
+            if (kvs_list.len == 0)
+                return self;
+
+            const sorted_keys = try allocator.alloc([]const u8, kvs_list.len);
+            errdefer allocator.free(sorted_keys);
+            const sorted_vals = try allocator.alloc(V, kvs_list.len);
+            errdefer allocator.free(sorted_vals);
+            const kvs = try allocator.create(KVs);
+            errdefer allocator.destroy(kvs);
+
+            self.initSortedKVs(kvs_list, sorted_keys, sorted_vals);
+            kvs.* = .{
+                .keys = sorted_keys.ptr,
+                .values = sorted_vals.ptr,
+                .len = kvs_list.len,
+            };
+            self.kvs = kvs;
+
+            const len_indexes = try allocator.alloc(u32, self.max_len + 1);
+            self.initLenIndexes(len_indexes);
+            self.len_indexes = len_indexes.ptr;
+            self.len_indexes_len = @intCast(len_indexes.len);
+            return self;
+        }
+
+        /// this method should only be used with init() and not with initComptime().
+        pub fn deinit(self: Self, allocator: mem.Allocator) void {
+            allocator.free(self.len_indexes[0..self.len_indexes_len]);
+            allocator.free(self.kvs.keys[0..self.kvs.len]);
+            allocator.free(self.kvs.values[0..self.kvs.len]);
+            allocator.destroy(self.kvs);
+        }
+
+        const SortContext = struct {
+            keys: [][]const u8,
+            vals: []V,
+
+            pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
+                return ctx.keys[a].len < ctx.keys[b].len;
+            }
+
+            pub fn swap(ctx: @This(), a: usize, b: usize) void {
+                std.mem.swap([]const u8, &ctx.keys[a], &ctx.keys[b]);
+                std.mem.swap(V, &ctx.vals[a], &ctx.vals[b]);
+            }
+        };
+
+        fn initSortedKVs(
+            self: *Self,
+            kvs_list: anytype,
+            sorted_keys: [][]const u8,
+            sorted_vals: []V,
+        ) void {
+            for (kvs_list, 0..) |kv, i| {
+                sorted_keys[i] = kv.@"0";
+                sorted_vals[i] = if (V == void) {} else kv.@"1";
+                self.min_len = @intCast(@min(self.min_len, kv.@"0".len));
+                self.max_len = @intCast(@max(self.max_len, kv.@"0".len));
+            }
+            mem.sortUnstableContext(0, sorted_keys.len, SortContext{
+                .keys = sorted_keys,
+                .vals = sorted_vals,
+            });
+        }
+
+        fn initLenIndexes(self: Self, len_indexes: []u32) void {
+            var len: usize = 0;
+            var i: u32 = 0;
+            while (len <= self.max_len) : (len += 1) {
+                // find the first keyword len == len
+                while (len > self.kvs.keys[i].len) {
+                    i += 1;
+                }
+                len_indexes[len] = i;
+            }
+        }
+
+        /// Checks if the map has a value for the key.
+        pub fn has(self: Self, str: []const u8) bool {
+            return self.get(str) != null;
+        }
+
+        /// Returns the value for the key if any, else null.
+        pub fn get(self: Self, str: []const u8) ?V {
+            if (self.kvs.len == 0)
+                return null;
+
+            return self.kvs.values[self.getIndex(str) orelse return null];
+        }
+
+        pub fn getIndex(self: Self, str: []const u8) ?usize {
+            const kvs = self.kvs.*;
+            if (kvs.len == 0)
+                return null;
+
+            if (str.len < self.min_len or str.len > self.max_len)
+                return null;
+
+            var i = self.len_indexes[str.len];
+            while (true) {
+                const key = kvs.keys[i];
+                if (key.len != str.len)
+                    return null;
+                if (eql(key, str))
+                    return i;
+                i += 1;
+                if (i >= kvs.len)
+                    return null;
+            }
+        }
+
+        /// Returns the longest key, value pair where key is a prefix of `str`
+        /// else null.
+        pub fn getLongestPrefix(self: Self, str: []const u8) ?KV {
+            if (self.kvs.len == 0)
+                return null;
+            const i = self.getLongestPrefixIndex(str) orelse return null;
+            const kvs = self.kvs.*;
+            return .{
+                .key = kvs.keys[i],
+                .value = kvs.values[i],
+            };
+        }
+
+        pub fn getLongestPrefixIndex(self: Self, str: []const u8) ?usize {
+            if (self.kvs.len == 0)
+                return null;
+
+            if (str.len < self.min_len)
+                return null;
+
+            var len = @min(self.max_len, str.len);
+            while (len >= self.min_len) : (len -= 1) {
+                if (self.getIndex(str[0..len])) |i|
+                    return i;
+            }
+            return null;
+        }
+
+        pub fn keys(self: Self) []const []const u8 {
+            const kvs = self.kvs.*;
+            return kvs.keys[0..kvs.len];
+        }
+
+        pub fn values(self: Self) []const V {
+            const kvs = self.kvs.*;
+            return kvs.values[0..kvs.len];
+        }
+    };
+}
+
+const TestEnum = enum { A, B, C, D, E };
+const TestMap = StaticStringMap(TestEnum);
+const TestKV = struct { []const u8, TestEnum };
+const TestMapVoid = StaticStringMap(void);
+const TestKVVoid = struct { []const u8 };
+const TestMapWithEql = StaticStringMapWithEql(TestEnum, eqlAsciiIgnoreCase);
+const testing = std.testing;
+const test_alloc = testing.allocator;
+
+test "list literal of list literals" {
+    const slice = [_]TestKV{
+        .{ "these", .D },
+        .{ "have", .A },
+        .{ "nothing", .B },
+        .{ "incommon", .C },
+        .{ "samelen", .E },
+    };
+    const map = TestMap.initComptime(slice);
+    try testMap(map);
+    // Default comparison is case sensitive
+    try testing.expect(null == map.get("NOTHING"));
+
+    // runtime init(), deinit()
+    const map_rt = try TestMap.init(slice, test_alloc);
+    defer map_rt.deinit(test_alloc);
+    try testMap(map_rt);
+    // Default comparison is case sensitive
+    try testing.expect(null == map_rt.get("NOTHING"));
+}
+
+test "array of structs" {
+    const slice = [_]TestKV{
+        .{ "these", .D },
+        .{ "have", .A },
+        .{ "nothing", .B },
+        .{ "incommon", .C },
+        .{ "samelen", .E },
+    };
+
+    try testMap(TestMap.initComptime(slice));
+}
+
+test "slice of structs" {
+    const slice = [_]TestKV{
+        .{ "these", .D },
+        .{ "have", .A },
+        .{ "nothing", .B },
+        .{ "incommon", .C },
+        .{ "samelen", .E },
+    };
+
+    try testMap(TestMap.initComptime(slice));
+}
+
+fn testMap(map: anytype) !void {
+    try testing.expectEqual(TestEnum.A, map.get("have").?);
+    try testing.expectEqual(TestEnum.B, map.get("nothing").?);
+    try testing.expect(null == map.get("missing"));
+    try testing.expectEqual(TestEnum.D, map.get("these").?);
+    try testing.expectEqual(TestEnum.E, map.get("samelen").?);
+
+    try testing.expect(!map.has("missing"));
+    try testing.expect(map.has("these"));
+
+    try testing.expect(null == map.get(""));
+    try testing.expect(null == map.get("averylongstringthathasnomatches"));
+}
+
+test "void value type, slice of structs" {
+    const slice = [_]TestKVVoid{
+        .{"these"},
+        .{"have"},
+        .{"nothing"},
+        .{"incommon"},
+        .{"samelen"},
+    };
+    const map = TestMapVoid.initComptime(slice);
+    try testSet(map);
+    // Default comparison is case sensitive
+    try testing.expect(null == map.get("NOTHING"));
+}
+
+test "void value type, list literal of list literals" {
+    const slice = [_]TestKVVoid{
+        .{"these"},
+        .{"have"},
+        .{"nothing"},
+        .{"incommon"},
+        .{"samelen"},
+    };
+
+    try testSet(TestMapVoid.initComptime(slice));
+}
+
+fn testSet(map: TestMapVoid) !void {
+    try testing.expectEqual({}, map.get("have").?);
+    try testing.expectEqual({}, map.get("nothing").?);
+    try testing.expect(null == map.get("missing"));
+    try testing.expectEqual({}, map.get("these").?);
+    try testing.expectEqual({}, map.get("samelen").?);
+
+    try testing.expect(!map.has("missing"));
+    try testing.expect(map.has("these"));
+
+    try testing.expect(null == map.get(""));
+    try testing.expect(null == map.get("averylongstringthathasnomatches"));
+}
+
+fn testStaticStringMapWithEql(map: TestMapWithEql) !void {
+    try testMap(map);
+    try testing.expectEqual(TestEnum.A, map.get("HAVE").?);
+    try testing.expectEqual(TestEnum.E, map.get("SameLen").?);
+    try testing.expect(null == map.get("SameLength"));
+    try testing.expect(map.has("ThESe"));
+}
+
+test "StaticStringMapWithEql" {
+    const slice = [_]TestKV{
+        .{ "these", .D },
+        .{ "have", .A },
+        .{ "nothing", .B },
+        .{ "incommon", .C },
+        .{ "samelen", .E },
+    };
+
+    try testStaticStringMapWithEql(TestMapWithEql.initComptime(slice));
+}
+
+test "empty" {
+    const m1 = StaticStringMap(usize).initComptime(.{});
+    try testing.expect(null == m1.get("anything"));
+
+    const m2 = StaticStringMapWithEql(usize, eqlAsciiIgnoreCase).initComptime(.{});
+    try testing.expect(null == m2.get("anything"));
+
+    const m3 = try StaticStringMap(usize).init(.{}, test_alloc);
+    try testing.expect(null == m3.get("anything"));
+
+    const m4 = try StaticStringMapWithEql(usize, eqlAsciiIgnoreCase).init(.{}, test_alloc);
+    try testing.expect(null == m4.get("anything"));
+}
+
+test "redundant entries" {
+    const slice = [_]TestKV{
+        .{ "redundant", .D },
+        .{ "theNeedle", .A },
+        .{ "redundant", .B },
+        .{ "re" ++ "dundant", .C },
+        .{ "redun" ++ "dant", .E },
+    };
+    const map = TestMap.initComptime(slice);
+
+    // No promises about which one you get:
+    try testing.expect(null != map.get("redundant"));
+
+    // Default map is not case sensitive:
+    try testing.expect(null == map.get("REDUNDANT"));
+
+    try testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
+}
+
+test "redundant insensitive" {
+    const slice = [_]TestKV{
+        .{ "redundant", .D },
+        .{ "theNeedle", .A },
+        .{ "redundanT", .B },
+        .{ "RE" ++ "dundant", .C },
+        .{ "redun" ++ "DANT", .E },
+    };
+
+    const map = TestMapWithEql.initComptime(slice);
+
+    // No promises about which result you'll get ...
+    try testing.expect(null != map.get("REDUNDANT"));
+    try testing.expect(null != map.get("ReDuNdAnT"));
+    try testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
+}
+
+test "comptime-only value" {
+    const map = StaticStringMap(type).initComptime(.{
+        .{ "a", struct {
+            pub const foo = 1;
+        } },
+        .{ "b", struct {
+            pub const foo = 2;
+        } },
+        .{ "c", struct {
+            pub const foo = 3;
+        } },
+    });
+
+    try testing.expect(map.get("a").?.foo == 1);
+    try testing.expect(map.get("b").?.foo == 2);
+    try testing.expect(map.get("c").?.foo == 3);
+    try testing.expect(map.get("d") == null);
+}
+
+test "getLongestPrefix" {
+    const slice = [_]TestKV{
+        .{ "a", .A },
+        .{ "aa", .B },
+        .{ "aaa", .C },
+        .{ "aaaa", .D },
+    };
+
+    const map = TestMap.initComptime(slice);
+
+    try testing.expectEqual(null, map.getLongestPrefix(""));
+    try testing.expectEqual(null, map.getLongestPrefix("bar"));
+    try testing.expectEqualStrings("aaaa", map.getLongestPrefix("aaaabar").?.key);
+    try testing.expectEqualStrings("aaa", map.getLongestPrefix("aaabar").?.key);
+}
+
+test "getLongestPrefix2" {
+    const slice = [_]struct { []const u8, u8 }{
+        .{ "one", 1 },
+        .{ "two", 2 },
+        .{ "three", 3 },
+        .{ "four", 4 },
+        .{ "five", 5 },
+        .{ "six", 6 },
+        .{ "seven", 7 },
+        .{ "eight", 8 },
+        .{ "nine", 9 },
+    };
+    const map = StaticStringMap(u8).initComptime(slice);
+
+    try testing.expectEqual(1, map.get("one"));
+    try testing.expectEqual(null, map.get("o"));
+    try testing.expectEqual(null, map.get("onexxx"));
+    try testing.expectEqual(9, map.get("nine"));
+    try testing.expectEqual(null, map.get("n"));
+    try testing.expectEqual(null, map.get("ninexxx"));
+    try testing.expectEqual(null, map.get("xxx"));
+
+    try testing.expectEqual(1, map.getLongestPrefix("one").?.value);
+    try testing.expectEqual(1, map.getLongestPrefix("onexxx").?.value);
+    try testing.expectEqual(null, map.getLongestPrefix("o"));
+    try testing.expectEqual(null, map.getLongestPrefix("on"));
+    try testing.expectEqual(9, map.getLongestPrefix("nine").?.value);
+    try testing.expectEqual(9, map.getLongestPrefix("ninexxx").?.value);
+    try testing.expectEqual(null, map.getLongestPrefix("n"));
+    try testing.expectEqual(null, map.getLongestPrefix("xxx"));
+}
+
+test "long kvs_list doesn't exceed @setEvalBranchQuota" {
+    _ = TestMapVoid.initComptime([1]TestKVVoid{.{"x"}} ** 1_000);
+}
lib/std/std.zig
@@ -16,8 +16,8 @@ pub const BufMap = @import("buf_map.zig").BufMap;
 pub const BufSet = @import("buf_set.zig").BufSet;
 /// Deprecated: use `process.Child`.
 pub const ChildProcess = @import("child_process.zig").ChildProcess;
-pub const ComptimeStringMap = comptime_string_map.ComptimeStringMap;
-pub const ComptimeStringMapWithEql = comptime_string_map.ComptimeStringMapWithEql;
+pub const StaticStringMap = static_string_map.StaticStringMap;
+pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;
 pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;
 pub const DynLib = @import("dynamic_library.zig").DynLib;
 pub const DynamicBitSet = bit_set.DynamicBitSet;
@@ -62,7 +62,7 @@ pub const builtin = @import("builtin.zig");
 pub const c = @import("c.zig");
 pub const coff = @import("coff.zig");
 pub const compress = @import("compress.zig");
-pub const comptime_string_map = @import("comptime_string_map.zig");
+pub const static_string_map = @import("static_string_map.zig");
 pub const crypto = @import("crypto.zig");
 pub const debug = @import("debug.zig");
 pub const dwarf = @import("dwarf.zig");
src/codegen/c.zig
@@ -116,7 +116,7 @@ const ValueRenderLocation = enum {
 
 const BuiltinInfo = enum { none, bits };
 
-const reserved_idents = std.ComptimeStringMap(void, .{
+const reserved_idents = std.StaticStringMap(void).initComptime(.{
     // C language
     .{ "alignas", {
         @setEvalBranchQuota(4000);
src/link/Wasm/types.zig
@@ -244,7 +244,7 @@ pub const Feature = struct {
     }
 };
 
-pub const known_features = std.ComptimeStringMap(Feature.Tag, .{
+pub const known_features = std.StaticStringMap(Feature.Tag).initComptime(.{
     .{ "atomics", .atomics },
     .{ "bulk-memory", .bulk_memory },
     .{ "exception-handling", .exception_handling },
src/Compilation.zig
@@ -265,7 +265,7 @@ pub const CRTFile = struct {
 
 /// Supported languages for "zig clang -x <lang>".
 /// Loosely based on llvm-project/clang/include/clang/Driver/Types.def
-pub const LangToExt = std.ComptimeStringMap(FileExt, .{
+pub const LangToExt = std.StaticStringMap(FileExt).initComptime(.{
     .{ "c", .c },
     .{ "c-header", .h },
     .{ "c++", .cpp },
src/translate_c.zig
@@ -671,7 +671,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co
     return addTopLevelDecl(c, var_name, node);
 }
 
-const builtin_typedef_map = std.ComptimeStringMap([]const u8, .{
+const builtin_typedef_map = std.StaticStringMap([]const u8).initComptime(.{
     .{ "uint8_t", "u8" },
     .{ "int8_t", "i8" },
     .{ "uint16_t", "u16" },
test/src/Cases.zig
@@ -993,7 +993,7 @@ const TestManifest = struct {
     config_map: std.StringHashMap([]const u8),
     trailing_bytes: []const u8 = "",
 
-    const valid_keys = std.ComptimeStringMap(void, .{
+    const valid_keys = std.StaticStringMap(void).initComptime(.{
         .{ "is_test", {} },
         .{ "output_mode", {} },
         .{ "target", {} },
tools/gen_spirv_spec.zig
@@ -45,7 +45,7 @@ const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairConte
 /// Khronos made it so that these names are not defined explicitly, so
 /// we need to hardcode it (like they did).
 /// See https://github.com/KhronosGroup/SPIRV-Registry/
-const set_names = std.ComptimeStringMap([]const u8, .{
+const set_names = std.StaticStringMap([]const u8).initComptime(.{
     .{ "opencl.std.100", "OpenCL.std" },
     .{ "glsl.std.450", "GLSL.std.450" },
     .{ "opencl.debuginfo.100", "OpenCL.DebugInfo.100" },
tools/generate_linux_syscalls.zig
@@ -9,7 +9,7 @@ const fmt = std.fmt;
 const zig = std.zig;
 const fs = std.fs;
 
-const stdlib_renames = std.ComptimeStringMap([]const u8, .{
+const stdlib_renames = std.StaticStringMap([]const u8).initComptime(.{
     // Most 64-bit archs.
     .{ "newfstatat", "fstatat64" },
     // POWER.
CMakeLists.txt
@@ -222,7 +222,7 @@ set(ZIG_STAGE2_SOURCES
     "${CMAKE_SOURCE_DIR}/lib/std/c/linux.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/child_process.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/coff.zig"
-    "${CMAKE_SOURCE_DIR}/lib/std/comptime_string_map.zig"
+    "${CMAKE_SOURCE_DIR}/lib/std/static_string_map.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/crypto.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/crypto/blake3.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/crypto/siphash.zig"