Skip to content

perf(android): pass UInt64 records and promise results through JNI directly - #1999

Open
mrousavy wants to merge 1 commit into
mrousavy/v1-kotlin-variant-casesfrom
mrousavy/v1-kotlin-erased-ulong
Open

mrousavy wants to merge 1 commit into
mrousavy/v1-kotlin-variant-casesfrom
mrousavy/v1-kotlin-erased-ulong

Conversation

@mrousavy

@mrousavy mrousavy commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Third of five stacked PRs: #1997 → #1998 → #1999 (this one) → #2008 → #2009.

Record<string, UInt64> crossed JNI as a Map<String, Long>, so Kotlin copied the map with mapValues on every crossing. Promise<UInt64> and Promise<UInt64[]> results, including Callback.callAsync results, went through a second Promise that converted the value.

Map values and Promise results are erased generics, so Kotlin can keep them as kotlin.ULong and kotlin.ULongArray boxes, and the JNI types become Map<String, ULong> and Promise<ULong>. JNI can't call ULong's box-impl/unbox-impl or name its class, because R8 can rename kotlin.ULong and its members in release builds and Nitro ships no keep rules for Kotlin stdlib classes. JNI sees these values as Objects (JULong, JULongArray) and creates and reads the boxes itself: AllocObject and a field write to box a value, a field read to unbox it, with no call into Kotlin per value. ULongBox, a @Keep object in nitro-core, hands JNI the box classes and their value fields once, and finds the fields by type, so R8 renaming them doesn't matter. It also boxes and unboxes in Kotlin, so R8 keeps those fields and doesn't assume they always hold their default value, since it can't see JNI's writes.

UInt64? parameters, properties and fields, and (UInt64 | undefined)[], still cross as Long?. Their JVM signatures name kotlin.ULong (Lkotlin/ULong;, [Lkotlin/ULong;), so they would need those keep rules. That costs one box per optional scalar, or a copy per array of optionals.

New TestObject methods pass UInt64 and UInt64[] records and Promise<UInt64>, and have native code call UInt64 callbacks with callAsync. The existing roundtripUnsignedValues test covers Record<string, UInt64 | undefined> inside a struct.

Validation: Kotlin compiles without warnings, javap shows the ULongBox signatures JNI looks up and that kotlin-stdlib's ULong and ULongArray each have one instance long/long[] field, and the changed nitro-core and generated JNI sources compile with the NDK. I did not run the runtime tests or an R8 release build locally.

🤖 Generated with Claude Code

@mrousavy
mrousavy added this pull request to stack #2000 October 23, 2026 12:23
@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
nitro-docs Skipped Skipped Oct 23, 2026 6:13pm UTC

Request Review

…rectly

Record<string, UInt64> crossed JNI as a Map<String, Long>, so Kotlin
copied the map with mapValues on every crossing. Promise<UInt64> and
Promise<UInt64[]> results went through a second Promise that converted
the value.

Map values and Promise results are erased generics, so Kotlin can keep
them as kotlin.ULong and kotlin.ULongArray boxes. JNI cannot name those
classes, since R8 may rename them in release builds without keep rules,
so it sees them as an Object. It creates and reads the boxes itself with
AllocObject and field access, using the classes and value fields that
ULongBox, a @keep helper in nitro-core, gives it once. ULongBox finds
the fields by type, and also boxes and unboxes in Kotlin so R8 keeps
the fields and does not assume their default value.

UInt64? parameters and fields, and arrays of optional UInt64, still
cross as Long. Their JVM signatures name kotlin.ULong, so they would
need keep rules for Kotlin stdlib classes, which Nitro does not ship.

New TestObject methods pass UInt64 and UInt64[] records and
Promise<UInt64>, and call UInt64 callbacks with callAsync.

This branch was previously deployed

1 inactive deployment
Preview — be7dd577 Deployed Oct 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant