mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 02:44:43 +01:00
Merge pull request 'Optimize {write,take}Leb128' (#30012) from NicoElbers/zig:leb-perf into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30012 Reviewed-by: Andrew Kelley <andrewrk@noreply.codeberg.org>
This commit is contained in:
commit
6bf9499c0c
2 changed files with 469 additions and 119 deletions
|
|
@ -1271,38 +1271,113 @@ pub fn takeEnumNonexhaustive(r: *Reader, comptime Enum: type, endian: std.builti
|
|||
pub const TakeLeb128Error = Error || error{Overflow};
|
||||
|
||||
/// Read a single LEB128 value as type T, or `error.Overflow` if the value cannot fit.
|
||||
pub fn takeLeb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
|
||||
const result_info = @typeInfo(Result).int;
|
||||
return std.math.cast(Result, try r.takeMultipleOf7Leb128(@Int(
|
||||
result_info.signedness,
|
||||
std.mem.alignForwardAnyAlign(u16, result_info.bits, 7),
|
||||
))) orelse error.Overflow;
|
||||
}
|
||||
pub fn takeLeb128(r: *Reader, comptime T: type) TakeLeb128Error!T {
|
||||
const info = switch (@typeInfo(T)) {
|
||||
.int => |info| info,
|
||||
else => @compileError(@typeName(T) ++ " not supported"),
|
||||
};
|
||||
const Byte = packed struct { bits: u7, more: bool };
|
||||
|
||||
fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
|
||||
const result_info = @typeInfo(Result).int;
|
||||
comptime assert(result_info.bits % 7 == 0);
|
||||
var remaining_bits: std.math.Log2IntCeil(Result) = result_info.bits;
|
||||
const UnsignedResult = @Int(.unsigned, result_info.bits);
|
||||
var result: UnsignedResult = 0;
|
||||
var fits = true;
|
||||
while (true) {
|
||||
const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try r.peekGreedy(1));
|
||||
for (buffer, 1..) |byte, len| {
|
||||
if (remaining_bits > 0) {
|
||||
result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) |
|
||||
if (result_info.bits > 7) @shrExact(result, 7) else 0;
|
||||
remaining_bits -= 7;
|
||||
} else if (fits) fits = switch (result_info.signedness) {
|
||||
.signed => @as(i7, @bitCast(byte.bits)) ==
|
||||
@as(i7, @truncate(@as(Result, @bitCast(result)) >> (result_info.bits - 1))),
|
||||
.unsigned => byte.bits == 0,
|
||||
};
|
||||
if (byte.more) continue;
|
||||
r.toss(len);
|
||||
return if (fits) @as(Result, @bitCast(result)) >> remaining_bits else error.Overflow;
|
||||
if (info.bits <= 7) {
|
||||
var byte: Byte = undefined;
|
||||
const Bits = @Int(info.signedness, 7);
|
||||
|
||||
byte = @bitCast(try r.takeByte());
|
||||
const val = std.math.cast(T, @as(Bits, @bitCast(byte.bits))) orelse error.Overflow;
|
||||
|
||||
const allowed_bits: u7 = switch (info.signedness) {
|
||||
.unsigned => 0,
|
||||
.signed => @bitCast(@as(i7, @bitCast(byte.bits)) >> 6),
|
||||
};
|
||||
|
||||
var fits = true;
|
||||
while (byte.more) {
|
||||
byte = @bitCast(try r.takeByte());
|
||||
|
||||
if (byte.bits != allowed_bits) fits = false;
|
||||
}
|
||||
|
||||
return if (fits) blk: {
|
||||
@branchHint(.likely);
|
||||
break :blk val;
|
||||
} else error.Overflow;
|
||||
}
|
||||
|
||||
const Unsigned = @Int(.unsigned, info.bits);
|
||||
const UInt = std.math.ByteAlignedInt(Unsigned);
|
||||
const Int = std.math.ByteAlignedInt(T);
|
||||
|
||||
const uint_bits = @typeInfo(UInt).int.bits;
|
||||
|
||||
var byte: Byte = undefined;
|
||||
var val: UInt = 0;
|
||||
const max_bytes = @divFloor(info.bits - 1, 7) + 1;
|
||||
inline for (0..max_bytes) |iteration| {
|
||||
const shift = iteration * 7;
|
||||
|
||||
byte = @bitCast(try r.takeByte());
|
||||
|
||||
const extended: UInt = byte.bits;
|
||||
val |= extended << shift;
|
||||
|
||||
const bits_written = shift + 7;
|
||||
|
||||
if (bits_written >= info.bits) {
|
||||
const bits_overflowed = bits_written - info.bits;
|
||||
const bits_remaining = @mod(info.bits, 7);
|
||||
|
||||
const allowed_bits: u7, var fits: bool = switch (info.signedness) {
|
||||
.unsigned => blk: {
|
||||
const fits = bits_remaining == 0 or byte.bits >> bits_remaining == 0;
|
||||
|
||||
break :blk .{ 0, fits };
|
||||
},
|
||||
.signed => blk: {
|
||||
const bits: i7 = @bitCast(byte.bits);
|
||||
|
||||
// Move the sign bit into the MSB
|
||||
const shifted_bits: i7 = bits << bits_overflowed;
|
||||
|
||||
const value_sign: i7 = shifted_bits >> 6; // sign extends
|
||||
const bits_sign: i7 = bits >> bits_remaining; // sign extends
|
||||
|
||||
const fits = bits_remaining == 0 or bits_sign == value_sign;
|
||||
|
||||
if (uint_bits != info.bits and value_sign != 0) {
|
||||
const sign_extend_mask = @as(UInt, std.math.maxInt(UInt)) << info.bits;
|
||||
val |= sign_extend_mask;
|
||||
}
|
||||
|
||||
break :blk .{ @bitCast(value_sign), fits };
|
||||
},
|
||||
};
|
||||
|
||||
switch (info.signedness) {
|
||||
.signed => assert(allowed_bits == 0 or allowed_bits == 0x7F),
|
||||
.unsigned => comptime assert(allowed_bits == 0),
|
||||
}
|
||||
|
||||
while (byte.more) {
|
||||
byte = @bitCast(try r.takeByte());
|
||||
if (byte.bits != allowed_bits) fits = false;
|
||||
}
|
||||
|
||||
return if (fits) blk: {
|
||||
@branchHint(.likely);
|
||||
break :blk std.math.cast(T, @as(Int, @bitCast(val))) orelse error.Overflow;
|
||||
} else error.Overflow;
|
||||
}
|
||||
|
||||
comptime assert(bits_written < info.bits);
|
||||
if (!byte.more) {
|
||||
if (info.signedness == .signed and // can be negative
|
||||
byte.bits & 0x40 != 0) // is negative
|
||||
{
|
||||
const sign_extend_mask = @as(UInt, std.math.maxInt(UInt)) << bits_written;
|
||||
val |= sign_extend_mask;
|
||||
}
|
||||
return std.math.cast(T, @as(Int, @bitCast(val))) orelse error.Overflow;
|
||||
}
|
||||
r.toss(buffer.len);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1610,13 +1685,6 @@ test takeEnum {
|
|||
try testing.expectEqual(@as(E2, @enumFromInt(0x0001)), try r.takeEnum(E2, .big));
|
||||
}
|
||||
|
||||
test takeLeb128 {
|
||||
var r: Reader = .fixed("\xc7\x9f\x7f\x80");
|
||||
try testing.expectEqual(-12345, try r.takeLeb128(i64));
|
||||
try testing.expectEqual(0x80, try r.peekByte());
|
||||
try testing.expectError(error.EndOfStream, r.takeLeb128(i64));
|
||||
}
|
||||
|
||||
test readSliceShort {
|
||||
var r: Reader = .fixed("HelloFren");
|
||||
var buf: [5]u8 = undefined;
|
||||
|
|
@ -1978,90 +2046,245 @@ pub fn writableVector(r: *Reader, buffer: [][]u8, data: []const []u8) Error!stru
|
|||
}
|
||||
|
||||
test "deserialize signed LEB128" {
|
||||
// Small values
|
||||
try testing.expectEqual(5, testLeb128(i7, "\x05"));
|
||||
try testing.expectEqual(53, testLeb128(i64, "\x35"));
|
||||
|
||||
try testing.expectEqual(-6, testLeb128(i7, "\x7A"));
|
||||
try testing.expectEqual(-23, testLeb128(i64, "\x69"));
|
||||
|
||||
// Random values
|
||||
try testing.expectEqual(90, testLeb128(i8, "\xDA\x00"));
|
||||
try testing.expectEqual(3434, testLeb128(i16, "\xEA\x1A"));
|
||||
try testing.expectEqual(1505683543, testLeb128(i32, "\xD7\xD0\xFB\xCD\x05"));
|
||||
try testing.expectEqual(105721575804011595, testLeb128(i64, "\xCB\x88\x92\xD7\xE8\xA6\xE6\xBB\x01"));
|
||||
try testing.expectEqual(51316697993548595875823294343650416388, testLeb128(i128, "\x84\xAE\xAC\xFC\xE4\xA0\xCD\xE2\x87\xED\x83\xB2\x87\xAA\xA3\x9E\x9B\xCD\x00"));
|
||||
|
||||
try testing.expectEqual(-68, testLeb128(i8, "\xBC\x7F"));
|
||||
try testing.expectEqual(-20174, testLeb128(i16, "\xB2\xE2\x7E"));
|
||||
try testing.expectEqual(-166511141, testLeb128(i32, "\xDB\xFB\xCC\xB0\x7F"));
|
||||
try testing.expectEqual(-4368809844285451825, testLeb128(i64, "\xCF\xA3\x8B\xA1\xFF\xD2\xB7\xAF\x43"));
|
||||
try testing.expectEqual(-43250117698642799010758201165100952046, testLeb128(i128, "\x92\xAC\xDB\xA4\xEC\xDE\xB9\x95\xD1\xBA\xEC\xB0\xD7\x80\xA4\xAA\xF6\xBE\x7F"));
|
||||
|
||||
// {min,max} values
|
||||
try testing.expectEqual(std.math.maxInt(i8), testLeb128(i8, "\xFF\x00"));
|
||||
try testing.expectEqual(std.math.maxInt(i16), testLeb128(i16, "\xFF\xFF\x01"));
|
||||
try testing.expectEqual(std.math.maxInt(i32), testLeb128(i32, "\xFF\xFF\xFF\xFF\x07"));
|
||||
try testing.expectEqual(std.math.maxInt(i64), testLeb128(i64, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"));
|
||||
try testing.expectEqual(std.math.maxInt(i128), testLeb128(i128, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01"));
|
||||
|
||||
try testing.expectEqual(std.math.minInt(i8), testLeb128(i8, "\x80\x7F"));
|
||||
try testing.expectEqual(std.math.minInt(i16), testLeb128(i16, "\x80\x80\x7E"));
|
||||
try testing.expectEqual(std.math.minInt(i32), testLeb128(i32, "\x80\x80\x80\x80\x78"));
|
||||
try testing.expectEqual(std.math.minInt(i64), testLeb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7F"));
|
||||
try testing.expectEqual(std.math.minInt(i128), testLeb128(i128, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7E"));
|
||||
|
||||
// Specific cases
|
||||
try testing.expectEqual(0, testLeb128(i0, "\x00"));
|
||||
try testing.expectEqual(0, testLeb128(i2, "\x00"));
|
||||
try testing.expectEqual(0, testLeb128(i8, "\x00"));
|
||||
|
||||
try testing.expectEqual(1, testLeb128(i2, "\x01"));
|
||||
try testing.expectEqual(1, testLeb128(i8, "\x01"));
|
||||
|
||||
try testing.expectEqual(-1, testLeb128(i2, "\x7F"));
|
||||
try testing.expectEqual(-1, testLeb128(i8, "\x7F"));
|
||||
|
||||
const end_of_stream: [20]u8 = @splat(0x80);
|
||||
const overflow: [21]u8 = end_of_stream ++ .{0x01};
|
||||
const long_zero: [21]u8 = end_of_stream ++ .{0x00};
|
||||
const long_one: [22]u8 = .{0x81} ++ end_of_stream ++ .{0x00};
|
||||
const long_minus_one: [20]u8 = @as([19]u8, @splat(0xFF)) ++ .{0x7F};
|
||||
|
||||
// Truncated
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i64, "\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i16, "\x80\x80\x84\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i16, "\x80\x80\x80\x84\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i32, "\x80\x80\x80\x80\x90"));
|
||||
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i7, ""));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i8, ""));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i14, ""));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i128, ""));
|
||||
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i7, "\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i8, "\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i14, "\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i128, "\x80"));
|
||||
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i7, &end_of_stream));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i8, &end_of_stream));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i14, &end_of_stream));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(i128, &end_of_stream));
|
||||
|
||||
// Overflow
|
||||
try testing.expectError(error.Overflow, testLeb128(i0, "\x01"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i0, "\x7F"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i8, "\x80\x01"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i8, "\xFF\x7E"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i8, "\x80\x80\x40"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i16, "\x80\x80\x80\x40"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i32, "\x80\x80\x80\x80\x40"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x40"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i8, "\xff\x7e"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i32, "\x80\x80\x80\x80\x08"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i32, "\x80\x80\x80\x80\x40"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01"));
|
||||
try testing.expectError(error.Overflow, testLeb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x40"));
|
||||
|
||||
// Decode SLEB128
|
||||
try testing.expect((try testLeb128(i64, "\x00")) == 0);
|
||||
try testing.expect((try testLeb128(i64, "\x01")) == 1);
|
||||
try testing.expect((try testLeb128(i64, "\x3f")) == 63);
|
||||
try testing.expect((try testLeb128(i64, "\x40")) == -64);
|
||||
try testing.expect((try testLeb128(i64, "\x41")) == -63);
|
||||
try testing.expect((try testLeb128(i64, "\x7f")) == -1);
|
||||
try testing.expect((try testLeb128(i64, "\x80\x01")) == 128);
|
||||
try testing.expect((try testLeb128(i64, "\x81\x01")) == 129);
|
||||
try testing.expect((try testLeb128(i64, "\xff\x7e")) == -129);
|
||||
try testing.expect((try testLeb128(i64, "\x80\x7f")) == -128);
|
||||
try testing.expect((try testLeb128(i64, "\x81\x7f")) == -127);
|
||||
try testing.expect((try testLeb128(i64, "\xc0\x00")) == 64);
|
||||
try testing.expect((try testLeb128(i64, "\xc7\x9f\x7f")) == -12345);
|
||||
try testing.expect((try testLeb128(i8, "\xff\x7f")) == -1);
|
||||
try testing.expect((try testLeb128(i16, "\xff\xff\x7f")) == -1);
|
||||
try testing.expect((try testLeb128(i32, "\xff\xff\xff\xff\x7f")) == -1);
|
||||
try testing.expect((try testLeb128(i32, "\x80\x80\x80\x80\x78")) == -0x80000000);
|
||||
try testing.expect((try testLeb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7f")) == @as(i64, @bitCast(@as(u64, @intCast(0x8000000000000000)))));
|
||||
try testing.expect((try testLeb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x40")) == -0x4000000000000000);
|
||||
try testing.expect((try testLeb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7f")) == -0x8000000000000000);
|
||||
try testing.expectError(error.Overflow, testLeb128(i0, &overflow));
|
||||
try testing.expectError(error.Overflow, testLeb128(i7, &overflow));
|
||||
try testing.expectError(error.Overflow, testLeb128(i8, &overflow));
|
||||
try testing.expectError(error.Overflow, testLeb128(i14, &overflow));
|
||||
try testing.expectError(error.Overflow, testLeb128(i128, &overflow));
|
||||
|
||||
// Decode unnormalized SLEB128 with extra padding bytes.
|
||||
try testing.expect((try testLeb128(i64, "\x80\x00")) == 0);
|
||||
try testing.expect((try testLeb128(i64, "\x80\x80\x00")) == 0);
|
||||
try testing.expect((try testLeb128(i64, "\xff\x00")) == 0x7f);
|
||||
try testing.expect((try testLeb128(i64, "\xff\x80\x00")) == 0x7f);
|
||||
try testing.expect((try testLeb128(i64, "\x80\x81\x00")) == 0x80);
|
||||
try testing.expect((try testLeb128(i64, "\x80\x81\x80\x00")) == 0x80);
|
||||
// Extra padding
|
||||
try testing.expectEqual(-1, testLeb128(i32, "\xFF\xFF\xFF\xFF\x7F"));
|
||||
try testing.expectEqual(-1, testLeb128(i64, "\xFF\x7F"));
|
||||
try testing.expectEqual(0x7F, testLeb128(i64, "\xFF\x00"));
|
||||
try testing.expectEqual(0x7F, testLeb128(i64, "\xFF\x80\x00"));
|
||||
try testing.expectEqual(0x80, testLeb128(i64, "\x80\x81\x00"));
|
||||
try testing.expectEqual(0x80, testLeb128(i64, "\x80\x81\x80\x00"));
|
||||
|
||||
try testing.expectEqual(0, testLeb128(i0, &long_zero));
|
||||
try testing.expectEqual(0, testLeb128(i7, &long_zero));
|
||||
try testing.expectEqual(0, testLeb128(i8, &long_zero));
|
||||
try testing.expectEqual(0, testLeb128(i14, &long_zero));
|
||||
try testing.expectEqual(0, testLeb128(i128, &long_zero));
|
||||
|
||||
try testing.expectEqual(1, testLeb128(i2, &long_one));
|
||||
try testing.expectEqual(1, testLeb128(i7, &long_one));
|
||||
try testing.expectEqual(1, testLeb128(i8, &long_one));
|
||||
try testing.expectEqual(1, testLeb128(i14, &long_one));
|
||||
try testing.expectEqual(1, testLeb128(i128, &long_one));
|
||||
|
||||
try testing.expectEqual(-1, testLeb128(i2, &long_minus_one));
|
||||
try testing.expectEqual(-1, testLeb128(i7, &long_minus_one));
|
||||
try testing.expectEqual(-1, testLeb128(i8, &long_minus_one));
|
||||
try testing.expectEqual(-1, testLeb128(i14, &long_minus_one));
|
||||
try testing.expectEqual(-1, testLeb128(i128, &long_minus_one));
|
||||
|
||||
// Decode byte boundaries
|
||||
try testing.expectEqual(std.math.maxInt(i7), testLeb128(i7, "\x3F"));
|
||||
try testing.expectEqual(std.math.maxInt(i7) + 1, testLeb128(i8, "\xC0\x00"));
|
||||
try testing.expectEqual(std.math.maxInt(i14), testLeb128(i14, "\xFF\x3F"));
|
||||
try testing.expectEqual(std.math.maxInt(i14) + 1, testLeb128(i15, "\x80\xC0\x00"));
|
||||
try testing.expectEqual(std.math.maxInt(i49), testLeb128(i49, "\xFF\xFF\xFF\xFF\xFF\xFF\x3F"));
|
||||
try testing.expectEqual(std.math.maxInt(i49) + 1, testLeb128(i50, "\x80\x80\x80\x80\x80\x80\xC0\x00"));
|
||||
try testing.expectEqual(std.math.maxInt(i56), testLeb128(i56, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F"));
|
||||
try testing.expectEqual(std.math.maxInt(i56) + 1, testLeb128(i57, "\x80\x80\x80\x80\x80\x80\x80\xC0\x00"));
|
||||
try testing.expectEqual(std.math.maxInt(i63), testLeb128(i63, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F"));
|
||||
try testing.expectEqual(std.math.maxInt(i63) + 1, testLeb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\xC0\x00"));
|
||||
|
||||
try testing.expectEqual(std.math.minInt(i7), testLeb128(i7, "\x40"));
|
||||
try testing.expectEqual(std.math.minInt(i7) - 1, testLeb128(i8, "\xBF\x7F"));
|
||||
try testing.expectEqual(std.math.minInt(i14), testLeb128(i14, "\x80\x40"));
|
||||
try testing.expectEqual(std.math.minInt(i14) - 1, testLeb128(i15, "\xFF\xBF\x7F"));
|
||||
try testing.expectEqual(std.math.minInt(i49), testLeb128(i49, "\x80\x80\x80\x80\x80\x80\x40"));
|
||||
try testing.expectEqual(std.math.minInt(i49) - 1, testLeb128(i50, "\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F"));
|
||||
try testing.expectEqual(std.math.minInt(i56), testLeb128(i56, "\x80\x80\x80\x80\x80\x80\x80\x40"));
|
||||
try testing.expectEqual(std.math.minInt(i56) - 1, testLeb128(i57, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F"));
|
||||
try testing.expectEqual(std.math.minInt(i63), testLeb128(i63, "\x80\x80\x80\x80\x80\x80\x80\x80\x40"));
|
||||
try testing.expectEqual(std.math.minInt(i63) - 1, testLeb128(i64, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F"));
|
||||
}
|
||||
|
||||
test "deserialize unsigned LEB128" {
|
||||
// Small values
|
||||
try testing.expectEqual(46, testLeb128(u7, "\x2E"));
|
||||
try testing.expectEqual(117, testLeb128(u64, "\x75"));
|
||||
|
||||
// Random values
|
||||
try testing.expectEqual(224, testLeb128(u8, "\xE0\x01"));
|
||||
try testing.expectEqual(53023, testLeb128(u16, "\x9F\x9E\x03"));
|
||||
try testing.expectEqual(2609971022, testLeb128(u32, "\xCE\xFE\xC3\xDC\x09"));
|
||||
try testing.expectEqual(10223253173206528843, testLeb128(u64, "\xCB\xE6\xF0\xEE\x88\xD3\x92\xF0\x8D\x01"));
|
||||
try testing.expectEqual(67831258924174241363439488509570048548, testLeb128(u128, "\xA4\xC4\xD7\xE9\x8C\xD2\x86\x80\xBC\xAC\xE5\xAB\xB4\xA2\xD1\xE9\x87\x66"));
|
||||
|
||||
// max values
|
||||
try testing.expectEqual(std.math.maxInt(u8), testLeb128(u8, "\xFF\x01"));
|
||||
try testing.expectEqual(std.math.maxInt(u16), testLeb128(u16, "\xFF\xFF\x03"));
|
||||
try testing.expectEqual(std.math.maxInt(u32), testLeb128(u32, "\xFF\xFF\xFF\xFF\x0F"));
|
||||
try testing.expectEqual(std.math.maxInt(u64), testLeb128(u64, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01"));
|
||||
try testing.expectEqual(std.math.maxInt(u128), testLeb128(u128, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x03"));
|
||||
|
||||
// Specific cases
|
||||
try testing.expectEqual(0, testLeb128(u0, "\x00"));
|
||||
try testing.expectEqual(0, testLeb128(u1, "\x00"));
|
||||
try testing.expectEqual(0, testLeb128(u8, "\x00"));
|
||||
|
||||
try testing.expectEqual(1, testLeb128(u1, "\x01"));
|
||||
try testing.expectEqual(1, testLeb128(u8, "\x01"));
|
||||
|
||||
const end_of_stream: [20]u8 = @splat(0x80);
|
||||
const overflow: [21]u8 = end_of_stream ++ .{0x01};
|
||||
const long_zero: [21]u8 = end_of_stream ++ .{0x00};
|
||||
const long_one: [22]u8 = .{0x81} ++ end_of_stream ++ .{0x00};
|
||||
|
||||
// Truncated
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u64, "\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u16, "\x80\x80\x84"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u16, "\x80\x80\x84\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u16, "\x80\x80\x80\x84\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u32, "\x80\x80\x80\x80\x90"));
|
||||
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u7, ""));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u8, ""));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u14, ""));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u128, ""));
|
||||
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u7, "\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u8, "\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u14, "\x80"));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u128, "\x80"));
|
||||
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u7, &end_of_stream));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u8, &end_of_stream));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u14, &end_of_stream));
|
||||
try testing.expectError(error.EndOfStream, testLeb128(u128, &end_of_stream));
|
||||
|
||||
// Overflow
|
||||
try testing.expectError(error.Overflow, testLeb128(u0, "\x01"));
|
||||
try testing.expectError(error.Overflow, testLeb128(u1, "\x02"));
|
||||
try testing.expectError(error.Overflow, testLeb128(u8, "\x80\x02"));
|
||||
try testing.expectError(error.Overflow, testLeb128(u8, "\x80\x80\x40"));
|
||||
try testing.expectError(error.Overflow, testLeb128(u16, "\x80\x80\x80\x40"));
|
||||
try testing.expectError(error.Overflow, testLeb128(u32, "\x80\x80\x80\x80\x40"));
|
||||
try testing.expectError(error.Overflow, testLeb128(u64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x40"));
|
||||
|
||||
// Decode ULEB128
|
||||
try testing.expect((try testLeb128(u64, "\x00")) == 0);
|
||||
try testing.expect((try testLeb128(u64, "\x01")) == 1);
|
||||
try testing.expect((try testLeb128(u64, "\x3f")) == 63);
|
||||
try testing.expect((try testLeb128(u64, "\x40")) == 64);
|
||||
try testing.expect((try testLeb128(u64, "\x7f")) == 0x7f);
|
||||
try testing.expect((try testLeb128(u64, "\x80\x01")) == 0x80);
|
||||
try testing.expect((try testLeb128(u64, "\x81\x01")) == 0x81);
|
||||
try testing.expect((try testLeb128(u64, "\x90\x01")) == 0x90);
|
||||
try testing.expect((try testLeb128(u64, "\xff\x01")) == 0xff);
|
||||
try testing.expect((try testLeb128(u64, "\x80\x02")) == 0x100);
|
||||
try testing.expect((try testLeb128(u64, "\x81\x02")) == 0x101);
|
||||
try testing.expect((try testLeb128(u64, "\x80\xc1\x80\x80\x10")) == 4294975616);
|
||||
try testing.expect((try testLeb128(u64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01")) == 0x8000000000000000);
|
||||
try testing.expectError(error.Overflow, testLeb128(u7, &overflow));
|
||||
try testing.expectError(error.Overflow, testLeb128(u8, &overflow));
|
||||
try testing.expectError(error.Overflow, testLeb128(u14, &overflow));
|
||||
try testing.expectError(error.Overflow, testLeb128(u128, &overflow));
|
||||
|
||||
// Decode ULEB128 with extra padding bytes
|
||||
try testing.expect((try testLeb128(u64, "\x80\x00")) == 0);
|
||||
try testing.expect((try testLeb128(u64, "\x80\x80\x00")) == 0);
|
||||
try testing.expect((try testLeb128(u64, "\xff\x00")) == 0x7f);
|
||||
try testing.expect((try testLeb128(u64, "\xff\x80\x00")) == 0x7f);
|
||||
try testing.expect((try testLeb128(u64, "\x80\x81\x00")) == 0x80);
|
||||
try testing.expect((try testLeb128(u64, "\x80\x81\x80\x00")) == 0x80);
|
||||
// Extra padding
|
||||
try testing.expectEqual(0x7F, testLeb128(u64, "\xFF\x00"));
|
||||
try testing.expectEqual(0x7F, testLeb128(u64, "\xFF\x80\x00"));
|
||||
try testing.expectEqual(0x80, testLeb128(u64, "\x80\x81\x00"));
|
||||
try testing.expectEqual(0x80, testLeb128(u64, "\x80\x81\x80\x80\x00"));
|
||||
|
||||
try testing.expectEqual(0, testLeb128(u0, &long_zero));
|
||||
try testing.expectEqual(0, testLeb128(u7, &long_zero));
|
||||
try testing.expectEqual(0, testLeb128(u8, &long_zero));
|
||||
try testing.expectEqual(0, testLeb128(u14, &long_zero));
|
||||
try testing.expectEqual(0, testLeb128(u128, &long_zero));
|
||||
|
||||
try testing.expectEqual(1, testLeb128(u1, &long_one));
|
||||
try testing.expectEqual(1, testLeb128(u7, &long_one));
|
||||
try testing.expectEqual(1, testLeb128(u8, &long_one));
|
||||
try testing.expectEqual(1, testLeb128(u14, &long_one));
|
||||
try testing.expectEqual(1, testLeb128(u128, &long_one));
|
||||
|
||||
// Decode byte boundaries
|
||||
try testing.expectEqual(std.math.maxInt(u7), testLeb128(u7, "\x7F"));
|
||||
try testing.expectEqual(std.math.maxInt(u7) + 1, testLeb128(u8, "\x80\x01"));
|
||||
try testing.expectEqual(std.math.maxInt(u14), testLeb128(u14, "\xFF\x7F"));
|
||||
try testing.expectEqual(std.math.maxInt(u14) + 1, testLeb128(u15, "\x80\x80\x01"));
|
||||
try testing.expectEqual(std.math.maxInt(u49), testLeb128(u49, "\xFF\xFF\xFF\xFF\xFF\xFF\x7F"));
|
||||
try testing.expectEqual(std.math.maxInt(u49) + 1, testLeb128(u50, "\x80\x80\x80\x80\x80\x80\x80\x01"));
|
||||
try testing.expectEqual(std.math.maxInt(u56), testLeb128(u56, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F"));
|
||||
try testing.expectEqual(std.math.maxInt(u56) + 1, testLeb128(u57, "\x80\x80\x80\x80\x80\x80\x80\x80\x01"));
|
||||
try testing.expectEqual(std.math.maxInt(u63), testLeb128(u63, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F"));
|
||||
try testing.expectEqual(std.math.maxInt(u63) + 1, testLeb128(u64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01"));
|
||||
}
|
||||
|
||||
fn testLeb128(comptime T: type, encoded: []const u8) !T {
|
||||
var reader: std.Io.Reader = .fixed(encoded);
|
||||
const result = try reader.takeLeb128(T);
|
||||
try testing.expect(reader.seek == reader.end);
|
||||
const result = reader.takeLeb128(T);
|
||||
try testing.expectEqual(reader.seek, reader.end);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1908,33 +1908,160 @@ pub fn writeSleb128(w: *Writer, value: anytype) Error!void {
|
|||
|
||||
/// Write a single integer as LEB128 to the given writer.
|
||||
pub fn writeLeb128(w: *Writer, value: anytype) Error!void {
|
||||
const value_info = @typeInfo(@TypeOf(value)).int;
|
||||
try w.writeMultipleOf7Leb128(@as(@Int(
|
||||
value_info.signedness,
|
||||
@max(std.mem.alignForwardAnyAlign(u16, value_info.bits, 7), 7),
|
||||
), value));
|
||||
const T = @TypeOf(value);
|
||||
const info = switch (@typeInfo(T)) {
|
||||
.int => |info| info,
|
||||
else => @compileError(@typeName(T) ++ " not supported"),
|
||||
};
|
||||
|
||||
const BoundInt = @Int(info.signedness, 7);
|
||||
if (info.bits <= 7 or (value >= std.math.minInt(BoundInt) and value <= std.math.maxInt(BoundInt))) {
|
||||
const Bits = @Int(info.signedness, 8);
|
||||
const byte = switch (info.signedness) {
|
||||
.signed => @as(Bits, @intCast(value)) & 0x7F,
|
||||
.unsigned => @as(Bits, @intCast(value)),
|
||||
};
|
||||
try w.writeByte(@bitCast(byte));
|
||||
return;
|
||||
}
|
||||
|
||||
const Byte = packed struct { bits: u7, more: bool };
|
||||
const Int = std.math.ByteAlignedInt(T);
|
||||
|
||||
const max_bytes = @divFloor(info.bits - 1, 7) + 1;
|
||||
|
||||
const sign_value = value >> (info.bits - 1);
|
||||
var val: Int = value;
|
||||
for (0..max_bytes) |_| {
|
||||
const more = switch (info.signedness) {
|
||||
.signed => val >> 6 != sign_value,
|
||||
.unsigned => val > std.math.maxInt(u7),
|
||||
};
|
||||
|
||||
try w.writeByte(@bitCast(@as(Byte, .{
|
||||
.bits = @intCast(val & 0x7F),
|
||||
.more = more,
|
||||
})));
|
||||
|
||||
if (!more) return;
|
||||
|
||||
val >>= 7;
|
||||
} else unreachable;
|
||||
}
|
||||
|
||||
fn writeMultipleOf7Leb128(w: *Writer, value: anytype) Error!void {
|
||||
const value_info = @typeInfo(@TypeOf(value)).int;
|
||||
const Byte = packed struct(u8) { bits: u7, more: bool };
|
||||
var bytes: [@divExact(value_info.bits, 7)]Byte = undefined;
|
||||
var remaining = value;
|
||||
for (&bytes, 1..) |*byte, len| {
|
||||
const more = switch (value_info.signedness) {
|
||||
.signed => remaining >> 6 != remaining >> (value_info.bits - 1),
|
||||
.unsigned => remaining > std.math.maxInt(u7),
|
||||
};
|
||||
byte.* = .{
|
||||
.bits = @bitCast(@as(
|
||||
@Int(value_info.signedness, 7),
|
||||
@truncate(remaining),
|
||||
)),
|
||||
.more = more,
|
||||
};
|
||||
if (value_info.bits > 7) remaining >>= 7;
|
||||
if (!more) return w.writeAll(@ptrCast(bytes[0..len]));
|
||||
} else unreachable;
|
||||
test "serialize signed LEB128" {
|
||||
// Small values
|
||||
try testLeb128Encoding(i7, 9, "\x09");
|
||||
try testLeb128Encoding(i64, 125, "\xFD\x00");
|
||||
|
||||
try testLeb128Encoding(i7, -34, "\x5E");
|
||||
try testLeb128Encoding(i64, -3, "\x7D");
|
||||
|
||||
// Random values
|
||||
try testLeb128Encoding(i16, 19373, "\xAD\x97\x01");
|
||||
try testLeb128Encoding(i32, 1628839242, "\xCA\xBA\xD8\x88\x06");
|
||||
try testLeb128Encoding(i64, 3789169920125966546, "\xD2\xB1\xD0\xD5\xF6\xBE\xF5\xCA\x34");
|
||||
try testLeb128Encoding(i128, 704622239050934257305893323522763588, "\xC4\xD6\x83\xC7\xE3\x91\x95\xC3\x96\x80\x8D\xA5\xF5\xDF\xA3\xDA\x87\x01");
|
||||
|
||||
try testLeb128Encoding(i16, -14558, "\xA2\x8E\x7F");
|
||||
try testLeb128Encoding(i32, -1702738165, "\x8B\x8E\x89\xD4\x79");
|
||||
try testLeb128Encoding(i64, -1709126996960612298, "\xB6\xE0\x87\xB1\xD3\xC1\xFD\xA3\x68");
|
||||
try testLeb128Encoding(i128, -113498719181566012704681230050325944039, "\x99\xD2\x80\xBC\xE6\x95\xBC\xC8\xDE\xB4\x9D\x81\x9F\xCA\xC6\xF8\x9C\xD5\x7E");
|
||||
|
||||
// {min,max} values
|
||||
try testLeb128Encoding(i16, std.math.maxInt(i16), "\xFF\xFF\x01");
|
||||
try testLeb128Encoding(i32, std.math.maxInt(i32), "\xFF\xFF\xFF\xFF\x07");
|
||||
try testLeb128Encoding(i64, std.math.maxInt(i64), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00");
|
||||
try testLeb128Encoding(i128, std.math.maxInt(i128), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01");
|
||||
|
||||
try testLeb128Encoding(i16, std.math.minInt(i16), "\x80\x80\x7E");
|
||||
try testLeb128Encoding(i32, std.math.minInt(i32), "\x80\x80\x80\x80\x78");
|
||||
try testLeb128Encoding(i64, std.math.minInt(i64), "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7F");
|
||||
try testLeb128Encoding(i128, std.math.minInt(i128), "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7E");
|
||||
|
||||
// Specific cases
|
||||
try testLeb128Encoding(i0, 0, "\x00");
|
||||
try testLeb128Encoding(i8, 0, "\x00");
|
||||
|
||||
try testLeb128Encoding(i2, -1, "\x7F");
|
||||
try testLeb128Encoding(i8, -1, "\x7F");
|
||||
|
||||
try testLeb128Encoding(i2, 1, "\x01");
|
||||
try testLeb128Encoding(i8, 1, "\x01");
|
||||
|
||||
// Encode byte boundaries
|
||||
try testLeb128Encoding(i7, std.math.maxInt(i7), "\x3F");
|
||||
try testLeb128Encoding(i8, std.math.maxInt(i7) + 1, "\xC0\x00");
|
||||
try testLeb128Encoding(i14, std.math.maxInt(i14), "\xFF\x3F");
|
||||
try testLeb128Encoding(i15, std.math.maxInt(i14) + 1, "\x80\xC0\x00");
|
||||
try testLeb128Encoding(i49, std.math.maxInt(i49), "\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
|
||||
try testLeb128Encoding(i50, std.math.maxInt(i49) + 1, "\x80\x80\x80\x80\x80\x80\xC0\x00");
|
||||
try testLeb128Encoding(i56, std.math.maxInt(i56), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
|
||||
try testLeb128Encoding(i57, std.math.maxInt(i56) + 1, "\x80\x80\x80\x80\x80\x80\x80\xC0\x00");
|
||||
try testLeb128Encoding(i63, std.math.maxInt(i63), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
|
||||
try testLeb128Encoding(i64, std.math.maxInt(i63) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\xC0\x00");
|
||||
|
||||
try testLeb128Encoding(i7, std.math.minInt(i7), "\x40");
|
||||
try testLeb128Encoding(i8, std.math.minInt(i7) - 1, "\xBF\x7F");
|
||||
try testLeb128Encoding(i14, std.math.minInt(i14), "\x80\x40");
|
||||
try testLeb128Encoding(i15, std.math.minInt(i14) - 1, "\xFF\xBF\x7F");
|
||||
try testLeb128Encoding(i49, std.math.minInt(i49), "\x80\x80\x80\x80\x80\x80\x40");
|
||||
try testLeb128Encoding(i50, std.math.minInt(i49) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
|
||||
try testLeb128Encoding(i56, std.math.minInt(i56), "\x80\x80\x80\x80\x80\x80\x80\x40");
|
||||
try testLeb128Encoding(i57, std.math.minInt(i56) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
|
||||
try testLeb128Encoding(i63, std.math.minInt(i63), "\x80\x80\x80\x80\x80\x80\x80\x80\x40");
|
||||
try testLeb128Encoding(i64, std.math.minInt(i63) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
|
||||
}
|
||||
|
||||
test "serialize unsigned LEB128" {
|
||||
// Small values
|
||||
try testLeb128Encoding(u7, 12, "\x0C");
|
||||
try testLeb128Encoding(u64, 201, "\xC9\x01");
|
||||
|
||||
// Random values
|
||||
try testLeb128Encoding(u8, 254, "\xFE\x01");
|
||||
try testLeb128Encoding(u16, 30241, "\xA1\xEC\x01");
|
||||
try testLeb128Encoding(u32, 2173531193, "\xB9\xE8\xB5\x8C\x08");
|
||||
try testLeb128Encoding(u64, 18321125691115744902, "\x86\xDD\xF2\x81\xF2\xD7\xED\xA0\xFE\x01");
|
||||
try testLeb128Encoding(u128, 122619209508942982841456325819614676193, "\xE1\x89\xF3\xD9\xE3\xAD\xEC\xF4\x98\x95\xF8\xBB\xD7\xB8\xF2\xCC\xBF\xB8\x01");
|
||||
|
||||
// Max values
|
||||
try testLeb128Encoding(u8, std.math.maxInt(u8), "\xFF\x01");
|
||||
try testLeb128Encoding(u16, std.math.maxInt(u16), "\xFF\xFF\x03");
|
||||
try testLeb128Encoding(u32, std.math.maxInt(u32), "\xFF\xFF\xFF\xFF\x0F");
|
||||
try testLeb128Encoding(u64, std.math.maxInt(u64), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01");
|
||||
try testLeb128Encoding(u128, std.math.maxInt(u128), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x03");
|
||||
|
||||
// Specific cases
|
||||
try testLeb128Encoding(u0, 0, "\x00");
|
||||
try testLeb128Encoding(u1, 0, "\x00");
|
||||
try testLeb128Encoding(u8, 0, "\x00");
|
||||
|
||||
try testLeb128Encoding(u1, 1, "\x01");
|
||||
try testLeb128Encoding(u8, 1, "\x01");
|
||||
|
||||
// Encode byte boundaries
|
||||
try testLeb128Encoding(u7, std.math.maxInt(u7), "\x7F");
|
||||
try testLeb128Encoding(u8, std.math.maxInt(u7) + 1, "\x80\x01");
|
||||
try testLeb128Encoding(u14, std.math.maxInt(u14), "\xFF\x7F");
|
||||
try testLeb128Encoding(u15, std.math.maxInt(u14) + 1, "\x80\x80\x01");
|
||||
try testLeb128Encoding(u49, std.math.maxInt(u49), "\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
|
||||
try testLeb128Encoding(u50, std.math.maxInt(u49) + 1, "\x80\x80\x80\x80\x80\x80\x80\x01");
|
||||
try testLeb128Encoding(u56, std.math.maxInt(u56), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
|
||||
try testLeb128Encoding(u57, std.math.maxInt(u56) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\x01");
|
||||
try testLeb128Encoding(u63, std.math.maxInt(u63), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
|
||||
try testLeb128Encoding(u64, std.math.maxInt(u63) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01");
|
||||
}
|
||||
|
||||
fn testLeb128Encoding(comptime T: type, value: T, encoding: []const u8) !void {
|
||||
const info = @typeInfo(T).int;
|
||||
const max_bytes = @divFloor(info.bits -| 1, 7) + 1;
|
||||
var bytes: [max_bytes]u8 = undefined;
|
||||
|
||||
var fw: Writer = .fixed(&bytes);
|
||||
try writeLeb128(&fw, value);
|
||||
|
||||
try std.testing.expectEqualSlices(u8, encoding, fw.buffered());
|
||||
}
|
||||
|
||||
test "printValue max_depth" {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue