LEB128(Little Endian Base 128)是一种支持将任意大的整数编码到变长字节流的可变长度编码。DWARF调试文件格式以及WebAssembly中所有整数字面量的二进制编码都使用了 LEB128。
编码格式
LEB128格式与可变长度数量(VLQ) 格式非常相似;两者的主要区别在于,LEB128采用小端序,而可变长度数量采用大端序。两者都允许将小数字存储在单个字节中,同时还允许对任意长度的数字进行编码。LEB128有两个版本:无符号LEB128和有符号LEB128。解码器必须知道编码值是无符号 LEB128还是有符号LEB128才能正确解码。
无符号LEB128
要使用无符号LEB128(ULEB128)编码无符号数,首先要用二进制表示该数,然后用零将该数的二进制表示扩展至长度为7位的倍数(这样,如果数非零,则最高7位不全为0),将该数分成7位一组。每一个7位的组对应输出的一个字节,顺序从最低有效位到最高有效位,除了最后一个字节外,其他字节都置位最高位。WebAssembly 允许对零进行其他编码(0x80 0x00、0x80 0x80 0x00,...)。 提出了“Masked VByte”,它在商用Haswell硬件上实现了每秒 6.5 亿到 27 亿个整数的速度,具体取决于编码密度。
后续论文提出了一种变体编码“Stream VByte:更快的面向字节的整数压缩” ,其速度提升至每秒超过 40 亿个整数。这种流编码将控制流与编码数据分离,因此与 LEB128 二进制不兼容。
类 C 伪代码
编码无符号整数
do {
byte = value & 0x7f; / low-order 7 bits of value /
value >>= 7;
if (value != 0) / more bytes to come /
byte |= 0x80; / set high-order bit of byte /
emit(byte);
} while (value != 0);
编码有符号整数
more = 1;
negative = (value >= 7;
/* the following is only necessary if the implementation of >>= uses a
logical shift rather than an arithmetic shift for a signed left operand
this does not happen on most programming languages if "value" is in a signed type to begin with */
if (negative)
value |= (~0
解码无符号整数
result = 0;
shift = 0;
unsigned char byte;
do {
byte = get_next_byte_in_input();
result |= (byte & 0x7f)
解码有符号整数
result = 0;
shift = 0;
/ the size in bits of the result variable, e.g., 64 if result's type is int64_t /
size = sizeof(result) CHAR_BITS; / no. of bits in signed integer */
/ will be assigned inside the do-while loop, but referenced afterwards /
unsigned char byte;
do {
byte = get_next_byte_in_input();
result |= (byte & 0x7f)
JavaScript 代码
编码有符号BigInt
const encodeSignedLeb128FromBigInt = (value) => {
value = BigInt(value);
const result = [];
while (true) {
const byte_ = Number(value & 0x7fn);
value >>= 7n;
if (
(value === 0n && (byte_ & 0x40) === 0) ||
(value === -1n && (byte_ & 0x40) !== 0)
) {
result.push(byte_);
return result;
}
result.push(byte_ | 0x80);
}
};
解码有符号BigInt
const decodeSignedBigInt = (input) => {
let result = 0n;
let shift = 0;
while (true) {
const byte = input.shift();
result |= BigInt((byte & 0x7f)
用途
- Android项目在其 Dalvik 可执行格式 (.dex) 文件格式中使用 LEB128。
- 惠普 IA-64 异常处理中的压缩表。
- DWARF文件格式使用无符号和有符号的LEB128编码。
- mpatrol 试工具在其跟踪文件格式中使用 LEB128。
- osu!在其“osu! 重放”(.osr)格式中使用 LEB128。
- W3C 高效 XML 交换(EXI)使用 LEB128 表示无符号整数。
- WebAssembly中。
相关编码
- [https://web.archive.org/web/20210224160104/http://www.dlugosz.com/ZIP2/VLI.html Dlugosz 的可变长度整数编码]([http://www.dlugosz.com/ZIP2/VLI.html 原版])在前三个长度分隔符处使用 7 位的倍数,但此后增量会发生变化。它还将所有前缀位放在字的开头,而不是每个字节的开头。
- 人体学输入设备(HID)报告描述符字节使用2位长的位域来编码后续整数的大小,该整数的字节数为零、一、二或四字节,始终采用小端序。符号性(即是否使用符号扩展缩短的整数)取决于描述符的类型。
- LLVM位码文件格式使用了类似的技术不同之处在于将值分成与上下文相关的大小的位组,最高位表示连续性,而不是固定的 7 位。
- 协议缓冲区(Protobuf)对无符号整数使用相同的编码,但对有符号整数进行编码时,将符号作为第一个字节的最低有效位。
- [http://luca.ntop.org/Teaching/Appunti/asn1.html ASN.1 BER、DER] 将每个 ASN.1 类型的值编码为八位字节字符串。
参考
参见
- DWARF 调试文件格式
- UTF-7
评论 (0)