Skip to content

Instantly share code, notes, and snippets.

@garettbass
Created June 8, 2022 07:17
Show Gist options
  • Save garettbass/aa861b67b016df12fa96e8045191631d to your computer and use it in GitHub Desktop.
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.
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