| // See file LICENSE for more information. |
| |
| import 'dart:typed_data'; |
| |
| const _MASK_3 = 0x07; |
| const _MASK_5 = 0x1F; |
| const _MASK_6 = 0x3F; |
| const _MASK_8 = 0xFF; |
| const _MASK_16 = 0xFFFF; |
| const _MASK_32 = 0xFFFFFFFF; |
| |
| // ignore: non_constant_identifier_names |
| final _MASK32_HI_BITS = [ |
| 0xFFFFFFFF, |
| 0x7FFFFFFF, |
| 0x3FFFFFFF, |
| 0x1FFFFFFF, |
| 0x0FFFFFFF, |
| 0x07FFFFFF, |
| 0x03FFFFFF, |
| 0x01FFFFFF, |
| 0x00FFFFFF, |
| 0x007FFFFF, |
| 0x003FFFFF, |
| 0x001FFFFF, |
| 0x000FFFFF, |
| 0x0007FFFF, |
| 0x0003FFFF, |
| 0x0001FFFF, |
| 0x0000FFFF, |
| 0x00007FFF, |
| 0x00003FFF, |
| 0x00001FFF, |
| 0x00000FFF, |
| 0x000007FF, |
| 0x000003FF, |
| 0x000001FF, |
| 0x000000FF, |
| 0x0000007F, |
| 0x0000003F, |
| 0x0000001F, |
| 0x0000000F, |
| 0x00000007, |
| 0x00000003, |
| 0x00000001, |
| 0x00000000 |
| ]; |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // 8 bit operations |
| // |
| int clip8(int x) => x & _MASK_8; |
| |
| int csum8(int x, int y) => sum8(clip8(x), clip8(y)); |
| int sum8(int x, int y) { |
| assert((x >= 0) && (x <= _MASK_8)); |
| assert((y >= 0) && (y <= _MASK_8)); |
| return (x + y) & _MASK_8; |
| } |
| |
| int csub8(int x, int y) => sub8(clip8(x), clip8(y)); |
| int sub8(int x, int y) { |
| assert((x >= 0) && (x <= _MASK_8)); |
| assert((y >= 0) && (y <= _MASK_8)); |
| return (x - y) & _MASK_8; |
| } |
| |
| int cshiftl8(int x, int n) => shiftl8(clip8(x), n); |
| int shiftl8(int x, int n) { |
| assert((x >= 0) && (x <= _MASK_8)); |
| return (x << (n & _MASK_3)) & _MASK_8; |
| } |
| |
| int cshiftr8(int x, int n) => shiftr8(clip8(x), n); |
| int shiftr8(int x, int n) { |
| assert((x >= 0) && (x <= _MASK_8)); |
| return x >> (n & _MASK_3); |
| } |
| |
| int cneg8(int x) => neg8(clip8(x)); |
| int neg8(int x) { |
| assert((x >= 0) && (x <= _MASK_8)); |
| return -x & _MASK_8; |
| } |
| |
| int cnot8(int x) => not8(clip8(x)); |
| int not8(int x) { |
| assert((x >= 0) && (x <= _MASK_8)); |
| return ~x & _MASK_8; |
| } |
| |
| int crotl8(int x, int n) => rotl8(clip8(x), n); |
| int rotl8(int x, int n) { |
| assert(n >= 0); |
| assert((x >= 0) && (x <= _MASK_8)); |
| n &= _MASK_3; |
| return ((x << n) & _MASK_8) | (x >> (8 - n)); |
| } |
| |
| int crotr8(int x, int n) => rotr8(clip8(x), n); |
| int rotr8(int x, int n) { |
| assert(n >= 0); |
| assert((x >= 0) && (x <= _MASK_8)); |
| n &= _MASK_3; |
| return ((x >> n) & _MASK_8) | ((x << (8 - n)) & _MASK_8); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // 16 bit operations |
| // |
| int clip16(int x) => x & _MASK_16; |
| |
| /// Packs a 16 bit integer into a byte buffer. The [out] parameter can be an [Uint8List] or a |
| /// [ByteData] if you will run it several times against the same buffer and want faster execution. |
| void pack16(int x, dynamic out, int offset, Endian endian) { |
| assert((x >= 0) && (x <= _MASK_16)); |
| if (out is! ByteData) { |
| out = ByteData.view(out.buffer, out.offsetInBytes, out.length); |
| } |
| out.setUint16(offset, x, endian); |
| } |
| |
| /// Unpacks a 16 bit integer from a byte buffer. The [inp] parameter can be an [Uint8List] or a |
| /// [ByteData] if you will run it several times against the same buffer and want faster execution. |
| int unpack16(dynamic inp, int offset, Endian endian) { |
| if (inp is! ByteData) { |
| inp = ByteData.view( |
| inp.buffer as ByteBuffer, inp.offsetInBytes as int, inp.length as int?); |
| } |
| return inp.getUint16(offset, endian); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // 32 bit operations |
| // |
| int clip32(int x) => x & _MASK_32; |
| |
| int csum32(int x, int y) => sum32(clip32(x), clip32(y)); |
| int sum32(int x, int y) { |
| assert((x >= 0) && (x <= _MASK_32)); |
| assert((y >= 0) && (y <= _MASK_32)); |
| return (x + y) & _MASK_32; |
| } |
| |
| int csub32(int x, int y) => sub32(clip32(x), clip32(y)); |
| int sub32(int x, int y) { |
| assert((x >= 0) && (x <= _MASK_32)); |
| assert((y >= 0) && (y <= _MASK_32)); |
| return (x - y) & _MASK_32; |
| } |
| |
| int cshiftl32(int x, int n) => shiftl32(clip32(x), n); |
| int shiftl32(int x, int n) { |
| assert((x >= 0) && (x <= _MASK_32)); |
| n &= _MASK_5; |
| x &= _MASK32_HI_BITS[n]; |
| return (x << n) & _MASK_32; |
| } |
| |
| int cshiftr32(int x, int n) => shiftr32(clip32(x), n); |
| int shiftr32(int x, int n) { |
| assert((x >= 0) && (x <= _MASK_32)); |
| n &= _MASK_5; |
| return x >> n; |
| } |
| |
| int cneg32(int x) => neg32(clip32(x)); |
| int neg32(int x) { |
| assert((x >= 0) && (x <= _MASK_32)); |
| return -x & _MASK_32; |
| } |
| |
| int cnot32(int x) => not32(clip32(x)); |
| int not32(int x) { |
| assert((x >= 0) && (x <= _MASK_32)); |
| return ~x & _MASK_32; |
| } |
| |
| int crotl32(int x, int n) => rotl32(clip32(x), n); |
| int rotl32(int x, int n) { |
| assert(n >= 0); |
| assert((x >= 0) && (x <= _MASK_32)); |
| n &= _MASK_5; |
| return shiftl32(x, n) | (x >> (32 - n)); |
| } |
| |
| int crotr32(int x, int n) => rotr32(clip32(x), n); |
| int rotr32(int x, int n) { |
| assert(n >= 0); |
| assert((x >= 0) && (x <= _MASK_32)); |
| n &= _MASK_5; |
| return (x >> n) | shiftl32(x, 32 - n); |
| } |
| |
| /// Packs a 32 bit integer into a byte buffer. The [out] parameter can be an [Uint8List] or a |
| /// [ByteData] if you will run it several times against the same buffer and want faster execution. |
| void pack32(int x, dynamic out, int offset, Endian endian) { |
| assert((x >= 0) && (x <= _MASK_32)); |
| if (out is! ByteData) { |
| out = |
| ByteData.view(out.buffer as ByteBuffer, out.offsetInBytes, out.length); |
| } |
| out.setUint32(offset, x, endian); |
| } |
| |
| /// Unpacks a 32 bit integer from a byte buffer. The [inp] parameter can be an [Uint8List] or a |
| /// [ByteData] if you will run it several times against the same buffer and want faster execution. |
| int unpack32(dynamic inp, int offset, Endian endian) { |
| if (inp is! ByteData) { |
| inp = ByteData.view(inp.buffer, inp.offsetInBytes, inp.length); |
| } |
| return inp.getUint32(offset, endian); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // 64 bit operations |
| // |
| class Register64 { |
| late int _hi32; |
| late int _lo32; |
| |
| Register64([Object hiOrLo32OrY = 0, int? lo32]) { |
| set(hiOrLo32OrY, lo32); |
| } |
| |
| int get lo32 => _lo32; |
| int get hi32 => _hi32; |
| |
| @override |
| bool operator ==(Object other) => other is Register64 |
| ? (((_hi32 == other._hi32) && (_lo32 == other._lo32))) |
| : false; |
| bool operator <(Register64 y) => |
| (_hi32 < y._hi32) || ((_hi32 == y._hi32) && (_lo32 < y._lo32)); |
| bool operator <=(Register64 y) => (this < y) || (this == y); |
| bool operator >(Register64 y) => |
| (_hi32 > y._hi32) || ((_hi32 == y._hi32) && (_lo32 > y._lo32)); |
| bool operator >=(Register64 y) => (this > y) || (this == y); |
| |
| void set(dynamic hiOrLo32OrY, [int? lo32]) { |
| if (lo32 == null) { |
| if (hiOrLo32OrY is Register64) { |
| _hi32 = hiOrLo32OrY._hi32; |
| _lo32 = hiOrLo32OrY._lo32; |
| } else { |
| assert(hiOrLo32OrY as int <= _MASK_32); |
| _hi32 = 0; |
| _lo32 = hiOrLo32OrY as int; |
| } |
| } else { |
| assert(hiOrLo32OrY as int <= _MASK_32); |
| assert(lo32 <= _MASK_32); |
| _hi32 = hiOrLo32OrY as int; |
| _lo32 = lo32; |
| } |
| } |
| |
| void sum(dynamic y) { |
| if (y is int) { |
| y &= _MASK_32; |
| var slo32 = _lo32 + y; |
| _lo32 = slo32 & _MASK_32; |
| if (slo32 != _lo32) { |
| _hi32++; |
| _hi32 &= _MASK_32; |
| } |
| } else { |
| var slo32 = _lo32 + y._lo32 as int; |
| _lo32 = slo32 & _MASK_32; |
| var carry = ((slo32 != _lo32) ? 1 : 0); |
| _hi32 = ((_hi32 + y._hi32 + carry) as int) & _MASK_32; |
| } |
| } |
| |
| void sumReg(Register64 y) { |
| var slo32 = _lo32 + y._lo32; |
| _lo32 = slo32 & _MASK_32; |
| var carry = ((slo32 != _lo32) ? 1 : 0); |
| _hi32 = (_hi32 + y._hi32 + carry) & _MASK_32; |
| } |
| |
| void sub(dynamic y) { |
| // TODO: optimize sub() ??? |
| sum(Register64(y)..neg()); |
| } |
| |
| void mul(dynamic y) { |
| // Grab 16-bit chunks. |
| final a0 = _lo32 & _MASK_16; |
| final a1 = (_lo32 >> 16) & _MASK_16; |
| final a2 = _hi32 & _MASK_16; |
| final a3 = (_hi32 >> 16) & _MASK_16; |
| late int b0, b1, b2, b3; |
| if (y is int) { |
| // Assume it is a 32-bit integer. |
| y &= _MASK_32; |
| b0 = y & _MASK_16; |
| b1 = (y >> 16) & _MASK_16; |
| b2 = b3 = 0; |
| } else /* if (y is Register64) */ { |
| b0 = (y as Register64)._lo32 & _MASK_16; |
| b1 = (y._lo32 >> 16) & _MASK_16; |
| b2 = y._hi32 & _MASK_16; |
| b3 = (y._hi32 >> 16) & _MASK_16; |
| } |
| |
| // Compute partial products. |
| // Optimization: if b is small, avoid multiplying by parts that are 0. |
| var p0 = a0 * b0; // << 0 |
| var p1 = a1 * b0; // << 16 |
| var p2 = a2 * b0; // << 32 |
| var p3 = a3 * b0; // << 48 |
| |
| if (b1 != 0) { |
| p1 += a0 * b1; |
| p2 += a1 * b1; |
| p3 += a2 * b1; |
| } |
| if (b2 != 0) { |
| p2 += a0 * b2; |
| p3 += a1 * b2; |
| } |
| if (b3 != 0) { |
| p3 += a0 * b3; |
| } |
| |
| // Accumulate into 32-bit chunks: |
| // |................................|................................| |
| // |................................|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| p0 |
| // |................................|................................| |
| // |................................|................................| |
| // |................xxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxx................| p1 |
| // |................................|................................| |
| // |................................|................................| |
| // |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|................................| p2 |
| // |................................|................................| |
| // |................................|................................| |
| // |xxxxxxxxxxxxxxxx................|................................| p3 |
| var slo32 = p0 + ((p1 & _MASK_16) << 16); |
| _lo32 = slo32 & _MASK_32; |
| var carry = ((slo32 != _lo32) ? 1 : 0); |
| // p1 is a 33-bit integer, shiftr operation will ignore 33th-bit on js |
| var carry2 = ((p1 & _MASK_32) != p1) ? 0x10000 : 0; |
| var shi32 = |
| ((p1 & _MASK_32) >> 16) + p2 + ((p3 & _MASK_16) << 16) + carry + carry2; |
| _hi32 = shi32 & _MASK_32; |
| } |
| |
| void neg() { |
| not(); |
| sum(1); |
| } |
| |
| void not() { |
| _hi32 = ~_hi32 & _MASK_32; |
| _lo32 = ~_lo32 & _MASK_32; |
| } |
| |
| void and(Register64 y) { |
| _hi32 &= y._hi32; |
| _lo32 &= y._lo32; |
| } |
| |
| void or(Register64 y) { |
| _hi32 |= y._hi32; |
| _lo32 |= y._lo32; |
| } |
| |
| void xor(Register64 y) { |
| _hi32 ^= y._hi32; |
| _lo32 ^= y._lo32; |
| } |
| |
| void shiftl(int n) { |
| n &= _MASK_6; |
| if (n == 0) { |
| // do nothing |
| } else if (n >= 32) { |
| _hi32 = shiftl32(_lo32, n - 32); |
| _lo32 = 0; |
| } else { |
| _hi32 = shiftl32(_hi32, n); |
| _hi32 |= _lo32 >> (32 - n); |
| _lo32 = shiftl32(_lo32, n); |
| } |
| } |
| |
| void shiftr(int n) { |
| n &= _MASK_6; |
| if (n == 0) { |
| // do nothing |
| } else if (n >= 32) { |
| _lo32 = _hi32 >> (n - 32); |
| _hi32 = 0; |
| } else { |
| _lo32 = _lo32 >> n; |
| _lo32 |= shiftl32(_hi32, 32 - n); |
| _hi32 = _hi32 >> n; |
| } |
| } |
| |
| void rotl(int n) { |
| n &= _MASK_6; |
| if (n == 0) { |
| // do nothing |
| } else { |
| if (n >= 32) { |
| var swap = _hi32; |
| _hi32 = _lo32; |
| _lo32 = swap; |
| n -= 32; |
| } |
| if (n == 0) { |
| // do nothing |
| } else { |
| var hi32 = _hi32; |
| _hi32 = shiftl32(_hi32, n); |
| _hi32 |= _lo32 >> (32 - n); |
| _lo32 = shiftl32(_lo32, n); |
| _lo32 |= hi32 >> (32 - n); |
| } |
| } |
| } |
| |
| void rotr(int n) { |
| n &= _MASK_6; |
| if (n == 0) { |
| // do nothing |
| } else { |
| if (n >= 32) { |
| var swap = _hi32; |
| _hi32 = _lo32; |
| _lo32 = swap; |
| n -= 32; |
| } |
| if (n == 0) { |
| // do nothing |
| } else { |
| var hi32 = _hi32; |
| _hi32 = _hi32 >> n; |
| _hi32 |= shiftl32(_lo32, 32 - n); |
| _lo32 = _lo32 >> n; |
| _lo32 |= shiftl32(hi32, 32 - n); |
| } |
| } |
| } |
| |
| void mod(int n) { |
| if (_hi32 == 0) { |
| // hi32 is zero, so just caculate lo32. |
| _lo32 %= n; |
| } else { |
| // hi32 is not zero, use Horner's Method |
| const b = 0x10000; |
| final a0 = _lo32 & _MASK_16; |
| final a1 = (_lo32 >> 16) & _MASK_16; |
| final a2 = _hi32 & _MASK_16; |
| final a3 = (_hi32 >> 16) & _MASK_16; |
| _lo32 = ((((((a3 % n) * b + a2) % n) * b + a1) % n) * b + a0) % n; |
| // Assume that n is a 32-bit integer, so hi32 will always be zero |
| _hi32 = 0; |
| } |
| } |
| |
| /// Packs a 64 bit integer into a byte buffer. The [out] parameter can be an [Uint8List] or a |
| /// [ByteData] if you will run it several times against the same buffer and want faster execution. |
| void pack(dynamic out, int offset, Endian endian) { |
| switch (endian) { |
| case Endian.big: |
| pack32(hi32, out, offset, endian); |
| pack32(lo32, out, offset + 4, endian); |
| break; |
| |
| case Endian.little: |
| pack32(hi32, out, offset + 4, endian); |
| pack32(lo32, out, offset, endian); |
| break; |
| |
| default: |
| throw UnsupportedError('Invalid endianness: $endian'); |
| } |
| } |
| |
| /// Unpacks a 64 bit integer from a byte buffer. The [inp] parameter can be an [Uint8List] or a |
| /// [ByteData] if you will run it several times against the same buffer and want faster execution. |
| void unpack(dynamic inp, int offset, Endian endian) { |
| switch (endian) { |
| case Endian.big: |
| _hi32 = unpack32(inp, offset, endian); |
| _lo32 = unpack32(inp, offset + 4, endian); |
| break; |
| |
| case Endian.little: |
| _hi32 = unpack32(inp, offset + 4, endian); |
| _lo32 = unpack32(inp, offset, endian); |
| break; |
| |
| default: |
| throw UnsupportedError('Invalid endianness: $endian'); |
| } |
| } |
| |
| @override |
| String toString() { |
| var sb = StringBuffer(); |
| _padWrite(sb, _hi32); |
| _padWrite(sb, _lo32); |
| return sb.toString(); |
| } |
| |
| void _padWrite(StringBuffer sb, int value) { |
| var str = value.toRadixString(16); |
| for (var i = 8 - str.length; i > 0; i--) { |
| sb.write('0'); |
| } |
| sb.write(str); |
| } |
| |
| @override |
| int get hashCode => Object.hash(_hi32, _lo32); |
| } |
| |
| class Register64List { |
| final List<Register64> _list; |
| |
| Register64List.from(List<List<int>> values) |
| : _list = List<Register64>.generate( |
| values.length, (i) => Register64(values[i][0], values[i][1])); |
| |
| Register64List(int length) |
| : _list = List<Register64>.generate(length, (_) => Register64()); |
| |
| int get length => _list.length; |
| |
| Register64 operator [](int index) => _list[index]; |
| |
| void fillRange(int start, int end, dynamic hiOrLo32OrY, [int? lo32]) { |
| for (var i = start; i < end; i++) { |
| _list[i].set(hiOrLo32OrY, lo32); |
| } |
| } |
| |
| void setRange(int start, int end, Register64List list, [int skipCount = 0]) { |
| var length = end - start; |
| for (var i = 0; i < length; i++) { |
| _list[start + i].set(list[skipCount + i]); |
| } |
| } |
| |
| @override |
| String toString() { |
| var sb = StringBuffer('('); |
| for (var i = 0; i < _list.length; i++) { |
| if (i > 0) { |
| sb.write(', '); |
| } |
| sb.write(_list[i].toString()); |
| } |
| sb.write(')'); |
| return sb.toString(); |
| } |
| } |