[core/shared] Allow for BigInt to work in isolategroup-bound callbacks.

Introduce runtime checks for BigInt to allow it being used as deeply immutable class.

TEST=run_isolate_group_run_test

Bug: https://github.com/dart-lang/sdk/issues/61030
Bug: https://github.com/dart-lang/sdk/issues/62601
Change-Id: I343862bdf5603be571d3bf937261dd61aca9ca0e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/477040
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
Alexander Aprelev
2026-03-02 13:52:34 -08:00
committed by Commit Queue
parent 652395537d
commit f3bc347448
13 changed files with 817 additions and 589 deletions
@@ -804,39 +804,39 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Object_tags_offset => 0x0;
@override
int get ObjectStore_double_type_offset => 0x1b0;
int get ObjectStore_double_type_offset => 0x1b8;
@override
int get ObjectStore_int_type_offset => 0x160;
int get ObjectStore_int_type_offset => 0x168;
@override
int get ObjectStore_record_field_names_offset => 0x428;
int get ObjectStore_record_field_names_offset => 0x430;
@override
int get ObjectStore_string_type_offset => 0x1d8;
int get ObjectStore_string_type_offset => 0x1e0;
@override
int get ObjectStore_type_type_offset => 0x138;
int get ObjectStore_type_type_offset => 0x140;
@override
int get ObjectStore_ffi_callback_code_offset => 0x520;
int get ObjectStore_ffi_callback_code_offset => 0x528;
@override
int get ObjectStore_suspend_state_await_offset => 0x478;
int get ObjectStore_suspend_state_await_offset => 0x480;
@override
int get ObjectStore_suspend_state_await_with_type_check_offset => 0x480;
int get ObjectStore_suspend_state_await_with_type_check_offset => 0x488;
@override
int get ObjectStore_suspend_state_handle_exception_offset => 0x4c0;
int get ObjectStore_suspend_state_handle_exception_offset => 0x4c8;
@override
int get ObjectStore_suspend_state_init_async_offset => 0x470;
int get ObjectStore_suspend_state_init_async_offset => 0x478;
@override
int get ObjectStore_suspend_state_init_async_star_offset => 0x498;
int get ObjectStore_suspend_state_init_async_star_offset => 0x4a0;
@override
int get ObjectStore_suspend_state_init_sync_star_offset => 0x4b0;
int get ObjectStore_suspend_state_init_sync_star_offset => 0x4b8;
@override
int get ObjectStore_suspend_state_return_async_offset => 0x488;
int get ObjectStore_suspend_state_return_async_offset => 0x490;
@override
int get ObjectStore_suspend_state_return_async_not_future_offset => 0x490;
int get ObjectStore_suspend_state_return_async_not_future_offset => 0x498;
@override
int get ObjectStore_suspend_state_return_async_star_offset => 0x4a8;
int get ObjectStore_suspend_state_return_async_star_offset => 0x4b0;
@override
int get ObjectStore_suspend_state_suspend_sync_star_at_start_offset => 0x4b8;
int get ObjectStore_suspend_state_suspend_sync_star_at_start_offset => 0x4c0;
@override
int get ObjectStore_suspend_state_yield_async_star_offset => 0x4a0;
int get ObjectStore_suspend_state_yield_async_star_offset => 0x4a8;
@override
int get OneByteString_data_offset => 0x10;
@override
@@ -1715,39 +1715,39 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Object_tags_offset => 0x0;
@override
int get ObjectStore_double_type_offset => 0x1b0;
int get ObjectStore_double_type_offset => 0x1b8;
@override
int get ObjectStore_int_type_offset => 0x160;
int get ObjectStore_int_type_offset => 0x168;
@override
int get ObjectStore_record_field_names_offset => 0x428;
int get ObjectStore_record_field_names_offset => 0x430;
@override
int get ObjectStore_string_type_offset => 0x1d8;
int get ObjectStore_string_type_offset => 0x1e0;
@override
int get ObjectStore_type_type_offset => 0x138;
int get ObjectStore_type_type_offset => 0x140;
@override
int get ObjectStore_ffi_callback_code_offset => 0x520;
int get ObjectStore_ffi_callback_code_offset => 0x528;
@override
int get ObjectStore_suspend_state_await_offset => 0x478;
int get ObjectStore_suspend_state_await_offset => 0x480;
@override
int get ObjectStore_suspend_state_await_with_type_check_offset => 0x480;
int get ObjectStore_suspend_state_await_with_type_check_offset => 0x488;
@override
int get ObjectStore_suspend_state_handle_exception_offset => 0x4c0;
int get ObjectStore_suspend_state_handle_exception_offset => 0x4c8;
@override
int get ObjectStore_suspend_state_init_async_offset => 0x470;
int get ObjectStore_suspend_state_init_async_offset => 0x478;
@override
int get ObjectStore_suspend_state_init_async_star_offset => 0x498;
int get ObjectStore_suspend_state_init_async_star_offset => 0x4a0;
@override
int get ObjectStore_suspend_state_init_sync_star_offset => 0x4b0;
int get ObjectStore_suspend_state_init_sync_star_offset => 0x4b8;
@override
int get ObjectStore_suspend_state_return_async_offset => 0x488;
int get ObjectStore_suspend_state_return_async_offset => 0x490;
@override
int get ObjectStore_suspend_state_return_async_not_future_offset => 0x490;
int get ObjectStore_suspend_state_return_async_not_future_offset => 0x498;
@override
int get ObjectStore_suspend_state_return_async_star_offset => 0x4a8;
int get ObjectStore_suspend_state_return_async_star_offset => 0x4b0;
@override
int get ObjectStore_suspend_state_suspend_sync_star_at_start_offset => 0x4b8;
int get ObjectStore_suspend_state_suspend_sync_star_at_start_offset => 0x4c0;
@override
int get ObjectStore_suspend_state_yield_async_star_offset => 0x4a0;
int get ObjectStore_suspend_state_yield_async_star_offset => 0x4a8;
@override
int get OneByteString_data_offset => 0x10;
@override
@@ -194,12 +194,17 @@ class DeeplyImmutableValidator {
continue;
}
if (!checkResult.isImmutable) {
diagnosticReporter.report(
diag.ffiDeeplyImmutableFieldsMustBeDeeplyImmutable,
field.fileOffset,
field.name.text.length,
field.location!.file,
);
// Allow BigIntImpl since it's only violating "final Uint32List _digits"
// is actually never modified.
if (node.name != '_BigIntImpl' &&
node.enclosingLibrary.name != 'dart:core') {
diagnosticReporter.report(
diag.ffiDeeplyImmutableFieldsMustBeDeeplyImmutable,
field.fileOffset,
field.name.text.length,
field.location!.file,
);
}
}
if (!field.isFinal || field.isLate) {
diagnosticReporter.report(
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -1832,17 +1832,23 @@ class WorkSet : public StackResource {
};
void Object::EnsureDeeplyImmutable(Zone* zone) const {
WorkSet workset(Thread::Current(), zone);
auto thread = Thread::Current();
WorkSet workset(thread, zone);
workset.Add(*this);
Object& current = Object::Handle(zone);
Function& function = Function::Handle(zone);
Object& obj = Object::Handle(zone);
ObjectStore* object_store = thread->isolate_group()->object_store();
const intptr_t bigint_cid =
Class::Handle(zone, object_store->bigint_impl_class()).id();
while (workset.TakeAndPush(&current)) {
if (current.IsSmi() || current.IsNull() || current.IsCanonical() ||
current.IsDeeplyImmutable() ||
IsTypedDataBaseClassId(current.GetClassId())) {
IsTypedDataBaseClassId(current.GetClassId()) ||
current.GetClassId() == bigint_cid) {
workset.PopAndProcessCompletedClosuresAndContexts(&obj);
continue;
}
+4
View File
@@ -446,6 +446,10 @@ void ObjectStore::LazyInitCoreMembers() {
ASSERT(!field.IsNull());
enum_name_field_.store(field.ptr());
cls = core_lib.LookupClassAllowPrivate(Symbols::_BigIntImpl());
ASSERT(!cls.IsNull());
bigint_impl_class_.store(cls.ptr());
auto& function = Function::Handle(zone);
function = core_lib.LookupFunctionAllowPrivate(Symbols::_objectHashCode());
+1
View File
@@ -47,6 +47,7 @@ class ObjectPointerVisitor;
LAZY_CORE(Class, list_class) \
LAZY_CORE(Class, map_class) \
LAZY_CORE(Class, set_class) \
LAZY_CORE(Class, bigint_impl_class) \
LAZY_CORE(Type, non_nullable_list_rare_type) \
LAZY_CORE(Type, non_nullable_map_rare_type) \
LAZY_CORE(Field, enum_index_field) \
+1
View File
@@ -29,6 +29,7 @@ class ObjectPointerVisitor;
V(StateError, "StateError") \
V(AssertionError, "_AssertionError") \
V(AssignIndexToken, "[]=") \
V(_BigIntImpl, "_BigIntImpl") \
V(Bool, "bool") \
V(BooleanExpression, "boolean expression") \
V(BoundsCheckForPartialInstantiation, "_boundsCheckForPartialInstantiation") \
@@ -0,0 +1,32 @@
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:_internal" show patch;
import 'dart:_vm' show FinalThreadLocal, ThreadLocal;
import "dart:typed_data" show Uint32List;
@patch
class _BigIntImpl {
@patch
static _DivRemResult get _cachedDivRemResult =>
_cachedDivRemResultValue.value;
@patch
static RegExp get _parseRE => _parseREThreadLocal.value;
@pragma('vm:shared')
static final _cachedDivRemResultValue = FinalThreadLocal<_DivRemResult>(
() => _DivRemResult(),
);
@pragma('vm:shared')
static final _parseREThreadLocal = FinalThreadLocal<RegExp>(
() => RegExp(
r'^\s*([+-]?)((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$',
caseSensitive: false,
),
);
}
@@ -65,13 +65,55 @@ int _min(int a, int b) => a < b ? a : b;
/// Allocate a new digits list of even length.
Uint32List _newDigits(int length) => Uint32List(length + (length & 1));
class _DivRemResult {
Uint32List? get dividendDigits => _dividendDigits;
int? get dividendUsed => _dividendUsed;
Uint32List? get divisorDigits => _divisorDigits;
int? get divisorUsed => _divisorUsed;
Uint32List get quoRemDigits => _quoRemDigits;
int get quoRemUsed => _quoRemUsed;
int get remUsed => _remUsed;
int get rem_nsh => _rem_nsh;
void update(
Uint32List dividendDigits,
int dividendUsed,
Uint32List divisorDigits,
int divisorUsed,
Uint32List quoRemDigits,
int quoRemUsed,
int remUsed,
int rem_nsh,
) {
_dividendUsed = dividendUsed;
_divisorDigits = divisorDigits;
_divisorUsed = divisorUsed;
_quoRemDigits = quoRemDigits;
_quoRemUsed = quoRemUsed;
_remUsed = remUsed;
_rem_nsh = rem_nsh;
}
Uint32List? _dividendDigits;
int? _dividendUsed;
Uint32List? _divisorDigits;
int? _divisorUsed;
late Uint32List _quoRemDigits;
late int _quoRemUsed;
late int _remUsed;
late int _rem_nsh;
}
/**
* An implementation for the arbitrarily large integer.
*
* The integer number is represented by a sign, an array of 32-bit unsigned
* integers in little endian format, and a number of used digits in that array.
*/
class _BigIntImpl implements BigInt {
@pragma("vm:entry-point")
final class _BigIntImpl implements BigInt {
// Bits per digit.
static const int _digitBits = 32;
static const int _digitBase = 1 << _digitBits;
@@ -81,14 +123,22 @@ class _BigIntImpl implements BigInt {
static const int _halfDigitBits = _digitBits >> 1;
static const int _halfDigitMask = (1 << _halfDigitBits) - 1;
@pragma('vm:shared')
static final _BigIntImpl zero = _BigIntImpl._fromInt(0);
@pragma('vm:shared')
static final _BigIntImpl one = _BigIntImpl._fromInt(1);
@pragma('vm:shared')
static final _BigIntImpl two = _BigIntImpl._fromInt(2);
@pragma('vm:shared')
static final _BigIntImpl _minusOne = -one;
@pragma('vm:shared')
static final _BigIntImpl _oneDigitMask = _BigIntImpl._fromInt(_digitMask);
@pragma('vm:shared')
static final _BigIntImpl _twoDigitMask = (one << (2 * _digitBits)) - one;
@pragma('vm:shared')
static final _BigIntImpl _oneBillion = _BigIntImpl._fromInt(1000000000);
static const int _minInt = -0x8000000000000000;
static const int _maxInt = 0x7fffffffffffffff;
@@ -106,14 +156,7 @@ class _BigIntImpl implements BigInt {
);
// Result cache for last _divRem call.
static Uint32List? _lastDividendDigits;
static int? _lastDividendUsed;
static Uint32List? _lastDivisorDigits;
static int? _lastDivisorUsed;
static late Uint32List _lastQuoRemDigits;
static late int _lastQuoRemUsed;
static late int _lastRemUsed;
static late int _lastRem_nsh;
external static _DivRemResult get _cachedDivRemResult;
/// Whether this bigint is negative.
final bool _isNegative;
@@ -125,6 +168,9 @@ class _BigIntImpl implements BigInt {
/// be strictly greater than `_used`.
/// Also, `_digits.length` must always be even, because intrinsics on 64-bit
/// platforms may process a digit pair as a 64-bit value.
///
/// Uint32List normally prevent pragma('vm:deeply-immutable')-check, but
/// this class is explicitly allow-listed in deeply_immutable.dart.
final Uint32List _digits;
/// The number of used entries in [_digits].
@@ -322,10 +368,7 @@ class _BigIntImpl implements BigInt {
return _parseRadix(nonNullMatch, radix, isNegative);
}
static RegExp _parseRE = RegExp(
r'^\s*([+-]?)((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$',
caseSensitive: false,
);
external static RegExp get _parseRE;
/// Finds the amount significant digits in the provided [digits] array.
static int _normalize(int used, Uint32List digits) {
@@ -1389,11 +1432,12 @@ class _BigIntImpl implements BigInt {
_divRem(other);
// Return quotient, i.e.
// _lastQuoRem_digits[_lastRem_used.._lastQuoRem_used-1] with proper sign.
var lastQuo_used = _lastQuoRemUsed - _lastRemUsed;
var lastQuo_used =
_cachedDivRemResult.quoRemUsed - _cachedDivRemResult.remUsed;
var quo_digits = _cloneDigits(
_lastQuoRemDigits,
_lastRemUsed,
_lastQuoRemUsed,
_cachedDivRemResult.quoRemDigits,
_cachedDivRemResult.remUsed,
_cachedDivRemResult.quoRemUsed,
lastQuo_used,
);
var quo = _BigIntImpl._(false, lastQuo_used, quo_digits);
@@ -1414,14 +1458,14 @@ class _BigIntImpl implements BigInt {
// Return remainder, i.e.
// denormalized _lastQuoRem_digits[0.._lastRem_used-1] with proper sign.
var remDigits = _cloneDigits(
_lastQuoRemDigits,
_cachedDivRemResult.quoRemDigits,
0,
_lastRemUsed,
_lastRemUsed,
_cachedDivRemResult.remUsed,
_cachedDivRemResult.remUsed,
);
var rem = _BigIntImpl._(false, _lastRemUsed, remDigits);
if (_lastRem_nsh > 0) {
rem = rem >> _lastRem_nsh; // Denormalize remainder.
var rem = _BigIntImpl._(false, _cachedDivRemResult.remUsed, remDigits);
if (_cachedDivRemResult.rem_nsh > 0) {
rem = rem >> _cachedDivRemResult.rem_nsh; // Denormalize remainder.
}
if (_isNegative && (rem._used > 0)) {
rem = -rem;
@@ -1431,18 +1475,18 @@ class _BigIntImpl implements BigInt {
/// Computes this ~/ other and this.remainder(other).
///
/// Stores the result in [_lastQuoRemDigits], [_lastQuoRemUsed] and
/// [_lastRemUsed]. The [_lastQuoRemDigits] contains the digits of *both*, the
/// Stores the result in [_cachedDivRemResult]'s [quoRemDigits], [quoRemUsed]
/// and [remUsed]. The [quoRemDigits] contains the digits of *both*, the
/// quotient and the remainder.
///
/// Caches the input to avoid doing the work again when users write
/// `a ~/ b` followed by a `a % b`.
void _divRem(_BigIntImpl other) {
// Check if result is already cached.
if ((this._used == _lastDividendUsed) &&
(other._used == _lastDivisorUsed) &&
identical(this._digits, _lastDividendDigits) &&
identical(other._digits, _lastDivisorDigits)) {
if ((this._used == _cachedDivRemResult.dividendUsed) &&
(other._used == _cachedDivRemResult.divisorUsed) &&
identical(this._digits, _cachedDivRemResult.dividendDigits) &&
identical(other._digits, _cachedDivRemResult.divisorDigits)) {
return;
}
assert(_used >= other._used);
@@ -1561,14 +1605,16 @@ class _BigIntImpl implements BigInt {
i -= d0;
}
// Cache result.
_lastDividendDigits = _digits;
_lastDividendUsed = _used;
_lastDivisorDigits = other._digits;
_lastDivisorUsed = other._used;
_lastQuoRemDigits = resultDigits;
_lastQuoRemUsed = resultUsed;
_lastRemUsed = yUsed;
_lastRem_nsh = nsh;
_cachedDivRemResult.update(
_digits,
_used,
other._digits,
other._used,
resultDigits,
resultUsed,
yUsed,
nsh,
);
}
// Customized version of _rem() minimizing allocations for use in reduction.
@@ -0,0 +1,23 @@
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:_internal" show patch;
import "dart:typed_data" show Uint32List;
@patch
class _BigIntImpl {
@patch
static _DivRemResult get _cachedDivRemResult => _cachedDivRemResultValue;
@patch
static RegExp get _parseRE => _parseREValue;
static final _cachedDivRemResultValue = _DivRemResult();
static final RegExp _parseREValue = RegExp(
r'^\s*([+-]?)((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$',
caseSensitive: false,
);
}
+3
View File
@@ -47,6 +47,7 @@
"_internal/vm/lib/array_patch.dart",
"_internal/vm/lib/core_patch.dart",
"_internal/vm_shared/lib/bigint_patch.dart",
"_internal/vm/lib/bigint_patch_patch.dart",
"_internal/vm_shared/lib/bool_patch.dart",
"_internal/vm_shared/lib/date_patch.dart",
"_internal/vm_shared/lib/integers_patch.dart",
@@ -146,6 +147,7 @@
"_internal/vm_shared/lib/map_patch.dart",
"_internal/vm_shared/lib/null_patch.dart",
"_internal/wasm/lib/array_patch.dart",
"_internal/wasm/lib/bigint_patch_patch.dart",
"_internal/wasm/lib/core_patch.dart",
"_internal/wasm/lib/date_patch_patch.dart",
"_internal/wasm/lib/int_common_patch.dart",
@@ -209,6 +211,7 @@
"_internal/vm_shared/lib/map_patch.dart",
"_internal/vm_shared/lib/null_patch.dart",
"_internal/wasm/lib/array_patch.dart",
"_internal/wasm/lib/bigint_patch_patch.dart",
"_internal/wasm/lib/core_patch.dart",
"_internal/wasm/lib/date_patch_patch.dart",
"_internal/wasm/lib/int_common_patch.dart",
+3
View File
@@ -53,6 +53,7 @@ vm_common:
- "_internal/vm/lib/array_patch.dart"
- "_internal/vm/lib/core_patch.dart"
- "_internal/vm_shared/lib/bigint_patch.dart"
- "_internal/vm/lib/bigint_patch_patch.dart"
- "_internal/vm_shared/lib/bool_patch.dart"
- "_internal/vm_shared/lib/date_patch.dart"
- "_internal/vm_shared/lib/integers_patch.dart"
@@ -135,6 +136,7 @@ wasm:
- _internal/vm_shared/lib/map_patch.dart
- _internal/vm_shared/lib/null_patch.dart
- _internal/wasm/lib/array_patch.dart
- _internal/wasm/lib/bigint_patch_patch.dart
- _internal/wasm/lib/core_patch.dart
- _internal/wasm/lib/date_patch_patch.dart
- _internal/wasm/lib/int_common_patch.dart
@@ -182,6 +184,7 @@ wasm_js_compatibility:
- _internal/vm_shared/lib/map_patch.dart
- _internal/vm_shared/lib/null_patch.dart
- _internal/wasm/lib/array_patch.dart
- _internal/wasm/lib/bigint_patch_patch.dart
- _internal/wasm/lib/core_patch.dart
- _internal/wasm/lib/date_patch_patch.dart
- _internal/wasm/lib/int_common_patch.dart
+104
View File
@@ -71,6 +71,7 @@ main(List<String> args) {
testBytesBuilder();
testRegExp();
testContentType();
testBigInt();
print("All tests completed :)");
}
@@ -562,3 +563,106 @@ void testContentType() {
);
});
}
///
expectSum(aString, bString, expectedString) {
BigInt a = BigInt.parse(aString, radix: 16);
BigInt b = BigInt.parse(bString, radix: 16);
BigInt expected = BigInt.parse(expectedString, radix: 16);
BigInt actual = a + b;
Expect.equals(expected, actual);
}
expectDifference(aString, bString, expectedString) {
BigInt a = BigInt.parse(aString, radix: 16);
BigInt b = BigInt.parse(bString, radix: 16);
BigInt expected = BigInt.parse(expectedString, radix: 16);
BigInt actual = a - b;
Expect.equals(expected, actual);
}
expectQuotient(aString, bString, expectedString) {
BigInt a = BigInt.parse(aString, radix: 16);
BigInt b = BigInt.parse(bString, radix: 16);
BigInt expected = BigInt.parse(expectedString, radix: 16);
BigInt actual = a ~/ b;
Expect.equals(expected, actual);
}
expectRemainder(aString, bString, expectedString) {
BigInt a = BigInt.parse(aString, radix: 16);
BigInt b = BigInt.parse(bString, radix: 16);
BigInt expected = BigInt.parse(expectedString, radix: 16);
BigInt actual = a % b;
Expect.equals(expected, actual);
}
expectShifted(aString, n, expectedString) {
BigInt a = BigInt.parse(aString, radix: 16);
BigInt expected = BigInt.parse(expectedString, radix: 16);
BigInt actual = a << n;
Expect.equals(expected, actual);
}
void testBigInt() {
IsolateGroup.runSync(() {
Expect.equals("${BigInt.zero}", "0");
Expect.equals("${BigInt.one}", "1");
Expect.equals("${BigInt.two}", "2");
expectSum(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"1ad478de9e340aba6cb25ae8dbb531d2bc0105fa0",
);
expectSum(
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"1ad478de9e340aba6cb25ae8dbb531d2bc0105fa0",
);
expectDifference(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"3b04b6a8ac2e74f9845c182e303993e0efa8184",
);
expectDifference(
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"-3b04b6a8ac2e74f9845c182e303993e0efa8184",
);
expectRemainder(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"3b04b6a8ac2e74f9845c182e303993e0efa8184",
);
expectRemainder(
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
);
expectQuotient(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"1",
);
expectQuotient(
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"0",
);
expectShifted(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
0,
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
);
expectShifted(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
1,
"1b0f7d9546e0392f6636b70109e56b669cf0ae124",
);
});
}