Skip to content

[capi] Fix i128Nanoseconds #372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ jobs:
pushd temporal_capi/tests/c && make && popd
pushd temporal_capi/tests/cpp && make && popd

- name: temporal_capi tests
run: cargo test -p temporal_capi --all-features
# There's no guarantee that dependencies are no_std unless you test with a toolchain without `std`
- name: Install no_std toolchain
run: rustup target add thumbv7m-none-eabi
Expand Down
2 changes: 1 addition & 1 deletion temporal_capi/bindings/c/I128Nanoseconds.d.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions temporal_capi/bindings/cpp/temporal_rs/I128Nanoseconds.d.hpp

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 48 additions & 9 deletions temporal_capi/src/instant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ pub mod ffi {
#[diplomat::opaque]
pub struct Instant(pub temporal_rs::Instant);

/// For portability, we use two i64s instead of an i128.
/// The sign is extracted first before
/// appending the high/low segments to each other.
/// For portability, we use two u64s instead of an i128.
/// The high bit of the u64 is the sign.
/// This cannot represent i128::MIN, and has a -0, but those are largely
/// irrelevant for this purpose.
///
/// This could potentially instead be a bit-by-bit split, or something else
#[derive(Debug, Copy, Clone)]
pub struct I128Nanoseconds {
pub high: i64,
pub high: u64,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this is a breaking change for capi, but I've mitigated it in v8.

pub low: u64,
}

Expand Down Expand Up @@ -165,12 +167,15 @@ pub mod ffi {
}
}

const U64_HIGH_BIT_MASK: u64 = 1 << 63;

impl From<ffi::I128Nanoseconds> for i128 {
fn from(ns: ffi::I128Nanoseconds) -> Self {
let is_neg = ns.high < 0;
let ns_high_abs = ns.high.unsigned_abs() as u128;
let is_neg = (ns.high & U64_HIGH_BIT_MASK) != 0;
// Remove the bitflag
let ns_high = (ns.high & !U64_HIGH_BIT_MASK) as u128;
// Stick them together
let total = ((ns_high_abs << 64) + ns.low as u128) as i128;
let total = ((ns_high << 64) + ns.low as u128) as i128;
// Reintroduce the sign
if is_neg {
-total
Expand All @@ -182,13 +187,47 @@ impl From<ffi::I128Nanoseconds> for i128 {

impl From<i128> for ffi::I128Nanoseconds {
fn from(ns: i128) -> Self {
debug_assert!(
ns != i128::MIN,
"temporal_rs should never produce i128::MIN, it is out of valid range"
);
let is_neg = ns < 0;
let ns = ns.unsigned_abs();

let high = (ns >> 64) as i64;
let high = (ns >> 64) as u64;
let low = (ns & u64::MAX as u128) as u64;
let high = if is_neg { -high } else { high };
let high = if (is_neg) {
high | U64_HIGH_BIT_MASK
} else {
high
};

ffi::I128Nanoseconds { high, low }
}
}

#[test]
fn test_i128_roundtrip() {
#[track_caller]
fn roundtrip(x: i128) {
let ns = ffi::I128Nanoseconds::from(x);
let round = i128::from(ns);
assert_eq!(x, round, "{x} does not roundtrip via {ns:?}");
}

roundtrip(0);
roundtrip(-1);
roundtrip(1);
roundtrip(100);
roundtrip(1000);
roundtrip(-1000);
roundtrip(1000000000);
roundtrip(-100000000);
roundtrip(u64::MAX as i128);
roundtrip(-(u64::MIN as i128));
roundtrip(100 * (u64::MAX as i128));
roundtrip(-100 * (u64::MAX as i128));
roundtrip(i128::MIN + 1);
roundtrip(i128::MAX);
roundtrip(i128::MAX - 10);
}
Loading