-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add @Tuple #4607
Comments
This is also useful for generating stack variables in code that generates calls to user functions. Use Case: I'm building an ECS that accepts a comptime-known function and generates a loop that inlines the function into the body. The parameters to the function are obtained from parallel arrays which need to be fetched by type, which has a runtime cost. Ideally I would be able to cache these arrays in stack variables, but without a way to generate stack vars based on the input function signature there isn't a typesafe way to do it. I've been getting around it for now by generating an on-stack const fn_info = @typeInfo(user_fn).Fn;
const args = fn_info.args;
comptime var types: [args.len]type = undefined;
comptime var array_types: [args.len]type = undefined;
var arrays: [args.len][*]u8 = undefined;
inline for (args) |arg, i| {
const ArgType = arg.arg_type.?;
types[i] = ArgType;
array_types[i] = ArgToArrayType(ArgType); // *T -> [*]T, *const T -> [*]const T, etc
arrays[i] = @ptrCast([*]u8, chunk.fetchSlice(ArgType).ptr);
}
for (chunk.range()) |_,i| {
switch (args.len) {
1 => @call(.{ .modifier = .always_inline }, user_fn, .{ @ptrCast(array_types[0], @alignCast(@alignOf(array_types[0].child, arrays[0]))[i] }),
2 => @call(.{ .modifier = .always_inline }, user_fn, .{
@ptrCast(array_types[0], @alignCast(@alignOf(array_types[0].child, arrays[0]))[i],
@ptrCast(array_types[1], @alignCast(@alignOf(array_types[1].child, arrays[1]))[i],
}),
3 => @call(.{ .modifier = .always_inline }, user_fn, .{
@ptrCast(array_types[0], @alignCast(@alignOf(array_types[0].child, arrays[0]))[i],
@ptrCast(array_types[1], @alignCast(@alignOf(array_types[1].child, arrays[1]))[i],
@ptrCast(array_types[2], @alignCast(@alignOf(array_types[2].child, arrays[2]))[i],
}),
// etc
}
} Alternatively, I could move the switch out to contain both the arg fetching and call statement. That approach would provide type safety but would also mean repeating myself 10 times (assuming 10 args is the limit). const fn_info = @typeInfo(user_fn).Fn;
const args = fnInfo.args;
comptime var types: [args.len]type = undefined;
comptime var slice_types: [args.len]type = undefined;
comptime for (args) |arg, i| {
const ArgType = arg.arg_type.?;
types[i] = ArgType;
slice_types[i] = ArgToSliceType(ArgType); // *T -> []T, *const T -> []const T, etc
}
const SliceTuple = @Tuple(slice_types);
const ArgTuple = @Tuple(types);
var slices: SliceTuple = undefined;
inline for (types) |ArgType, i| {
slices[i] = chunk.fetchSlice(ArgType);
}
for (range(chunk.len)) |_,chunk_i| {
var callArgs: ArgTuple = undefined;
inline for (types) |_, arg_i| {
callArgs[arg_i] = slices[arg_i][chunk_i];
}
@call(.{ .modifier = always_inline }, user_fn, callArgs);
} This ends up being a few more lines, but importantly, it now undergoes full type checking. It also gets rid of the call switch, though in this simple case that could be solved alternately by adding |
This is actually achievable in userspace. const std = @import("std");
// Create a tuple with one runtime-typed field
fn UniTuple(comptime T: type) type {
const Hack = struct {
var forced_runtime: T = undefined;
};
return @TypeOf( .{ Hack.forced_runtime } );
}
/// Types should be an iterable of types
fn Tuple(comptime types: var) type {
const H = struct {
value: var,
};
var empty_tuple = .{};
var container = H{
.value = empty_tuple,
};
for (types) |T| {
container.value = container.value ++ UniTuple(T){ .@"0" = undefined };
}
return @TypeOf(container.value);
}
pub fn main() !void
{
const T = Tuple(.{ i32, []const u8 });
var t : T = .{
.@"0" = 42,
.@"1" = "Hello, World!"
};
std.debug.print("t = {}\n", .{ t });
}
I will try applying this to the usecase I mentioned in the opening comment (in interface.zig) tomorrow. |
This is now possible in userspace with @type(.Struct). |
Implements std.meta.Tuple(), implements #4607 in userland.
This is a proposal to add a builtin function that returns a tuple type.
Proposed signature:
This would be a useful feature for metaprogramming purposes.
More specifically, it would be helpful in metaprograms that generate functions that forward arguments using
@call
, as it could be used to generate non-generic function pointers that accept tuple types calculated at comptime.Currently, passing a tuple requires the argument to be 'var' and forces the function to be generic.
Here is an example usecase from the std.interface PR:
The text was updated successfully, but these errors were encountered: