Created
June 8, 2022 07:17
-
-
Save garettbass/aa861b67b016df12fa96e8045191631d to your computer and use it in GitHub Desktop.
A generic type assigns an integer key to types at `comptime`, and allows values to be associated with the type at runtime.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
/// Creates a named section in the binary, mapping concrete types to | |
/// monotonically increasing keys of integer or enum type. | |
/// The value associated with a type may be assigned at runtime, and queried | |
/// with the key assigned to that concrete type at `comptime`. | |
pub fn TypeMap( | |
comptime name: []const u8, | |
comptime TKey: type, | |
comptime Value: type | |
) type { | |
const index_info = @typeInfo(TKey); | |
const TInt = switch (index_info) { | |
.Int => TKey, | |
.Enum => |t| | |
if (t.is_exhaustive) | |
@compileError("TKey enum must be non-exhaustive") | |
else | |
t.tag_type, | |
else => @compileError("TKey must be an enum or integer type"), | |
}; | |
return struct { | |
const Int = TInt; | |
const Key = TKey; | |
const Head = TypeMapPair(name, Value, void); | |
const Tail = TypeMapTail(name, Value); | |
const Ptr = TPtr(Value); | |
const ptr_size = @sizeOf(Ptr); | |
/// Returns the unique Key bound to type `T`. | |
pub fn key(comptime T: type) Key { | |
const Pair = TypeMapPair(name, Value, T); | |
return ptrToKey(Pair.ptr()); | |
} | |
/// Returns the value associated with type `T` for which `k == key(T)`. | |
pub fn get(k: Key) Value { | |
return (keyToPtr(k).*)(); | |
} | |
/// Sets the value associated with type `T`, and | |
/// returns the unique Key bound to type `T`. | |
pub fn set(comptime Type: type, value: Value) Key { | |
const Pair = TypeMapPair(name, Value, Type); | |
Pair.set(value); | |
return ptrToKey(Pair.ptr()); | |
} | |
/// Returns the number of types/keys/values stored in the map. | |
pub fn len() usize { | |
return ptrToInt(Tail.ptr()); | |
} | |
fn intToKey(i: Int) Key { | |
return switch (index_info) { | |
.Int => i, | |
.Enum => @intToEnum(Key, i), | |
else => unreachable, | |
}; | |
} | |
fn intToPtr(i: Int) Ptr { | |
std.debug.assert(i < len()); | |
return Head.ptr() + i; | |
} | |
fn keyToInt(k: Key) Int { | |
return switch (index_info) { | |
.Int => k, | |
.Enum => @enumToInt(k), | |
else => unreachable, | |
}; | |
} | |
fn keyToPtr(k: Key) Ptr { | |
return intToPtr(keyToInt(k)); | |
} | |
fn ptrToIndex(p: Ptr) usize { | |
return (@ptrToInt(p) - @ptrToInt(Head.ptr())) / ptr_size; | |
} | |
fn ptrToInt(p: Ptr) Int { | |
return @truncate(Int, ptrToIndex(p)); | |
} | |
fn ptrToKey(p: Ptr) Key { | |
return intToKey(ptrToInt(p)); | |
} | |
}; | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
fn TGet(comptime Value: type) type { | |
return fn() callconv(.C) Value; | |
} | |
fn TPtr(comptime Value: type) type { | |
return [*c]const TGet(Value); | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
// TODO: other platforms may require a different section name format | |
const section_prefix = "__DATA,"; | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
fn TypeMapPair( | |
comptime map_name: []const u8, | |
comptime Value: type, | |
comptime Type: type, | |
) type { | |
const comptimePrint = std.fmt.comptimePrint; | |
const Hash = std.hash.Fnv1a_64; | |
const type_name = @typeName(Type); | |
const type_hash = Hash.hash(type_name); | |
const type_id = comptimePrint("{X}", .{type_hash}); | |
const export_name = map_name ++ type_id; | |
// @compileLog(export_name); | |
// const type_id = type_name; | |
return struct { | |
const Ptr = TPtr(Value); | |
comptime { | |
@export(get, .{ | |
.name = export_name, | |
.linkage = .Strong, | |
.section = section_prefix ++ map_name ++ "$a", | |
}); | |
} | |
var _value: Value = undefined; | |
fn get() callconv(.C) Value { return _value; } | |
fn set(value: Value) void { _value = value; } | |
fn ptr() Ptr { return &get; } | |
}; | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
fn TypeMapTail( | |
comptime map_name: []const u8, | |
comptime Value: type, | |
) type { | |
return struct { | |
const Ptr = TPtr(Value); | |
comptime { | |
@export(get, .{ | |
.name = map_name ++ ".tail", | |
.linkage = .Strong, | |
.section = section_prefix ++ map_name ++ "$z", | |
}); | |
} | |
fn get() callconv(.C) Value { unreachable; } | |
fn ptr() Ptr { return &get; } | |
}; | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
test "TypeMap(usize, u32)" { | |
const print = std.debug.print; | |
const Type = std.builtin.Type; | |
const Map = TypeMap("coolness", usize, u32); | |
print("\n", .{}); | |
print(" Map.key(void): {}\n", .{Map.key(void)}); | |
print(" Map.key(void): {}\n", .{Map.key(void)}); | |
print(" Map.key(bool): {}\n", .{Map.key(bool)}); | |
print(" Map.key(Type): {}\n", .{Map.key(Type)}); | |
print(" Map.len(): {}\n", .{Map.len()}); | |
print("\n", .{}); | |
print(" Map.set(void, 0): {}\n", .{Map.set(void, 10)}); | |
print(" Map.set(void, 0): {}\n", .{Map.set(void, 10)}); | |
print(" Map.set(bool, 1): {}\n", .{Map.set(bool, 11)}); | |
print(" Map.set(Type, 2): {}\n", .{Map.set(Type, 12)}); | |
print(" Map.len(): {}\n", .{Map.len()}); | |
print("\n", .{}); | |
print(" Map.get(0): {}\n", .{Map.get(0)}); | |
print(" Map.get(0): {}\n", .{Map.get(0)}); | |
print(" Map.get(1): {}\n", .{Map.get(1)}); | |
print(" Map.get(2): {}\n", .{Map.get(2)}); | |
print(" Map.len(): {}\n", .{Map.len()}); | |
const expectEqual = std.testing.expectEqual; | |
try expectEqual(@as(usize, 0), Map.key(void)); | |
try expectEqual(@as(usize, 0), Map.key(void)); | |
try expectEqual(@as(usize, 1), Map.key(bool)); | |
try expectEqual(@as(usize, 1), Map.key(bool)); | |
try expectEqual(@as(usize, 2), Map.key(Type)); | |
try expectEqual(@as(usize, 2), Map.key(Type)); | |
try expectEqual(@as(usize, 3), Map.len()); | |
try expectEqual(@as(u32, 10), Map.get(0)); | |
try expectEqual(@as(u32, 10), Map.get(0)); | |
try expectEqual(@as(u32, 11), Map.get(1)); | |
try expectEqual(@as(u32, 11), Map.get(1)); | |
try expectEqual(@as(u32, 12), Map.get(2)); | |
try expectEqual(@as(u32, 12), Map.get(2)); | |
try expectEqual(@as(usize, 3), Map.len()); | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
test "TypeMap(Enum, *const TypeFormat)" { | |
const Enum = enum(u8) { | |
_, | |
const Self = @This(); | |
pub fn init(n: u8) Self { | |
return @intToEnum(Self, n); | |
} | |
pub fn toInt(self: Self) u8 { | |
return @enumToInt(self); | |
} | |
}; | |
const type_format = @import("type_format.zig"); | |
const Map = TypeMap("rtti", Enum, *const type_format.TypeFormat); | |
const Foo = enum { | |
zero, | |
one, | |
two, | |
three, | |
}; | |
const Bar = struct { | |
b: bool, | |
u: u32, | |
f: f32, | |
p: *f64, | |
a: [3]f64, | |
}; | |
const Baz = @Vector(4, f32); | |
const Bun = [4]f32; | |
const Uno = union(enum) { | |
b: bool, | |
i: u64, | |
}; | |
const DosTag = enum { | |
b, | |
i, | |
}; | |
const Dos = union(DosTag) { | |
b: bool, | |
i: u64, | |
}; | |
const Tres = extern union { | |
b: bool, | |
i: u64, | |
}; | |
const layoutOf = type_format.layoutOf; | |
const print = std.debug.print; | |
print("\n", .{}); | |
print(" Map.key(void): {}\n", .{Map.key(void)}); | |
print(" Map.key(void): {}\n", .{Map.key(void)}); | |
print(" Map.key(bool): {}\n", .{Map.key(bool)}); | |
print(" Map.key(Foo): {}\n", .{Map.key(Foo)}); | |
print(" Map.key(Bar): {}\n", .{Map.key(Bar)}); | |
print(" Map.key(Baz): {}\n", .{Map.key(Baz)}); | |
print(" Map.key(Bun): {}\n", .{Map.key(Bun)}); | |
print(" Map.key(Uno): {}\n", .{Map.key(Uno)}); | |
print(" Map.key(Dos): {}\n", .{Map.key(Dos)}); | |
print(" Map.key(Tres): {}\n", .{Map.key(Tres)}); | |
print(" Map.len(): {}\n", .{Map.len()}); | |
print("\n", .{}); | |
print(" Map.set(void, ...): {}\n", .{Map.set(void, layoutOf(void))}); | |
print(" Map.set(void, ...): {}\n", .{Map.set(void, layoutOf(void))}); | |
print(" Map.set(bool, ...): {}\n", .{Map.set(bool, layoutOf(bool))}); | |
print(" Map.set(Foo, ...): {}\n", .{Map.set(Foo, layoutOf(Foo))}); | |
print(" Map.set(Bar, ...): {}\n", .{Map.set(Bar, layoutOf(Bar))}); | |
print(" Map.set(Baz, ...): {}\n", .{Map.set(Baz, layoutOf(Baz))}); | |
print(" Map.set(Bun, ...): {}\n", .{Map.set(Bun, layoutOf(Bun))}); | |
print(" Map.set(Uno, ...): {}\n", .{Map.set(Uno, layoutOf(Uno))}); | |
print(" Map.set(Dos, ...): {}\n", .{Map.set(Dos, layoutOf(Dos))}); | |
print(" Map.set(Tres, ...): {}\n", .{Map.set(Tres, layoutOf(Tres))}); | |
print(" Map.len(): {}\n", .{Map.len()}); | |
print("\n", .{}); | |
print(" Map.get(0): {}\n", .{Map.get(Enum.init(0))}); | |
print(" Map.get(0): {}\n", .{Map.get(Enum.init(0))}); | |
print(" Map.get(1): {}\n", .{Map.get(Enum.init(1))}); | |
print(" Map.get(2): {}\n", .{Map.get(Enum.init(2))}); | |
print(" Map.get(3): {}\n", .{Map.get(Enum.init(3))}); | |
print(" Map.get(4): {}\n", .{Map.get(Enum.init(4))}); | |
print(" Map.get(5): {}\n", .{Map.get(Enum.init(5))}); | |
print(" Map.get(6): {}\n", .{Map.get(Enum.init(6))}); | |
print(" Map.get(7): {}\n", .{Map.get(Enum.init(7))}); | |
print(" Map.get(8): {}\n", .{Map.get(Enum.init(8))}); | |
print(" Map.len(): {}\n", .{Map.len()}); | |
const expectEqual = std.testing.expectEqual; | |
try expectEqual(@as(usize, 9), Map.len()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment