I didn’t know it was possible to create a comptime program that was so large it would no longer compile. I kept raising @setEvalBranchQuota to fix it, but now, by passing maxInt(u32), the compiler just segfaults. I have to assume I’m either missing something obvious to work around this or I’ve been misusing comptime altogether. After investing several weeks into this tool already, I wanted to see if anyone had insight into how salvageable this project is.
I work in transportation and this is a cli/library for my job. There are a range of versions of raw/processed geodata this library has to pull from. My solution to this was to create a multi-step build system that embedded these files at comptime. When I originally wrote it I just parsed these json files and created the derived hashmaps I needed at runtime. I decided this wasn’t good enough, and I got greedy.
To get around not being able to parse json at comptime, I made a build module that turned the json into zon and handed it + a manifest off to the final program. By largely copying the StaticStringMap implementation, I made a generalized comptime map that would make these maps “instantly” available at runtime. (there’s a lot more to the system but hopefully you get the picture)
pub fn Map(
comptime K: type, // has to be an int
comptime V: type, // hopefully a struct, if it's an array we gotta do something else I think
) type {
return struct {
kvs: *const KVs = &empty_kvs,
pub const KV = struct {
key: K,
value: V,
};
const KVs = struct {
keys: [*]const K,
values: [*]const V,
len: u32,
};
const Self = @This();
const empty_kvs = KVs{
.keys = &empty_keys,
.values = &empty_vals,
.len = 0,
};
const empty_keys = [0]K{};
const empty_vals = [0]V{};
const empty_len_indexes = [0]u32{};
// same deal as StaticStringMap
// has to be a list of struct { K, V }
pub inline fn init(comptime kvs_list: anytype) Self {
comptime {
var self = Self {};
@setEvalBranchQuota(std.math.maxInt(u32));
// this is a dogshit sort but it's comptime so you can't get mad at me
var keys: [kvs_list.len]K = undefined;
for(kvs_list, 0..kvs_list.len) |kv, i| {
const key, const val = kv;
_ = val;
keys[i] = key;
}
std.mem.sort(K, &keys, {}, std.sort.asc(K));
var values: [kvs_list.len]V = undefined;
for(keys, 0..keys.len) |sorted_key, i| {
const index = blk: {
for(kvs_list, 0..kvs_list.len) |kv, j| {
const key, const val = kv;
_ = val;
if(key == sorted_key) {
break :blk j;
}
}
unreachable;
};
const key, const val = kvs_list[index];
_ = key;
values[i] = val;
}
const final_keys = keys;
const final_values = values;
self.kvs = &.{
.keys = &final_keys,
.values = &final_values,
.len = @intCast(kvs_list.len),
};
return self;
}
}
pub fn get(self: Self, key: K) ?V {
// TODO: keys are sorted, do a binary search
// but I just want to see if it compiles
for(self.kvs.keys, 0..self.kvs.len) |map_key, i| {
if(map_key == key) {
return self.kvs.values[i];
}
}
return null;
}
};
}
This worked, but compile times ballooned to an insane degree. This should have been a warning, but I pressed on. I made it pretty far and the library is ok and functional, but now that I need to add a set of maps that was derived from 2 other maps, I’ve hit my head on the setEvalBranchQuota ceiling.
I’m sure I’m not the first person to obsessively pre-compute to this degree. Where did I go wrong