tiny.simd.print
Defined in tiny.simd.
API (5)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: lib/simd/src/print.zig
zig
const std = @import("std");const bfloat = @import("bfloat.zig");pub const Window = struct { lane: usize = 0, max_lanes: usize = 7,};const Bounds = struct { begin: usize, end: usize,};const Big = struct { limbs: [40]u32 = @splat(0), len: usize = 0, fn init(value: u128) Big { var result = Big{}; var remaining = value; while (remaining != 0) { result.limbs[result.len] = @truncate(remaining); result.len += 1; remaining >>= 32; } return result; } fn multiplySmall(self: *Big, factor: u32) void { var carry: u64 = 0; for (self.limbs[0..self.len]) |*limb| { const product = @as(u64, limb.*) * factor + carry; limb.* = @truncate(product); carry = product >> 32; } if (carry != 0) { std.debug.assert(self.len < self.limbs.len); self.limbs[self.len] = @truncate(carry); self.len += 1; } } fn multiplyPower5(self: *Big, count: usize) void { for (0..count) |_| self.multiplySmall(5); } fn shiftLeft(self: *Big, amount: usize) void { if (self.len == 0 or amount == 0) return; const words = amount / 32; const bits: u5 = @intCast(amount % 32); std.debug.assert(self.len + words + @intFromBool(bits != 0) <= self.limbs.len); var index = self.len; while (index != 0) { index -= 1; self.limbs[index + words] = self.limbs[index]; } @memset(self.limbs[0..words], 0); self.len += words; if (bits == 0) return; var carry: u64 = 0; for (self.limbs[0..self.len]) |*limb| { const shifted = (@as(u64, limb.*) << bits) | carry; limb.* = @truncate(shifted); carry = shifted >> 32; } if (carry != 0) { self.limbs[self.len] = @truncate(carry); self.len += 1; } } fn shiftedU128(self: *const Big, shift: usize) u128 { var result: u128 = 0; for (0..4) |word| { result |= @as(u128, self.wordAt(shift + word * 32)) << @intCast(word * 32); } return result; } fn roundedShift(self: *const Big, shift: usize) u128 { if (shift == 0) return self.shiftedU128(0); var quotient = self.shiftedU128(shift); const half = self.bitAt(shift - 1); if (half and (self.anyBelow(shift - 1) or quotient & 1 != 0)) quotient += 1; return quotient; } fn wordAt(self: *const Big, bit: usize) u32 { const word = bit / 32; if (word >= self.len) return 0; const offset: u5 = @intCast(bit % 32); if (offset == 0) return self.limbs[word]; var result = self.limbs[word] >> offset; if (word + 1 < self.len) { const remaining: u5 = @intCast(32 - @as(u6, offset)); result |= self.limbs[word + 1] << remaining; } return result; } fn bitAt(self: *const Big, bit: usize) bool { const word = bit / 32; if (word >= self.len) return false; const offset: u5 = @intCast(bit % 32); return self.limbs[word] & (@as(u32, 1) << offset) != 0; } fn anyBelow(self: *const Big, bit: usize) bool { const words = @min(bit / 32, self.len); for (self.limbs[0..words]) |limb| if (limb != 0) return true; const remaining: u5 = @intCast(bit % 32); if (remaining == 0 or words >= self.len) return false; const mask = (@as(u32, 1) << remaining) - 1; return self.limbs[words] & mask != 0; } fn divideSmall(self: *Big, divisor: u32) u32 { var remainder: u64 = 0; var index = self.len; while (index != 0) { index -= 1; const value = (remainder << 32) | self.limbs[index]; self.limbs[index] = @intCast(value / divisor); remainder = value % divisor; } while (self.len != 0 and self.limbs[self.len - 1] == 0) self.len -= 1; return @intCast(remainder); }};const FloatParts = struct { negative: bool, mantissa: u64, exponent: i32, special: enum { finite, infinity, nan },};pub fn writeTypeName( writer: *std.Io.Writer, comptime T: type, lane_count: usize,) std.Io.Writer.Error!void { validateType(T); try writer.print("{c}{d}", .{ typePrefix(T), typeBits(T) }); if (lane_count != 1) try writer.print("x{d}", .{lane_count});}pub fn writeValue( writer: *std.Io.Writer, value: anytype,) std.Io.Writer.Error!void { const T = @TypeOf(value); validateType(T); if (T == bfloat.BFloat16) { return writeFloat(writer, @floatCast(value.toF32()), 3, 1e-3); } switch (@typeInfo(T)) { .int => |info| switch (info.bits) { 8 => if (info.signedness == .signed) try writer.print("{d}", .{value}) else try writer.print("0x{X:0>2}", .{value}), 16 => try writer.print("0x{X:0>4}", .{@as(u16, @bitCast(value))}), 32 => try writer.print("{d}", .{value}), 64 => try writer.print("0x{x:0>16}", .{@as(u64, @bitCast(value))}), 128 => { const bits: u128 = @bitCast(value); try writer.print( "0x{x:0>16}_{x:0>16}", .{ @as(u64, @truncate(bits >> 64)), @as(u64, @truncate(bits)) }, ); }, else => unreachable, }, .float => |info| switch (info.bits) { 16 => try writeFloat(writer, @floatCast(value), 4, 1e-4), 32 => try writeFloat( writer, @floatCast(value), 9, @as(f64, @floatCast(@as(f32, 1e-6))), ), 64 => try writeFloat(writer, value, 18, 1e-9), else => unreachable, }, else => unreachable, }}pub fn writeArray( writer: *std.Io.Writer, caption: []const u8, values: anytype, options: Window,) std.Io.Writer.Error!void { const Slice = @TypeOf(values); const pointer = switch (@typeInfo(Slice)) { .pointer => |info| info, else => @compileError("diagnostic arrays require a slice or array pointer"), }; if (pointer.size != .slice and pointer.size != .one) { @compileError("diagnostic arrays require a slice or array pointer"); } const T = switch (@typeInfo(pointer.child)) { .array => |info| info.child, else => pointer.child, }; validateType(T); const slice: []const T = values; const bounds = try writeHeader(writer, T, caption, slice.len, options); for (slice[bounds.begin..bounds.end]) |value| { try writeValue(writer, value); try writer.writeByte(','); } try writeFooter(writer, bounds);}pub fn writeVector( writer: *std.Io.Writer, comptime D: type, caption: []const u8, value: D.Vector, options: Window,) std.Io.Writer.Error!void { const is_bfloat = @hasDecl(D, "is_bfloat16") and D.is_bfloat16; const T = if (is_bfloat) bfloat.BFloat16 else D.Lane; validateType(T); const bounds = try writeHeader(writer, T, caption, D.lane_count, options); const lanes: [D.lane_count]D.Lane = @bitCast(value); for (bounds.begin..bounds.end) |index| { if (is_bfloat) { try writeValue(writer, bfloat.BFloat16.fromBits(lanes[index])); } else { try writeValue(writer, lanes[index]); } try writer.writeByte(','); } try writeFooter(writer, bounds);}fn writeHeader( writer: *std.Io.Writer, comptime T: type, caption: []const u8, lane_count: usize, options: Window,) std.Io.Writer.Error!Bounds { const begin = options.lane -| 2; const end = @min(begin +| options.max_lanes, lane_count); try writeTypeName(writer, T, lane_count); try writer.print(" {s} [{d}+ ->]:\n ", .{ caption, begin }); return .{ .begin = @min(begin, lane_count), .end = end };}fn writeFooter(writer: *std.Io.Writer, bounds: Bounds) std.Io.Writer.Error!void { if (bounds.begin >= bounds.end) try writer.writeAll("(out of bounds)"); try writer.writeByte('\n');}fn writeFloat( writer: *std.Io.Writer, value: f64, comptime precision: usize, threshold: f64,) std.Io.Writer.Error!void { var buffer: [400]u8 = undefined; const text = if (@abs(value) < threshold) renderScientific(&buffer, value, precision) else renderFixed(&buffer, value, precision); try writer.writeAll(text[0..@min(text.len, 99)]);}fn renderScientific( buffer: []u8, value: f64, comptime precision: usize,) []const u8 { const parts = decompose(value); if (parts.special != .finite) return renderSpecial(buffer, parts); var exponent: i32 = if (parts.mantissa == 0) 0 else decimalExponent(value); var significand = scientificSignificand(parts, precision, exponent); const lower = power10(precision); const upper = lower * 10; if (parts.mantissa != 0 and significand < lower) { exponent -= 1; significand = scientificSignificand(parts, precision, exponent); } if (significand >= upper) { significand /= 10; exponent += 1; } var writer = std.Io.Writer.fixed(buffer); if (parts.negative) writer.writeByte('-') catch unreachable; var digits: [19]u8 = undefined; var remaining = significand; var index = precision + 1; while (index != 0) { index -= 1; digits[index] = '0' + @as(u8, @intCast(remaining % 10)); remaining /= 10; } writer.writeByte(digits[0]) catch unreachable; if (precision != 0) { writer.writeByte('.') catch unreachable; writer.writeAll(digits[1 .. precision + 1]) catch unreachable; } writer.writeByte('E') catch unreachable; if (exponent < 0) { writer.writeByte('-') catch unreachable; } else { writer.writeByte('+') catch unreachable; } writer.print("{d:0>2}", .{@abs(exponent)}) catch unreachable; return writer.buffered();}fn renderFixed( buffer: []u8, value: f64, comptime precision: usize,) []const u8 { const parts = decompose(value); if (parts.special != .finite) return renderSpecial(buffer, parts); var scaled = Big.init(parts.mantissa); scaled.multiplyPower5(precision); const binary_exponent = parts.exponent + @as(i32, @intCast(precision)); if (binary_exponent >= 0) { scaled.shiftLeft(@intCast(binary_exponent)); } else { scaled = Big.init(scaled.roundedShift(@intCast(-binary_exponent))); } var digits_buffer: [400]u8 = undefined; const digits = renderBigDecimal(&digits_buffer, scaled); var writer = std.Io.Writer.fixed(buffer); if (parts.negative) writer.writeByte('-') catch unreachable; if (precision == 0) { writer.writeAll(digits) catch unreachable; } else if (digits.len > precision) { const point = digits.len - precision; writer.writeAll(digits[0..point]) catch unreachable; writer.writeByte('.') catch unreachable; writer.writeAll(digits[point..]) catch unreachable; } else { writer.writeAll("0.") catch unreachable; for (0..precision - digits.len) |_| writer.writeByte('0') catch unreachable; writer.writeAll(digits) catch unreachable; } return writer.buffered();}fn renderSpecial(buffer: []u8, parts: FloatParts) []const u8 { var writer = std.Io.Writer.fixed(buffer); if (parts.negative) writer.writeByte('-') catch unreachable; writer.writeAll(if (parts.special == .nan) "nan" else "inf") catch unreachable; return writer.buffered();}fn renderBigDecimal(buffer: []u8, value: Big) []const u8 { var remaining = value; if (remaining.len == 0) { buffer[0] = '0'; return buffer[0..1]; } var chunks: [40]u32 = undefined; var count: usize = 0; while (remaining.len != 0) { chunks[count] = remaining.divideSmall(1_000_000_000); count += 1; } var writer = std.Io.Writer.fixed(buffer); writer.print("{d}", .{chunks[count - 1]}) catch unreachable; var index = count - 1; while (index != 0) { index -= 1; writer.print("{d:0>9}", .{chunks[index]}) catch unreachable; } return writer.buffered();}fn scientificSignificand( parts: FloatParts, comptime precision: usize, decimal_exponent: i32,) u128 { if (parts.mantissa == 0) return 0; const decimal_shift = @as(i32, @intCast(precision)) - decimal_exponent; std.debug.assert(decimal_shift >= 0); var scaled = Big.init(parts.mantissa); scaled.multiplyPower5(@intCast(decimal_shift)); const binary_exponent = parts.exponent + decimal_shift; if (binary_exponent >= 0) { scaled.shiftLeft(@intCast(binary_exponent)); return scaled.shiftedU128(0); } return scaled.roundedShift(@intCast(-binary_exponent));}fn decimalExponent(value: f64) i32 { var normalized: f128 = @floatCast(@abs(value)); var exponent: i32 = 0; while (normalized >= 10) { normalized /= 10; exponent += 1; } while (normalized < 1) { normalized *= 10; exponent -= 1; } return exponent;}fn decompose(value: f64) FloatParts { const bits: u64 = @bitCast(value); const exponent_bits: u11 = @truncate(bits >> 52); const fraction = bits & 0x000f_ffff_ffff_ffff; const negative = bits >> 63 != 0; if (exponent_bits == 0x7ff) { return .{ .negative = negative, .mantissa = fraction, .exponent = 0, .special = if (fraction == 0) .infinity else .nan, }; } if (exponent_bits == 0) { return .{ .negative = negative, .mantissa = fraction, .exponent = -1074, .special = .finite, }; } return .{ .negative = negative, .mantissa = fraction | (@as(u64, 1) << 52), .exponent = @as(i32, exponent_bits) - 1023 - 52, .special = .finite, };}fn power10(comptime exponent: usize) u128 { var result: u128 = 1; for (0..exponent) |_| result *= 10; return result;}fn typePrefix(comptime T: type) u8 { if (T == bfloat.BFloat16) return 'i'; return switch (@typeInfo(T)) { .float => 'f', .int => |info| if (info.signedness == .signed) 'i' else 'u', else => unreachable, };}fn typeBits(comptime T: type) usize { return if (T == bfloat.BFloat16) 16 else @bitSizeOf(T);}fn validateType(comptime T: type) void { if (T == bfloat.BFloat16) return; switch (@typeInfo(T)) { .int => |info| { if (info.bits != 8 and info.bits != 16 and info.bits != 32 and info.bits != 64 and info.bits != 128) { @compileError("diagnostic integers require 8/16/32/64/128 bits"); } if (info.bits == 128 and info.signedness == .signed) { @compileError("Highway diagnostics only support unsigned 128-bit lanes"); } }, .float => |info| if (info.bits != 16 and info.bits != 32 and info.bits != 64) { @compileError("diagnostic floats require 16/32/64 bits"); }, else => @compileError("unsupported Highway diagnostic lane type"), }}test "pinned Highway diagnostic type and value oracle matches byte-for-byte" { var buffer: [2048]u8 = undefined; var writer = std.Io.Writer.fixed(&buffer); const cases = .{ .{ "u8", @as(u8, 0xaf), @as(usize, 8) }, .{ "i8", @as(i8, -123), @as(usize, 1) }, .{ "u16", @as(u16, 0xabcd), @as(usize, 4) }, .{ "i16", @as(i16, -2), @as(usize, 4) }, .{ "f16-small", @as(f16, @bitCast(@as(u16, 1))), @as(usize, 4) }, .{ "f16-fixed", @as(f16, 1.5), @as(usize, 4) }, .{ "bf16-small", bfloat.BFloat16.fromBits(1), @as(usize, 4) }, .{ "bf16-fixed", bfloat.BFloat16.fromF32(-2.25), @as(usize, 4) }, .{ "u32", @as(u32, std.math.maxInt(u32)), @as(usize, 8) }, .{ "i32", @as(i32, std.math.minInt(i32)), @as(usize, 8) }, .{ "f32-small", @as(f32, 0.0000005), @as(usize, 8) }, .{ "f32-fixed", @as(f32, -12.25), @as(usize, 8) }, .{ "f32-tenth", @as(f32, 0.1), @as(usize, 8) }, .{ "u64", @as(u64, 0x0123_4567_89ab_cdef), @as(usize, 2) }, .{ "i64", @as(i64, -2), @as(usize, 2) }, .{ "f64-small", @as(f64, 0.0000000005), @as(usize, 2) }, .{ "f64-fixed", @as(f64, -12.25), @as(usize, 2) }, .{ "f64-tenth", @as(f64, 0.1), @as(usize, 2) }, .{ "f64-max", @as(f64, 0x1.fffffffffffffp1023), @as(usize, 2) }, .{ "u128", @as(u128, 0xfedc_ba98_7654_3210_0123_4567_89ab_cdef), @as(usize, 2) }, }; inline for (cases) |case| { try writer.print("{s} type=", .{case[0]}); try writeTypeName(&writer, @TypeOf(case[1]), case[2]); try writer.writeAll(" value="); try writeValue(&writer, case[1]); try writer.writeByte('\n'); } try std.testing.expectEqualStrings( \\u8 type=u8x8 value=0xAF \\i8 type=i8 value=-123 \\u16 type=u16x4 value=0xABCD \\i16 type=i16x4 value=0xFFFE \\f16-small type=f16x4 value=5.9605E-08 \\f16-fixed type=f16x4 value=1.5000 \\bf16-small type=i16x4 value=9.184E-41 \\bf16-fixed type=i16x4 value=-2.250 \\u32 type=u32x8 value=4294967295 \\i32 type=i32x8 value=-2147483648 \\f32-small type=f32x8 value=4.999999987E-07 \\f32-fixed type=f32x8 value=-12.250000000 \\f32-tenth type=f32x8 value=0.100000001 \\u64 type=u64x2 value=0x0123456789abcdef \\i64 type=i64x2 value=0xfffffffffffffffe \\f64-small type=f64x2 value=5.000000000000000311E-10 \\f64-fixed type=f64x2 value=-12.250000000000000000 \\f64-tenth type=f64x2 value=0.100000000000000006 \\f64-max type=f64x2 value=179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878 \\u128 type=u128x2 value=0xfedcba9876543210_0123456789abcdef \\ , writer.buffered(), );}test "pinned Highway diagnostic lane windows match byte-for-byte" { const values = [_]u32{ 10, 11, 12, 13, 14, 15, 16, 17 }; var buffer: [512]u8 = undefined; var writer = std.Io.Writer.fixed(&buffer); try writeArray(&writer, "lanes", &values, .{ .lane = 4, .max_lanes = 5 }); try writeArray(&writer, "oob", &values, .{ .lane = 99 }); try writeArray(&writer, "zero", &values, .{ .max_lanes = 0 }); try std.testing.expectEqualStrings( \\u32x8 lanes [2+ ->]: \\ 12,13,14,15,16, \\u32x8 oob [97+ ->]: \\ (out of bounds) \\u32x8 zero [0+ ->]: \\ (out of bounds) \\ , writer.buffered(), );}test "vector diagnostics preserve array order and bfloat meaning" { const tag = @import("tag.zig"); const D = tag.FixedTag(i16, 8); const values = [_]i16{ -4, -3, -2, -1, 0, 1, 2, 3 }; const vector: D.Vector = values; var array_buffer: [256]u8 = undefined; var array_writer = std.Io.Writer.fixed(&array_buffer); try writeArray(&array_writer, "vector", &values, .{ .lane = 5, .max_lanes = 4 }); var vector_buffer: [256]u8 = undefined; var vector_writer = std.Io.Writer.fixed(&vector_buffer); try writeVector(&vector_writer, D, "vector", vector, .{ .lane = 5, .max_lanes = 4 }); try std.testing.expectEqualStrings(array_writer.buffered(), vector_writer.buffered()); const BD = bfloat.Tag(4); const bvalues = [_]bfloat.BFloat16{ bfloat.BFloat16.fromF32(1), bfloat.BFloat16.fromF32(-2.25), bfloat.BFloat16.fromBits(1), bfloat.BFloat16.fromF32(4), }; const bvector = bfloat.load(BD, &bvalues); var bbuffer: [256]u8 = undefined; var bwriter = std.Io.Writer.fixed(&bbuffer); try writeVector(&bwriter, BD, "bf16", bvector, .{ .max_lanes = 4 }); try std.testing.expectEqualStrings( "i16x4 bf16 [0+ ->]:\n 1.000,-2.250,9.184E-41,4.000,\n", bwriter.buffered(), );}test "diagnostic boundaries retain scientific thresholds and empty slices" { var buffer: [512]u8 = undefined; var writer = std.Io.Writer.fixed(&buffer); try writeValue(&writer, @as(f32, -0.0)); try writer.writeByte(' '); try writeValue(&writer, @as(f32, @bitCast(@as(u32, 0x3586_37bc)))); try writer.writeByte(' '); try writeValue(&writer, @as(f32, @bitCast(@as(u32, 0x3586_37bd)))); try writer.writeByte(' '); try writeValue(&writer, @as(f32, @bitCast(@as(u32, 0x3586_37be)))); try writer.writeByte(' '); try writeValue(&writer, std.math.inf(f64)); try writer.writeByte(' '); try writeValue(&writer, std.math.nan(f64)); try writer.writeByte('\n'); try writeArray(&writer, "empty", @as([]const u8, &.{}), .{}); try std.testing.expectEqualStrings( "-0.000000000E+00 9.999998838E-07 0.000001000 0.000001000 inf nan\nu8x0 empty [0+ ->]:\n (out of bounds)\n", writer.buffered(), );}Source: lib/simd/src/root.zig:23
zig
pub const print = @import("print.zig");Audit
| Definitions | 1 |
|---|---|
| Public names | 1 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |