Skip to content

Commit 8644ce6

Browse files
authored
[workerd-cxx] kj::Date support (#64)
represented as `KjDate` object on rust side.
1 parent 7cab136 commit 8644ce6

24 files changed

+724
-73
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
if: matrix.os.name == 'macos'
3939
run: sudo xcode-select -s "/Applications/Xcode_16.3.app"
4040
- run: bazel --version
41-
- run: bazel test ... --verbose_failures ${{matrix.os.name == 'macos' && '--xcode_version_config=tools/bazel:github_actions_xcodes' || ''}}
41+
- run: bazel test ... --verbose_failures --test_output=errors ${{matrix.os.name == 'macos' && '--xcode_version_config=tools/bazel:github_actions_xcodes' || ''}}
4242

4343

4444
clippy:
@@ -57,7 +57,7 @@ jobs:
5757
steps:
5858
- uses: actions/checkout@v4
5959
- run: sudo apt-get install lld
60-
- run: bazel test --config=asan ... --verbose_failures
60+
- run: bazel test --config=asan ... --verbose_failures --test_output=errors
6161

6262
clang-tidy:
6363
name: Clang Tidy

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ The following smart pointers can be used in ffi layers:
3636
### KJ Data Structures Integration
3737

3838
- `kj::Maybe<T>` - corresponds to `kj_rs::KjMaybe<T>`.
39+
- `kj::Date` - corresponds to `kj_rs::KjDate`.
3940

4041
### KJ/Rust conversion layer
4142

gen/src/include.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub struct Includes<'a> {
4343
pub basetsd: bool,
4444
pub sys_types: bool,
4545
pub content: Content<'a>,
46+
pub kj_date: bool,
4647
pub kj_rs: bool,
4748
pub jsg: bool,
4849
}
@@ -107,6 +108,7 @@ pub fn write(out: &mut OutFile) {
107108
vector,
108109
basetsd,
109110
sys_types,
111+
kj_date,
110112
kj_rs,
111113
jsg,
112114
content: _,
@@ -190,6 +192,9 @@ pub fn write(out: &mut OutFile) {
190192
writeln!(out, "#include <ranges>");
191193
writeln!(out, "#endif");
192194
}
195+
if kj_date && !cxx_header {
196+
writeln!(out, "#include \"kj-rs/date.h\"");
197+
}
193198
if kj_rs && !cxx_header {
194199
writeln!(out, "#include \"kj-rs/kj-rs.h\"");
195200
}

gen/src/write.rs

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
240240
| Type::KjArc(_) => {
241241
out.include.kj_rs = true;
242242
}
243+
Type::KjDate(_) => {
244+
out.include.kj_date = true;
245+
}
243246
}
244247
}
245248
}
@@ -760,15 +763,7 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
760763
if i > 0 || efn.receiver.is_some() {
761764
write!(out, ", ");
762765
}
763-
if arg.ty == RustString {
764-
write_type_space(out, &arg.ty);
765-
write!(out, "const *{}", arg.name.cxx);
766-
} else if let Type::RustVec(_) = arg.ty {
767-
write_type_space(out, &arg.ty);
768-
write!(out, "const *{}", arg.name.cxx);
769-
} else {
770-
write_extern_arg(out, arg);
771-
}
766+
write_cxx_shim_sig_arg(out, arg);
772767
}
773768
let indirect_return = indirect_return(efn, out.types);
774769
if indirect_return {
@@ -840,6 +835,9 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
840835
write_type(out, ty);
841836
write!(out, ">::repr(");
842837
}
838+
Some(Type::KjDate(_)) => {
839+
write!(out, "::kj_rs::repr::toNanos(");
840+
}
843841
_ => {}
844842
}
845843
match &efn.receiver {
@@ -850,34 +848,13 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
850848
if i > 0 {
851849
write!(out, ", ");
852850
}
853-
if let Type::RustBox(_) = &arg.ty {
854-
write_type(out, &arg.ty);
855-
write!(out, "::from_raw({})", arg.name.cxx);
856-
} else if let Type::UniquePtr(_) = &arg.ty {
857-
write_type(out, &arg.ty);
858-
write!(out, "({})", arg.name.cxx);
859-
} else if arg.ty == RustString {
860-
out.builtin.unsafe_bitcopy = true;
861-
write!(
862-
out,
863-
"::rust::String(::rust::unsafe_bitcopy, *{})",
864-
arg.name.cxx,
865-
);
866-
} else if let Type::RustVec(_) = arg.ty {
867-
out.builtin.unsafe_bitcopy = true;
868-
write_type(out, &arg.ty);
869-
write!(out, "(::rust::unsafe_bitcopy, *{})", arg.name.cxx);
870-
} else if out.types.needs_indirect_abi(&arg.ty) {
871-
out.include.utility = true;
872-
write!(out, "::std::move(*{})", arg.name.cxx);
873-
} else {
874-
write!(out, "{}", arg.name.cxx);
875-
}
851+
write_cxx_shim_arg(out, arg);
876852
}
877853
write!(out, ")");
878854
match &efn.ret {
879855
Some(Type::RustBox(_)) => write!(out, ".into_raw()"),
880856
Some(Type::UniquePtr(_)) => write!(out, ".release()"),
857+
Some(Type::KjDate(_)) => write!(out, ")"),
881858
Some(Type::Str(_) | Type::SliceRef(_)) if !indirect_return => write!(out, ")"),
882859
_ => {}
883860
}
@@ -898,6 +875,63 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
898875
out.end_block(Block::ExternC);
899876
}
900877

878+
/// Write argument as part of cxx function call inside of cxx shim
879+
fn write_cxx_shim_arg(out: &mut OutFile, arg: &Var) {
880+
match &arg.ty {
881+
Type::RustBox(_) => {
882+
write_type(out, &arg.ty);
883+
write!(out, "::from_raw({})", arg.name.cxx);
884+
}
885+
Type::UniquePtr(_) => {
886+
write_type(out, &arg.ty);
887+
write!(out, "({})", arg.name.cxx);
888+
}
889+
Type::RustVec(_) => {
890+
out.builtin.unsafe_bitcopy = true;
891+
write_type(out, &arg.ty);
892+
write!(out, "(::rust::unsafe_bitcopy, *{})", arg.name.cxx);
893+
}
894+
Type::KjDate(_) => {
895+
write!(out, "::kj_rs::repr::fromNanos({})", arg.name.cxx);
896+
}
897+
t if t == RustString => {
898+
out.builtin.unsafe_bitcopy = true;
899+
write!(
900+
out,
901+
"::rust::String(::rust::unsafe_bitcopy, *{})",
902+
arg.name.cxx,
903+
);
904+
}
905+
t if out.types.needs_indirect_abi(t) => {
906+
out.include.utility = true;
907+
write!(out, "::std::move(*{})", arg.name.cxx);
908+
}
909+
_ => {
910+
write!(out, "{}", arg.name.cxx);
911+
}
912+
}
913+
}
914+
915+
/// Write argument as part of cxx shim function signature
916+
fn write_cxx_shim_sig_arg(out: &mut OutFile, arg: &Var) {
917+
match &arg.ty {
918+
Type::RustVec(_) => {
919+
write_type_space(out, &arg.ty);
920+
write!(out, "const *{}", arg.name.cxx);
921+
}
922+
Type::KjDate(_) => {
923+
write!(out, "::std::int64_t {}", arg.name.cxx);
924+
}
925+
t if t == RustString => {
926+
write_type_space(out, &arg.ty);
927+
write!(out, "const *{}", arg.name.cxx);
928+
}
929+
_ => {
930+
write_extern_arg(out, arg);
931+
}
932+
}
933+
}
934+
901935
fn write_function_pointer_trampoline(out: &mut OutFile, efn: &ExternFn, var: &Pair, f: &Signature) {
902936
let r_trampoline = mangle::r_trampoline(efn, var, out.types);
903937
let indirect_call = true;
@@ -1216,6 +1250,9 @@ fn write_extern_return_type_space(out: &mut OutFile, ty: &Option<Type>) {
12161250
out.builtin.repr_fat = true;
12171251
write!(out, "::rust::repr::Fat ");
12181252
}
1253+
Some(Type::KjDate(_)) => {
1254+
write!(out, "::std::int64_t ");
1255+
}
12191256
Some(ty) if out.types.needs_indirect_abi(ty) => write!(out, "void "),
12201257
_ => write_return_type(out, ty),
12211258
}
@@ -1341,6 +1378,7 @@ fn write_type(out: &mut OutFile, ty: &Type) {
13411378
write!(out, ", {}>", &a.len);
13421379
}
13431380
Type::Void(_) => write!(out, "void"),
1381+
Type::KjDate(_) => write!(out, "::kj::Date"),
13441382
Type::Future(ty) => {
13451383
write!(out, "kj::Promise<");
13461384
write_type(out, &ty.output);
@@ -1387,6 +1425,7 @@ fn write_space_after_type(out: &mut OutFile, ty: &Type) {
13871425
| Type::WeakPtr(_)
13881426
| Type::Str(_)
13891427
| Type::KjMaybe(_)
1428+
| Type::KjDate(_)
13901429
| Type::CxxVector(_)
13911430
| Type::RustVec(_)
13921431
| Type::SliceRef(_)

kj-rs/convert.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,6 @@ struct RustCopy {
268268
return from(&ptr);
269269
}
270270

271-
272271
/// from<RustCopy>(rustSliceOfStrs) - Copy slice of strs to null-terminated KJ strings
273272
static kj::Array<kj::String> into(::rust::Slice<::rust::str> slice) {
274273
auto res = kj::heapArrayBuilder<kj::String>(slice.size());

kj-rs/date.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#pragma once
2+
3+
#include <kj/time.h>
4+
5+
#include <cstdint>
6+
7+
namespace kj_rs {
8+
namespace repr {
9+
10+
inline std::int64_t toNanos(kj::Date date) {
11+
return (date - kj::origin<kj::Date>()) / kj::NANOSECONDS;
12+
}
13+
14+
inline kj::Date fromNanos(std::int64_t nanos) {
15+
return kj::origin<kj::Date>() + (nanos * kj::NANOSECONDS);
16+
}
17+
18+
} // namespace repr
19+
} // namespace kj_rs

kj-rs/date.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use core::fmt;
2+
use std::time::{Duration, SystemTime};
3+
4+
/// Represents a `kj::Date` from the KJ library.
5+
///
6+
/// Like C++ represents a point in time as nanoseconds since the Unix epoch (January 1, 1970 UTC).
7+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8+
pub struct KjDate {
9+
/// Nanoseconds since Unix epoch (January 1, 1970 UTC)
10+
nanoseconds: i64,
11+
}
12+
13+
impl KjDate {
14+
/// Creates a new `KjDate` representing the Unix epoch (January 1, 1970 UTC).
15+
#[inline]
16+
#[must_use]
17+
pub const fn unix_epoch() -> Self {
18+
Self { nanoseconds: 0 }
19+
}
20+
21+
/// Returns the nanoseconds since Unix epoch.
22+
/// This is the only public accessor method needed.
23+
#[inline]
24+
#[must_use]
25+
pub const fn nanoseconds(&self) -> i64 {
26+
self.nanoseconds
27+
}
28+
}
29+
30+
impl Default for KjDate {
31+
/// Returns the Unix epoch as the default date.
32+
fn default() -> Self {
33+
Self::unix_epoch()
34+
}
35+
}
36+
37+
impl fmt::Debug for KjDate {
38+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39+
write!(
40+
f,
41+
"KjDate({:?})",
42+
std::convert::Into::<SystemTime>::into(*self)
43+
)
44+
}
45+
}
46+
47+
impl From<i64> for KjDate {
48+
#[inline]
49+
fn from(nanoseconds: i64) -> Self {
50+
Self { nanoseconds }
51+
}
52+
}
53+
54+
impl From<KjDate> for i64 {
55+
#[inline]
56+
fn from(date: KjDate) -> Self {
57+
date.nanoseconds
58+
}
59+
}
60+
61+
impl From<SystemTime> for KjDate {
62+
fn from(time: SystemTime) -> Self {
63+
match time.duration_since(std::time::UNIX_EPOCH) {
64+
Ok(duration) => {
65+
let nanoseconds = i64::try_from(duration.as_nanos()).unwrap_or(i64::MAX);
66+
Self { nanoseconds }
67+
}
68+
Err(err) => {
69+
let duration_before = err.duration();
70+
let nanoseconds = -(i64::try_from(duration_before.as_nanos()).unwrap_or(i64::MAX));
71+
Self { nanoseconds }
72+
}
73+
}
74+
}
75+
}
76+
77+
impl From<KjDate> for SystemTime {
78+
fn from(date: KjDate) -> Self {
79+
let duration =
80+
Duration::from_nanos(u64::try_from(date.nanoseconds.abs()).unwrap_or(u64::MAX));
81+
82+
if date.nanoseconds >= 0 {
83+
std::time::UNIX_EPOCH + duration
84+
} else {
85+
std::time::UNIX_EPOCH - duration
86+
}
87+
}
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
94+
#[test]
95+
fn test_unix_epoch() {
96+
let epoch = KjDate::unix_epoch();
97+
assert_eq!(epoch.nanoseconds(), 0);
98+
}
99+
100+
#[test]
101+
fn test_from_nanoseconds() {
102+
let date = KjDate::from_nanoseconds(1000000000);
103+
assert_eq!(date.nanoseconds(), 1000000000);
104+
}
105+
106+
#[test]
107+
fn test_ordering() {
108+
let earlier = KjDate::from_nanoseconds(1000);
109+
let later = KjDate::from_nanoseconds(2000);
110+
assert!(earlier < later);
111+
}
112+
113+
#[test]
114+
fn test_default() {
115+
let default_date = KjDate::default();
116+
assert_eq!(default_date, KjDate::unix_epoch());
117+
}
118+
119+
#[test]
120+
fn test_system_time_conversion() {
121+
let date = KjDate::from_nanoseconds(1000000000);
122+
let system_time = date.to_system_time();
123+
let converted_back = KjDate::from(system_time);
124+
assert_eq!(date, converted_back);
125+
}
126+
}

kj-rs/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use awaiter::WakerRef;
33

44
pub use crate::ffi::KjWaker;
55
pub use awaiter::PromiseAwaiter;
6+
pub use date::KjDate;
67
pub use future::FuturePollStatus;
78
pub use maybe::repr::KjMaybe;
89
pub use own::repr::KjOwn;
@@ -11,9 +12,10 @@ pub use promise::KjPromiseNodeImpl;
1112
pub use promise::OwnPromiseNode;
1213
pub use promise::PromiseFuture;
1314
pub use promise::new_callbacks_promise_future;
14-
pub use repr::{KjArc, KjRc};
15+
pub use refcount::repr::{KjArc, KjRc};
1516

1617
mod awaiter;
18+
mod date;
1719
mod future;
1820
pub mod maybe;
1921
mod own;

0 commit comments

Comments
 (0)