Weird vector creation syntax when casting from slices

I am playing around with SIMD and came across this very very weird sytnax, which I believe is logically correct.

What I have expected here to happen is obviously to not use the second [0…64], because logically the slice is already of lenght of 64.

Am I expecting too much? Is there a better way to solve this?

pub fn xor(a: []const []u8, b: []const []u8) void {
    std.debug.assert(a.len == b.len);
    std.debug.assert(a.len >= 0);
    std.debug.assert(a[0].len % 64 == 0);
    std.debug.assert(b[0].len % 64 == 0);

    for (a, b) |ac, bc| {
        for (0..a[0].len / 64) |i| {
            const start = i * 64;
            const end = (i + 1) * 64;

            const c: @Vector(64, u8) = ac[start..end][0..64].*;
            const d: @Vector(64, u8) = bc[start..end][0..64].*;

            ac[start..end][0..64].* = c ^ d;
        }
    }
}

test xor {
    var arr1: [128]u8 = @splat(0);
    var arr2: [128]u8 = @splat(1);

    const slices_arr1: [1][]u8 = .{arr1[0..arr1.len]};
    const slices_arr2: [1][]u8 = .{arr2[0..arr2.len]};

    xor(slices_arr1[0..1], slices_arr2[0..1]);

    try std.testing.expectEqualDeep(slices_arr1, slices_arr2);
}

The code below results in the following error:

pub fn xor(a: []const []u8, b: []const []u8) void {
    std.debug.assert(a.len == b.len);
    std.debug.assert(a.len >= 0);
    std.debug.assert(a[0].len % 64 == 0);
    std.debug.assert(b[0].len % 64 == 0);

    for (a, b) |ac, bc| {
        for (0..a[0].len / 64) |i| {
            const start = i * 64;
            const end = (i + 1) * 64;

            const c: @Vector(64, u8) = ac[start..end].*;
            const d: @Vector(64, u8) = bc[start..end].*;

            ac[start..end].* = c ^ d;
        }
    }
}
src/main.zig:15:54: error: index syntax required for slice type '[]u8'
            const c: @Vector(64, u8) = ac[start..end].*;

In general, Zig doesn’t track any “properties” of values outside of the type system and compile-time-known values. In other words, the compiler can’t semantically “remember” that end - start is 64. Partly for this reason, a[start..end] actually isn’t all that common in Zig; it’s far more typical to see a[start..][0..len]. As well as making this case work (because the length is comptime-known), it’s also more often than not nicer in practice to work with start+len rather than start+end.

So in your case, just remove the uses of end (and so also its declaration) from the first snippet!

6 Likes

You can make it a bit nicer by using pointer arithmetic with the many pointer of ac and bc:

const start = i * 64;
// const end = (i + 1) * 64;

const c: @Vector(64, u8) = (ac.ptr + start)[0..64].*;
const d: @Vector(64, u8) = (bc.ptr + start)[0..64].*;

// This could probably be expressed more nicely as a @memcpy()
(ac.ptr + start)[0..64].* = c ^ d;

In general this function is very confusing to me.
Since you’re always operating on a slice of slices where you know each slice’s len will be a multiple of 64, it seems to make more sense to me to just pass a comptime usize as the length of each slice, then change the function’s other arguments to a slice of vectors with that length.

Well it’s because the slice len is not comptime known

What I’m doing for SIMD is using slice of vectors and ptrCast from slice of elements.

It implies slices must be aligned but if you’re in SIMD land you probably want that anyway.