Seven-Streams opened a new pull request, #815:
URL: https://github.com/apache/tvm-ffi/pull/815
[FEAT][RUST] Native BigInt arithmetic, ported from the C++ int_ops
Stacked on #804, which adds the `BigInt` value type with C++-backed
construction. Only the commits after that branch belong here.
## Summary
`tvm_ffi::BigInt` gains arithmetic with unbounded signed integer semantics,
implemented in Rust because the C ABI exposes no arithmetic entry points and
the C++ operators are header-only.
- Operators `+ - * / % & | ^ << >>` for owned and borrowed operands with
`i64`
accepted on either side, compound assignment, unary `-` and `!`. `/` and
`%`
truncate toward zero; bitwise operations sign-extend infinitely. Like the
primitive operators, `/`, `%`, `<<` and `>>` panic on a zero divisor or a
negative count.
- Fallible forms for ffi callbacks, where a panic cannot unwind: `try_div`,
`try_rem`, `div_rem`, `floor_div`, `floor_mod`, `try_shl`, `try_shr`. A
zero
divisor raises the new `ZERO_DIVISION_ERROR` kind, a negative shift count
`ValueError`, and a shift that cannot be allocated `OverflowError`.
- `FromStr` and `Display` in decimal, `to_f64` rounding to nearest with ties
to
even, and `is_zero`.
`big_int/int_ops.rs` mirrors C++ `details::int_ops` in `big_int.h` one for
one, so the two can be reviewed side by side: sign-extended add, subtract and
negate; multiply on `u128` with the signed correction; shifts; scalar
division
in radix 2^32 with the normalized 64-bit divisor path; Knuth's Algorithm D
for
multiword division; the floor variants; and the double conversion.
The memory model differs from C++ on purpose. C++ builds each result inside a
freshly allocated object and prunes its length afterwards, which in Rust
would
need a private allocator with a capacity prefix and its own deleter. Here
every
algorithm writes into `WordsBuf`, a 32-word stack buffer that spills to a
`Vec`, and finishes with `from_words`, which allocates the object at its
final
length through `ObjectArc::new_with_extra_items` as `Shape` and `String` do.
A result that fits `i64` never touches the heap, the crate's generic deleter
releases every object, and the loops themselves are unchanged.
Benchmarks against the C++ operators on the same machine (ns/op, lower is
better):
| operation | Rust | C++ |
| --- | ---: | ---: |
| add, 4 words + 1 | 23.5 | 15.3 |
| multiply, 4 x 4 words | 31.1 | 36.9 |
| divide, 4 words / 1 | 41.3 | 34.3 |
| divide, 4 words / 4 | 64.2 | 61.5 |
| shift left by 70 | 27.1 | 17.4 |
| heap construction | 15.5 | 13.0 |
| to decimal, 4 words | 192 | 628 |
Allocation now costs about the same on both sides; the remaining gap on add
and shift is loop code generation, which the C++ compiler turns into a
straight carry chain.
## Testing
- `cargo test -p tvm-ffi`: **286 passed**. `tests/test_big_int.rs` grows to
26
tests, 13 of them for arithmetic: the cases ported from
`tests/cpp/test_big_int.cc` (promotion and demotion at the `i64` boundary,
mixed `i64` operands, compound assignment); signed division in every sign
quadrant with inline and multiword operands and every error path;
shift-count
overflow and negative counts; an independent fixture with decimal
expectations
computed in Python; decimal parsing; Algorithm D estimate clamping, one and
two corrections, add-back and the maximal normalization shift; scalar
division boundaries including the two-correction path and `|INT64_MIN|`;
the
small-divisor remainder fast path against int32 boundary answers; a
1500-round random property test (ring identities, division identities with
the truncating and flooring sign rules, shifts as multiplication and floor
division, text and double round trips, cross-checked against `i128` where
it
applies); a Rust-allocated object held and released by a C++ container and
compared and hashed by C++ against a runtime-built twin; and results wider
than the stack buffer.
- An offline interop harness passes: Rust-built values through the C++
`+`, `*`, `/` and `<<`, agreement of C++ `operator<<` with Rust `Display`,
`ZeroDivisionError` / `TypeError` / `ValueError` crossing from C++, C++
calling Rust callbacks with C++-built values, and reference counts back to
one afterwards.
- `rustfmt` and `clippy` clean on the new files. No C++ changes.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]