Skip to documentation
SLOP

tiny.simd.unroller

Reference tiny.simd unroller

Defined in tiny.simd.

API (4)

Actions

Public operations.

No direct callersNo direct callstiny.simdunroller
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/simd/src/root.zig:40

zig
pub const unroller = @import("unroller.zig");

Source: lib/simd/src/unroller.zig

zig
const std = @import("std");const memory = @import("memory.zig");const tag = @import("tag.zig");const Window = struct {    lane_start: usize,    storage_start: usize,    count: usize,};pub fn UnrollerUnit(comptime InputLane: type, comptime OutputLane: type) type {    validateLane(InputLane);    validateLane(OutputLane);    const LargerLane = signedLane(@max(@sizeOf(InputLane), @sizeOf(OutputLane)));    const LargerTag = tag.ScalableTag(LargerLane);    return struct {        pub const input_count: usize = 1;        pub const Input: type = InputLane;        pub const Output: type = OutputLane;        pub const Larger: type = LargerLane;        pub const Tag: type = LargerTag;        pub const InputTag: type = LargerTag.rebind(InputLane);        pub const OutputTag: type = LargerTag.rebind(OutputLane);        pub const InputVector: type = InputTag.Vector;        pub const OutputVector: type = OutputTag.Vector;        pub const XVector: type = InputVector;        pub const YVector: type = OutputVector;        pub const max_unit_lanes: usize = LargerTag.lane_count;        pub const actual_lanes: usize = LargerTag.lane_count;        pub fn maxUnitLanes() usize {            return max_unit_lanes;        }        pub fn actualLanes() usize {            return actual_lanes;        }        pub fn x0Init() InputVector {            return @splat(0);        }        pub fn yInit() OutputVector {            return @splat(0);        }        pub fn load(index: isize, input: []const Input) InputVector {            return loadFull(InputTag, index, input);        }        pub fn maskLoad(index: isize, input: []const Input, places: isize) InputVector {            return maskLoadOr(@splat(0), index, input, places);        }        pub fn maskLoadOr(            inactive: InputVector,            index: isize,            input: []const Input,            places: isize,        ) InputVector {            return loadPartial(InputTag, inactive, index, input, places);        }        pub fn storeAndShortCircuit(            index: isize,            output: []Output,            value: OutputVector,        ) bool {            storeFull(OutputTag, index, output, value);            return true;        }        pub fn maskStore(            index: isize,            output: []Output,            value: OutputVector,            places: isize,        ) usize {            return storePartial(OutputTag, index, output, value, places);        }        pub fn reduceFinal(_: OutputVector, _: []Output) usize {            return 0;        }        pub fn reduceUnrolled(            _: OutputVector,            _: OutputVector,            _: OutputVector,            value: OutputVector,        ) OutputVector {            return value;        }    };}pub fn UnrollerUnit2D(    comptime Input0Lane: type,    comptime Input1Lane: type,    comptime OutputLane: type,) type {    validateLane(Input0Lane);    validateLane(Input1Lane);    validateLane(OutputLane);    const LargerLane = signedLane(@max(        @sizeOf(Input0Lane),        @max(@sizeOf(Input1Lane), @sizeOf(OutputLane)),    ));    const LargerTag = tag.ScalableTag(LargerLane);    return struct {        pub const input_count: usize = 2;        pub const Input0: type = Input0Lane;        pub const Input1: type = Input1Lane;        pub const Output: type = OutputLane;        pub const Larger: type = LargerLane;        pub const Tag: type = LargerTag;        pub const Input0Tag: type = LargerTag.rebind(Input0Lane);        pub const Input1Tag: type = LargerTag.rebind(Input1Lane);        pub const OutputTag: type = LargerTag.rebind(OutputLane);        pub const Input0Vector: type = Input0Tag.Vector;        pub const Input1Vector: type = Input1Tag.Vector;        pub const OutputVector: type = OutputTag.Vector;        pub const X0Vector: type = Input0Vector;        pub const X1Vector: type = Input1Vector;        pub const YVector: type = OutputVector;        pub const max_unit_lanes: usize = LargerTag.lane_count;        pub const actual_lanes: usize = LargerTag.lane_count;        pub fn maxUnitLanes() usize {            return max_unit_lanes;        }        pub fn actualLanes() usize {            return actual_lanes;        }        pub fn x0Init() Input0Vector {            return @splat(0);        }        pub fn x1Init() Input1Vector {            return @splat(0);        }        pub fn yInit() OutputVector {            return @splat(0);        }        pub fn load0(index: isize, input: []const Input0) Input0Vector {            return loadFull(Input0Tag, index, input);        }        pub fn load1(index: isize, input: []const Input1) Input1Vector {            return loadFull(Input1Tag, index, input);        }        pub fn maskLoad0(            index: isize,            input: []const Input0,            places: isize,        ) Input0Vector {            return maskLoad0Or(@splat(0), index, input, places);        }        pub fn maskLoad0Or(            inactive: Input0Vector,            index: isize,            input: []const Input0,            places: isize,        ) Input0Vector {            return loadPartial(Input0Tag, inactive, index, input, places);        }        pub fn maskLoad1(            index: isize,            input: []const Input1,            places: isize,        ) Input1Vector {            return maskLoad1Or(@splat(0), index, input, places);        }        pub fn maskLoad1Or(            inactive: Input1Vector,            index: isize,            input: []const Input1,            places: isize,        ) Input1Vector {            return loadPartial(Input1Tag, inactive, index, input, places);        }        pub fn storeAndShortCircuit(            index: isize,            output: []Output,            value: OutputVector,        ) bool {            storeFull(OutputTag, index, output, value);            return true;        }        pub fn maskStore(            index: isize,            output: []Output,            value: OutputVector,            places: isize,        ) usize {            return storePartial(OutputTag, index, output, value, places);        }        pub fn reduceFinal(_: OutputVector, _: []Output) usize {            return 0;        }        pub fn reduceUnrolled(            _: OutputVector,            _: OutputVector,            _: OutputVector,            value: OutputVector,        ) OutputVector {            return value;        }    };}pub fn unroll(unit: anytype, input: anytype, output: anytype) void {    const Unit = pointee(@TypeOf(unit));    const Base = unitBase(Unit, 1);    if (!@hasDecl(Unit, "func")) @compileError("unroller unit must declare func");    const input_slice: []const Base.Input = input;    const output_slice: []Base.Output = output;    const lane_count = Base.actual_lanes;    std.debug.assert(input_slice.len <= std.math.maxInt(isize));    var x0 = x0Init(Unit, Base, unit);    var y = yInit(Unit, Base, unit);    var index: usize = 0;    if (input_slice.len < lane_count) {        const count: isize = @intCast(input_slice.len);        x0 = maskLoad(Unit, Base, unit, 0, input_slice, count);        y = unit.func(0, x0, y);        _ = maskStore(Unit, Base, unit, 0, output_slice, y, count);        _ = reduceFinal(Unit, Base, unit, y, output_slice);        return;    }    if (input_slice.len > 4 * lane_count) {        var x01 = x0Init(Unit, Base, unit);        var y1 = yInit(Unit, Base, unit);        var x02 = x0Init(Unit, Base, unit);        var y2 = yInit(Unit, Base, unit);        var x03 = x0Init(Unit, Base, unit);        var y3 = yInit(Unit, Base, unit);        while (index + 4 * lane_count <= input_slice.len) {            x0 = load(Unit, Base, unit, @intCast(index), input_slice);            x01 = load(Unit, Base, unit, @intCast(index + lane_count), input_slice);            x02 = load(Unit, Base, unit, @intCast(index + 2 * lane_count), input_slice);            x03 = load(Unit, Base, unit, @intCast(index + 3 * lane_count), input_slice);            y = unit.func(@intCast(index), x0, y);            y1 = unit.func(@intCast(index + lane_count), x01, y1);            y2 = unit.func(@intCast(index + 2 * lane_count), x02, y2);            y3 = unit.func(@intCast(index + 3 * lane_count), x03, y3);            if (!storeAndShortCircuit(Unit, Base, unit, @intCast(index), output_slice, y)) return;            index += lane_count;            if (!storeAndShortCircuit(Unit, Base, unit, @intCast(index), output_slice, y1)) return;            index += lane_count;            if (!storeAndShortCircuit(Unit, Base, unit, @intCast(index), output_slice, y2)) return;            index += lane_count;            if (!storeAndShortCircuit(Unit, Base, unit, @intCast(index), output_slice, y3)) return;            index += lane_count;        }        y = reduceUnrolled(Unit, Base, unit, y3, y2, y1, y);    }    while (index + lane_count <= input_slice.len) : (index += lane_count) {        x0 = load(Unit, Base, unit, @intCast(index), input_slice);        y = unit.func(@intCast(index), x0, y);        if (!storeAndShortCircuit(Unit, Base, unit, @intCast(index), output_slice, y)) return;    }    if (index != input_slice.len) {        const tail_index = input_slice.len - lane_count;        const places = @as(isize, @intCast(index)) - @as(isize, @intCast(input_slice.len));        x0 = maskLoad(Unit, Base, unit, @intCast(tail_index), input_slice, places);        y = unit.func(@intCast(tail_index), x0, y);        _ = maskStore(Unit, Base, unit, @intCast(tail_index), output_slice, y, places);    }    _ = reduceFinal(Unit, Base, unit, y, output_slice);}pub fn unroll2(unit: anytype, input0: anytype, input1: anytype, output: anytype) void {    const Unit = pointee(@TypeOf(unit));    const Base = unitBase(Unit, 2);    if (!@hasDecl(Unit, "func")) @compileError("two-input unroller unit must declare func");    const input0_slice: []const Base.Input0 = input0;    const input1_slice: []const Base.Input1 = input1;    const output_slice: []Base.Output = output;    const lane_count = Base.actual_lanes;    std.debug.assert(input0_slice.len == input1_slice.len);    std.debug.assert(input0_slice.len <= std.math.maxInt(isize));    var x0 = x0Init2(Unit, Base, unit);    var x1 = x1Init2(Unit, Base, unit);    var y = yInit(Unit, Base, unit);    var index: usize = 0;    if (input0_slice.len < lane_count) {        const count: isize = @intCast(input0_slice.len);        x0 = maskLoad0(Unit, Base, unit, 0, input0_slice, count);        x1 = maskLoad1(Unit, Base, unit, 0, input1_slice, count);        y = unit.func(0, x0, x1, y);        _ = maskStore(Unit, Base, unit, 0, output_slice, y, count);        _ = reduceFinal(Unit, Base, unit, y, output_slice);        return;    }    if (input0_slice.len > 4 * lane_count) {        var x01 = x0Init2(Unit, Base, unit);        var x11 = x1Init2(Unit, Base, unit);        var y1 = yInit(Unit, Base, unit);        var x02 = x0Init2(Unit, Base, unit);        var x12 = x1Init2(Unit, Base, unit);        var y2 = yInit(Unit, Base, unit);        var x03 = x0Init2(Unit, Base, unit);        var x13 = x1Init2(Unit, Base, unit);        var y3 = yInit(Unit, Base, unit);        while (index + 4 * lane_count <= input0_slice.len) {            x0 = load0(Unit, Base, unit, @intCast(index), input0_slice);            x1 = load1(Unit, Base, unit, @intCast(index), input1_slice);            x01 = load0(Unit, Base, unit, @intCast(index + lane_count), input0_slice);            x11 = load1(Unit, Base, unit, @intCast(index + lane_count), input1_slice);            x02 = load0(Unit, Base, unit, @intCast(index + 2 * lane_count), input0_slice);            x12 = load1(Unit, Base, unit, @intCast(index + 2 * lane_count), input1_slice);            x03 = load0(Unit, Base, unit, @intCast(index + 3 * lane_count), input0_slice);            x13 = load1(Unit, Base, unit, @intCast(index + 3 * lane_count), input1_slice);            y = unit.func(@intCast(index), x0, x1, y);            y1 = unit.func(@intCast(index + lane_count), x01, x11, y1);            y2 = unit.func(@intCast(index + 2 * lane_count), x02, x12, y2);            y3 = unit.func(@intCast(index + 3 * lane_count), x03, x13, y3);            if (!storeAndShortCircuit(Unit, Base, unit, @intCast(index), output_slice, y)) return;            index += lane_count;            if (!storeAndShortCircuit(Unit, Base, unit, @intCast(index), output_slice, y1)) return;            index += lane_count;            if (!storeAndShortCircuit(Unit, Base, unit, @intCast(index), output_slice, y2)) return;            index += lane_count;            if (!storeAndShortCircuit(Unit, Base, unit, @intCast(index), output_slice, y3)) return;            index += lane_count;        }        y = reduceUnrolled(Unit, Base, unit, y3, y2, y1, y);    }    while (index + lane_count <= input0_slice.len) : (index += lane_count) {        x0 = load0(Unit, Base, unit, @intCast(index), input0_slice);        x1 = load1(Unit, Base, unit, @intCast(index), input1_slice);        y = unit.func(@intCast(index), x0, x1, y);        if (!storeAndShortCircuit(Unit, Base, unit, @intCast(index), output_slice, y)) return;    }    if (index != input0_slice.len) {        const tail_index = input0_slice.len - lane_count;        const places = @as(isize, @intCast(index)) - @as(isize, @intCast(input0_slice.len));        x0 = maskLoad0(Unit, Base, unit, @intCast(tail_index), input0_slice, places);        x1 = maskLoad1(Unit, Base, unit, @intCast(tail_index), input1_slice, places);        y = unit.func(@intCast(tail_index), x0, x1, y);        _ = maskStore(Unit, Base, unit, @intCast(tail_index), output_slice, y, places);    }    _ = reduceFinal(Unit, Base, unit, y, output_slice);}fn x0Init(comptime Unit: type, comptime Base: type, unit: anytype) Base.InputVector {    if (comptime @hasDecl(Unit, "x0Init")) return unit.x0Init();    return Base.x0Init();}fn x0Init2(comptime Unit: type, comptime Base: type, unit: anytype) Base.Input0Vector {    if (comptime @hasDecl(Unit, "x0Init")) return unit.x0Init();    return Base.x0Init();}fn x1Init2(comptime Unit: type, comptime Base: type, unit: anytype) Base.Input1Vector {    if (comptime @hasDecl(Unit, "x1Init")) return unit.x1Init();    return Base.x1Init();}fn yInit(comptime Unit: type, comptime Base: type, unit: anytype) Base.OutputVector {    if (comptime @hasDecl(Unit, "yInit")) return unit.yInit();    return Base.yInit();}fn load(    comptime Unit: type,    comptime Base: type,    unit: anytype,    index: isize,    input: []const Base.Input,) Base.InputVector {    if (comptime @hasDecl(Unit, "load")) return unit.load(index, input);    return Base.load(index, input);}fn load0(    comptime Unit: type,    comptime Base: type,    unit: anytype,    index: isize,    input: []const Base.Input0,) Base.Input0Vector {    if (comptime @hasDecl(Unit, "load0")) return unit.load0(index, input);    return Base.load0(index, input);}fn load1(    comptime Unit: type,    comptime Base: type,    unit: anytype,    index: isize,    input: []const Base.Input1,) Base.Input1Vector {    if (comptime @hasDecl(Unit, "load1")) return unit.load1(index, input);    return Base.load1(index, input);}fn maskLoad(    comptime Unit: type,    comptime Base: type,    unit: anytype,    index: isize,    input: []const Base.Input,    places: isize,) Base.InputVector {    if (comptime @hasDecl(Unit, "maskLoad")) return unit.maskLoad(index, input, places);    return Base.maskLoad(index, input, places);}fn maskLoad0(    comptime Unit: type,    comptime Base: type,    unit: anytype,    index: isize,    input: []const Base.Input0,    places: isize,) Base.Input0Vector {    if (comptime @hasDecl(Unit, "maskLoad0")) return unit.maskLoad0(index, input, places);    return Base.maskLoad0(index, input, places);}fn maskLoad1(    comptime Unit: type,    comptime Base: type,    unit: anytype,    index: isize,    input: []const Base.Input1,    places: isize,) Base.Input1Vector {    if (comptime @hasDecl(Unit, "maskLoad1")) return unit.maskLoad1(index, input, places);    return Base.maskLoad1(index, input, places);}fn storeAndShortCircuit(    comptime Unit: type,    comptime Base: type,    unit: anytype,    index: isize,    output: []Base.Output,    value: Base.OutputVector,) bool {    if (comptime @hasDecl(Unit, "storeAndShortCircuit")) {        return unit.storeAndShortCircuit(index, output, value);    }    return Base.storeAndShortCircuit(index, output, value);}fn maskStore(    comptime Unit: type,    comptime Base: type,    unit: anytype,    index: isize,    output: []Base.Output,    value: Base.OutputVector,    places: isize,) usize {    if (comptime @hasDecl(Unit, "maskStore")) {        return unit.maskStore(index, output, value, places);    }    return Base.maskStore(index, output, value, places);}fn reduceFinal(    comptime Unit: type,    comptime Base: type,    unit: anytype,    value: Base.OutputVector,    output: []Base.Output,) usize {    if (comptime @hasDecl(Unit, "reduceFinal")) return unit.reduceFinal(value, output);    return Base.reduceFinal(value, output);}fn reduceUnrolled(    comptime Unit: type,    comptime Base: type,    unit: anytype,    x0: Base.OutputVector,    x1: Base.OutputVector,    x2: Base.OutputVector,    value: Base.OutputVector,) Base.OutputVector {    if (comptime @hasDecl(Unit, "reduceUnrolled")) {        return unit.reduceUnrolled(x0, x1, x2, value);    }    return Base.reduceUnrolled(x0, x1, x2, value);}fn loadFull(comptime D: type, index: isize, input: []const D.Lane) D.Vector {    std.debug.assert(index >= 0);    const start: usize = @intCast(index);    std.debug.assert(start <= input.len);    std.debug.assert(D.lane_count <= input.len - start);    return memory.load(D, input[start..]);}fn loadPartial(    comptime D: type,    inactive: D.Vector,    index: isize,    input: []const D.Lane,    places: isize,) D.Vector {    const selected = window(D.lane_count, index, places, input.len);    var lanes: [D.lane_count]D.Lane = inactive;    for (0..selected.count) |offset| {        lanes[selected.lane_start + offset] = input[selected.storage_start + offset];    }    return lanes;}fn storeFull(    comptime D: type,    index: isize,    output: []D.Lane,    value: D.Vector,) void {    std.debug.assert(index >= 0);    const start: usize = @intCast(index);    std.debug.assert(start <= output.len);    std.debug.assert(D.lane_count <= output.len - start);    memory.store(D, value, output[start..]);}fn storePartial(    comptime D: type,    index: isize,    output: []D.Lane,    value: D.Vector,    places: isize,) usize {    const selected = window(D.lane_count, index, places, output.len);    const lanes: [D.lane_count]D.Lane = value;    for (0..selected.count) |offset| {        output[selected.storage_start + offset] = lanes[selected.lane_start + offset];    }    return selected.count;}fn window(lane_count: usize, index: isize, places: isize, length: usize) Window {    const bound: isize = @intCast(lane_count);    std.debug.assert(places >= -bound);    std.debug.assert(places <= bound);    const count: usize = @intCast(if (places < 0) -places else places);    const lane_start = if (places < 0) lane_count - count else 0;    const lane_offset: isize = @intCast(lane_start);    std.debug.assert(index <= std.math.maxInt(isize) - lane_offset);    const storage_index = index + lane_offset;    std.debug.assert(storage_index >= 0);    const storage_start: usize = @intCast(storage_index);    std.debug.assert(storage_start <= length);    std.debug.assert(count <= length - storage_start);    return .{        .lane_start = lane_start,        .storage_start = storage_start,        .count = count,    };}fn pointee(comptime Pointer: type) type {    return switch (@typeInfo(Pointer)) {        .pointer => |info| if (info.size == .one)            info.child        else            @compileError("unroller unit must be passed by single-item pointer"),        else => @compileError("unroller unit must be passed by pointer"),    };}fn unitBase(comptime Unit: type, comptime input_count: usize) type {    if (!@hasDecl(Unit, "Base")) {        @compileError("unroller unit must declare Base = UnrollerUnit(...) or UnrollerUnit2D(...)");    }    const Base = Unit.Base;    if (!@hasDecl(Base, "input_count") or Base.input_count != input_count) {        @compileError("unroller unit Base has the wrong input count");    }    return Base;}fn validateLane(comptime T: type) void {    if (!tag.isLane(T)) @compileError("unroller units require SIMD lane types");}fn signedLane(comptime byte_count: usize) type {    return switch (byte_count) {        1 => i8,        2 => i16,        4 => i32,        8 => i64,        else => @compileError("unroller lane sizes must be 1, 2, 4, or 8 bytes"),    };}fn MultiplyUnit(comptime T: type) type {    const UnitBase = UnrollerUnit2D(T, T, T);    return struct {        pub const Base: type = UnitBase;        pub fn func(            _: *@This(),            _: isize,            x0: Base.Input0Vector,            x1: Base.Input1Vector,            _: Base.OutputVector,        ) Base.OutputVector {            return multiplyVectors(T, x0, x1);        }    };}fn AccumulateUnit(comptime T: type) type {    const UnitBase = UnrollerUnit(T, T);    return struct {        pub const Base: type = UnitBase;        pub fn func(            _: *@This(),            _: isize,            x: Base.InputVector,            y: Base.OutputVector,        ) Base.OutputVector {            return addVectors(T, x, y);        }        pub fn storeAndShortCircuit(            _: *@This(),            _: isize,            _: []T,            _: Base.OutputVector,        ) bool {            return true;        }        pub fn maskStore(            _: *@This(),            _: isize,            _: []T,            _: Base.OutputVector,            _: isize,        ) usize {            return 0;        }        pub fn reduceFinal(            _: *@This(),            value: Base.OutputVector,            output: []T,        ) usize {            std.debug.assert(output.len >= 1);            output[0] = sumVector(T, value);            return 1;        }        pub fn reduceUnrolled(            _: *@This(),            x0: Base.OutputVector,            x1: Base.OutputVector,            x2: Base.OutputVector,            value: Base.OutputVector,        ) Base.OutputVector {            return addVectors(T, addVectors(T, x0, x1), addVectors(T, x2, value));        }    };}fn DotUnit(comptime T: type) type {    const UnitBase = UnrollerUnit2D(T, T, T);    return struct {        pub const Base: type = UnitBase;        pub fn func(            _: *@This(),            _: isize,            x0: Base.Input0Vector,            x1: Base.Input1Vector,            y: Base.OutputVector,        ) Base.OutputVector {            return addVectors(T, multiplyVectors(T, x0, x1), y);        }        pub fn storeAndShortCircuit(            _: *@This(),            _: isize,            _: []T,            _: Base.OutputVector,        ) bool {            return true;        }        pub fn maskStore(            _: *@This(),            _: isize,            _: []T,            _: Base.OutputVector,            _: isize,        ) usize {            return 0;        }        pub fn reduceFinal(            _: *@This(),            value: Base.OutputVector,            output: []T,        ) usize {            std.debug.assert(output.len >= 1);            output[0] = sumVector(T, value);            return 1;        }        pub fn reduceUnrolled(            _: *@This(),            x0: Base.OutputVector,            x1: Base.OutputVector,            x2: Base.OutputVector,            value: Base.OutputVector,        ) Base.OutputVector {            return addVectors(T, addVectors(T, x0, x1), addVectors(T, x2, value));        }    };}fn MinUnit(comptime T: type) type {    const UnitBase = UnrollerUnit(T, T);    return struct {        pub const Base: type = UnitBase;        pub fn func(            _: *@This(),            _: isize,            x: Base.InputVector,            y: Base.OutputVector,        ) Base.OutputVector {            return @min(x, y);        }        pub fn yInit(_: *@This()) Base.OutputVector {            return @splat(highestValue(T));        }        pub fn maskLoad(            _: *@This(),            index: isize,            input: []const T,            places: isize,        ) Base.InputVector {            return Base.maskLoadOr(@splat(highestValue(T)), index, input, places);        }        pub fn storeAndShortCircuit(            _: *@This(),            _: isize,            _: []T,            _: Base.OutputVector,        ) bool {            return true;        }        pub fn maskStore(            _: *@This(),            _: isize,            _: []T,            _: Base.OutputVector,            _: isize,        ) usize {            return 0;        }        pub fn reduceFinal(            _: *@This(),            value: Base.OutputVector,            output: []T,        ) usize {            std.debug.assert(output.len >= 1);            output[0] = minVector(T, value);            return 1;        }        pub fn reduceUnrolled(            _: *@This(),            x0: Base.OutputVector,            x1: Base.OutputVector,            x2: Base.OutputVector,            value: Base.OutputVector,        ) Base.OutputVector {            return @min(@min(x0, x1), @min(x2, value));        }    };}fn ConvertUnit(comptime From: type, comptime To: type) type {    const UnitBase = UnrollerUnit(From, To);    return struct {        pub const Base: type = UnitBase;        pub fn func(            _: *@This(),            _: isize,            x: Base.InputVector,            _: Base.OutputVector,        ) Base.OutputVector {            var result: Base.OutputVector = undefined;            inline for (0..Base.actual_lanes) |lane_index| {                result[lane_index] = convertScalar(To, x[lane_index]);            }            return result;        }    };}fn FindUnit(comptime T: type) type {    const Index = signedLane(@sizeOf(T));    const UnitBase = UnrollerUnit(T, Index);    return struct {        needle: T,        pub const Base: type = UnitBase;        pub fn func(            self: *@This(),            index: isize,            x: Base.InputVector,            y: Base.OutputVector,        ) Base.OutputVector {            inline for (0..Base.actual_lanes) |lane_index| {                if (x[lane_index] == self.needle) {                    const found = index + @as(isize, @intCast(lane_index));                    return @splat(@as(Index, @intCast(found)));                }            }            return y;        }        pub fn x0Init(self: *@This()) Base.InputVector {            return @splat(otherValue(T, self.needle));        }        pub fn yInit(_: *@This()) Base.OutputVector {            return @splat(-1);        }        pub fn maskLoad(            self: *@This(),            index: isize,            input: []const T,            places: isize,        ) Base.InputVector {            return Base.maskLoadOr(@splat(otherValue(T, self.needle)), index, input, places);        }        pub fn storeAndShortCircuit(            _: *@This(),            _: isize,            output: []Index,            value: Base.OutputVector,        ) bool {            std.debug.assert(output.len >= 1);            output[0] = value[0];            return value[0] == -1;        }        pub fn maskStore(            _: *@This(),            _: isize,            output: []Index,            value: Base.OutputVector,            _: isize,        ) usize {            std.debug.assert(output.len >= 1);            output[0] = value[0];            return 1;        }    };}const TraceUnit = struct {    x0_inits: usize = 0,    y_inits: usize = 0,    loads: usize = 0,    mask_loads: usize = 0,    funcs: usize = 0,    stores: usize = 0,    mask_stores: usize = 0,    unrolled_reductions: usize = 0,    final_reductions: usize = 0,    last_places: isize = 0,    pub const Base: type = UnrollerUnit(i32, i32);    pub fn x0Init(self: *@This()) Base.InputVector {        self.x0_inits += 1;        return Base.x0Init();    }    pub fn yInit(self: *@This()) Base.OutputVector {        self.y_inits += 1;        return Base.yInit();    }    pub fn load(self: *@This(), index: isize, input: []const i32) Base.InputVector {        self.loads += 1;        return Base.load(index, input);    }    pub fn maskLoad(        self: *@This(),        index: isize,        input: []const i32,        places: isize,    ) Base.InputVector {        self.mask_loads += 1;        self.last_places = places;        return Base.maskLoad(index, input, places);    }    pub fn func(        self: *@This(),        _: isize,        x: Base.InputVector,        _: Base.OutputVector,    ) Base.OutputVector {        self.funcs += 1;        return x;    }    pub fn storeAndShortCircuit(        self: *@This(),        index: isize,        output: []i32,        value: Base.OutputVector,    ) bool {        self.stores += 1;        return Base.storeAndShortCircuit(index, output, value);    }    pub fn maskStore(        self: *@This(),        index: isize,        output: []i32,        value: Base.OutputVector,        places: isize,    ) usize {        self.mask_stores += 1;        self.last_places = places;        return Base.maskStore(index, output, value, places);    }    pub fn reduceUnrolled(        self: *@This(),        x0: Base.OutputVector,        x1: Base.OutputVector,        x2: Base.OutputVector,        value: Base.OutputVector,    ) Base.OutputVector {        self.unrolled_reductions += 1;        return Base.reduceUnrolled(x0, x1, x2, value);    }    pub fn reduceFinal(        self: *@This(),        value: Base.OutputVector,        output: []i32,    ) usize {        self.final_reductions += 1;        return Base.reduceFinal(value, output);    }};const StopUnit = struct {    funcs: usize = 0,    stores: usize = 0,    final_reductions: usize = 0,    pub const Base: type = UnrollerUnit(i32, i32);    pub fn func(        self: *@This(),        _: isize,        x: Base.InputVector,        _: Base.OutputVector,    ) Base.OutputVector {        self.funcs += 1;        return x;    }    pub fn storeAndShortCircuit(        self: *@This(),        _: isize,        _: []i32,        _: Base.OutputVector,    ) bool {        self.stores += 1;        return false;    }    pub fn reduceFinal(        self: *@This(),        _: Base.OutputVector,        _: []i32,    ) usize {        self.final_reductions += 1;        return 0;    }};fn counts(lane_count: usize) [14]usize {    return .{        1,        3,        7,        16,        @max(lane_count / 2, 1),        @max(2 * lane_count / 3, 1),        lane_count,        lane_count + 1,        4 * lane_count / 3,        3 * lane_count,        8 * lane_count,        8 * lane_count + 2,        256 * lane_count - 1,        256 * lane_count,    };}fn verifyDotAndMin(comptime T: type) !void {    const Base = DotUnit(T).Base;    const max_values = 256 * Base.actual_lanes;    var a: [max_values]T = undefined;    var b: [max_values]T = undefined;    var products: [max_values]T = undefined;    for (counts(Base.actual_lanes)) |count| {        var expected_dot_f64: f64 = 0;        var expected_min = std.math.floatMax(T);        for (a[0..count], b[0..count], 0..) |*a_value, *b_value, index| {            const a_integer = @as(i32, @intCast(index * 37 % 1_024)) - 512;            const b_integer = @as(i32, @intCast(index * 53 % 1_024)) - 512;            a_value.* = @as(T, @floatFromInt(a_integer)) * @as(T, 1.0 / 64.0);            b_value.* = @as(T, @floatFromInt(b_integer)) * @as(T, 1.0 / 64.0);            expected_dot_f64 += @as(f64, @floatCast(a_value.*)) *                @as(f64, @floatCast(b_value.*));            expected_min = @min(expected_min, a_value.*);        }        const expected_dot: T = @floatCast(expected_dot_f64);        expected_dot_f64 = @floatCast(expected_dot);        var multiply = MultiplyUnit(T){};        unroll2(&multiply, a[0..count], b[0..count], products[0..count]);        var accumulate = AccumulateUnit(T){};        var via_multiply: [1]T = undefined;        unroll(&accumulate, products[0..count], &via_multiply);        var dot = DotUnit(T){};        var direct: [1]T = undefined;        unroll2(&dot, a[0..count], b[0..count], &direct);        const tolerance = 120.0 * @as(f64, @floatCast(std.math.floatEps(T))) *            @abs(expected_dot_f64);        const multiply_error = @abs(            expected_dot_f64 - @as(f64, @floatCast(via_multiply[0])),        );        const direct_error = @abs(expected_dot_f64 - @as(f64, @floatCast(direct[0])));        try std.testing.expect(multiply_error <= tolerance);        try std.testing.expect(direct_error <= tolerance);        var minimum = MinUnit(T){};        var actual_min: [1]T = undefined;        unroll(&minimum, a[0..count], &actual_min);        try std.testing.expectEqual(expected_min, actual_min[0]);    }}fn verifyConvert(comptime T: type) !void {    const Base = ConvertUnit(T, i32).Base;    const max_values = 256 * Base.actual_lanes;    var input: [max_values]T = undefined;    var integers: [max_values]i32 = undefined;    var round_trip: [max_values]T = undefined;    for (counts(Base.actual_lanes)) |count| {        for (input[0..count], 0..) |*value, index| {            value.* = @as(T, @floatFromInt(index)) * @as(T, 0.25);        }        var to_integer = ConvertUnit(T, i32){};        unroll(&to_integer, input[0..count], integers[0..count]);        for (input[0..count], integers[0..count]) |value, integer| {            try std.testing.expectEqual(@as(i32, @intFromFloat(value)), integer);        }        var to_float = ConvertUnit(i32, T){};        unroll(&to_float, integers[0..count], round_trip[0..count]);        for (integers[0..count], round_trip[0..count]) |integer, value| {            try std.testing.expectEqual(@as(T, @floatFromInt(integer)), value);        }    }}fn verifyFind(comptime T: type) !void {    const Unit = FindUnit(T);    const Index = Unit.Base.Output;    const max_values = 256 * Unit.Base.actual_lanes;    var input: [max_values]T = undefined;    for (counts(Unit.Base.actual_lanes)) |count| {        for (input[0..count], 0..) |*value, index| value.* = @floatFromInt(index);        var found: [1]Index = undefined;        var last = Unit{ .needle = @floatFromInt(count - 1) };        unroll(&last, input[0..count], &found);        try std.testing.expect(found[0] >= 0);        try std.testing.expectEqual(last.needle, input[@intCast(found[0])]);        var zero = Unit{ .needle = 0 };        unroll(&zero, input[0..count], &found);        try std.testing.expectEqual(@as(Index, 0), found[0]);        var absent = Unit{ .needle = std.math.floatMax(T) };        unroll(&absent, input[0..count], &found);        try std.testing.expectEqual(@as(Index, -1), found[0]);    }}fn addVectors(comptime T: type, a: anytype, b: @TypeOf(a)) @TypeOf(a) {    return if (comptime @typeInfo(T) == .int) a +% b else a + b;}fn multiplyVectors(comptime T: type, a: anytype, b: @TypeOf(a)) @TypeOf(a) {    return if (comptime @typeInfo(T) == .int) a *% b else a * b;}fn sumVector(comptime T: type, value: anytype) T {    var result: T = 0;    inline for (0..@typeInfo(@TypeOf(value)).vector.len) |index| {        result = if (comptime @typeInfo(T) == .int)            result +% value[index]        else            result + value[index];    }    return result;}fn minVector(comptime T: type, value: anytype) T {    var result = value[0];    inline for (1..@typeInfo(@TypeOf(value)).vector.len) |index| {        result = @min(result, value[index]);    }    return result;}fn highestValue(comptime T: type) T {    return switch (@typeInfo(T)) {        .int => std.math.maxInt(T),        .float => std.math.floatMax(T),        else => unreachable,    };}fn otherValue(comptime T: type, value: T) T {    return switch (@typeInfo(T)) {        .int => value +% 1,        .float => std.math.nan(T),        else => unreachable,    };}fn convertScalar(comptime To: type, value: anytype) To {    return switch (@typeInfo(@TypeOf(value))) {        .int => switch (@typeInfo(To)) {            .int => @intCast(value),            .float => @floatFromInt(value),            else => unreachable,        },        .float => switch (@typeInfo(To)) {            .int => @intFromFloat(value),            .float => @floatCast(value),            else => unreachable,        },        else => unreachable,    };}test "Highway unroller descriptors use the largest lane size" {    const One = UnrollerUnit(u8, f64);    const Two = UnrollerUnit2D(f16, u32, f64);    try std.testing.expectEqual(tag.ScalableTag(i64).lane_count, One.actual_lanes);    try std.testing.expectEqual(One.actual_lanes, One.InputTag.lane_count);    try std.testing.expectEqual(One.actual_lanes, One.OutputTag.lane_count);    try std.testing.expectEqual(One.max_unit_lanes, One.maxUnitLanes());    try std.testing.expectEqual(tag.ScalableTag(i64).lane_count, Two.actual_lanes);    try std.testing.expectEqual(Two.actual_lanes, Two.Input0Tag.lane_count);    try std.testing.expectEqual(Two.actual_lanes, Two.Input1Tag.lane_count);    try std.testing.expectEqual(Two.actual_lanes, Two.OutputTag.lane_count);}test "Highway unroller ports multiply dot sum and minimum sweeps" {    inline for (.{ f16, f32, f64 }) |T| try verifyDotAndMin(T);}test "Highway unroller ports widening narrowing and find sweeps" {    inline for (.{ f16, f32, f64 }) |T| {        try verifyConvert(T);        try verifyFind(T);    }}test "Highway unroller invokes hooks across four-way and overlapping tails" {    const lanes = TraceUnit.Base.actual_lanes;    const tail_count = @min(2, lanes - 1);    const has_tail: usize = @intFromBool(tail_count != 0);    const count = 5 * lanes + tail_count;    var input: [count]i32 = undefined;    var output: [input.len]i32 = undefined;    for (&input, 0..) |*value, index| value.* = @intCast(index * 7 + 3);    var trace = TraceUnit{};    unroll(&trace, &input, &output);    try std.testing.expectEqualSlices(i32, &input, &output);    try std.testing.expectEqual(@as(usize, 4), trace.x0_inits);    try std.testing.expectEqual(@as(usize, 4), trace.y_inits);    try std.testing.expectEqual(@as(usize, 5), trace.loads);    try std.testing.expectEqual(has_tail, trace.mask_loads);    try std.testing.expectEqual(5 + has_tail, trace.funcs);    try std.testing.expectEqual(@as(usize, 5), trace.stores);    try std.testing.expectEqual(has_tail, trace.mask_stores);    try std.testing.expectEqual(@as(usize, 1), trace.unrolled_reductions);    try std.testing.expectEqual(@as(usize, 1), trace.final_reductions);    try std.testing.expectEqual(-@as(isize, @intCast(tail_count)), trace.last_places);    try std.testing.expectEqual(count, output.len);}test "Highway unroller handles small spans and short circuits before reduction" {    var empty_input: [0]i32 = .{};    var empty_output: [0]i32 = .{};    var empty_trace = TraceUnit{};    unroll(&empty_trace, &empty_input, &empty_output);    try std.testing.expectEqual(@as(usize, 1), empty_trace.funcs);    try std.testing.expectEqual(@as(usize, 1), empty_trace.mask_loads);    try std.testing.expectEqual(@as(usize, 1), empty_trace.mask_stores);    try std.testing.expectEqual(@as(usize, 1), empty_trace.final_reductions);    var sum = AccumulateUnit(i32){};    var empty_sum = [_]i32{99};    unroll(&sum, &empty_input, &empty_sum);    try std.testing.expectEqual(@as(i32, 0), empty_sum[0]);    const small_count = @min(3, TraceUnit.Base.actual_lanes - 1);    var small_input: [small_count]i32 = undefined;    var small_output: [small_count]i32 = @splat(0);    for (&small_input, 0..) |*value, index| value.* = @intCast(index + 4);    var trace = TraceUnit{};    unroll(&trace, &small_input, &small_output);    try std.testing.expectEqualSlices(i32, &small_input, &small_output);    try std.testing.expectEqual(@as(usize, 1), trace.x0_inits);    try std.testing.expectEqual(@as(usize, 1), trace.y_inits);    try std.testing.expectEqual(@as(usize, 0), trace.loads);    try std.testing.expectEqual(@as(usize, 1), trace.mask_loads);    try std.testing.expectEqual(@as(usize, 1), trace.mask_stores);    try std.testing.expectEqual(@as(isize, @intCast(small_count)), trace.last_places);    const count = 5 * StopUnit.Base.actual_lanes;    var input: [count]i32 = @splat(1);    var output: [count]i32 = @splat(0);    var stop = StopUnit{};    unroll(&stop, &input, &output);    try std.testing.expectEqual(@as(usize, 4), stop.funcs);    try std.testing.expectEqual(@as(usize, 1), stop.stores);    try std.testing.expectEqual(@as(usize, 0), stop.final_reductions);}

Audit

Definitions1
Public names1
Members0
Version26.7.0
Revisiondaab053ee433