Skip to content
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 docs/docs/types/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,5 @@ into `Object`s, and provides bulk copy methods. This makes all array operations
- `Array<Double>` -> [`DoubleArray`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double-array/)
- `Array<Boolean>` -> [`BooleanArray`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-boolean-array/)
- `Array<Long>` -> [`LongArray`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-long-array/)

`UInt64[]` uses `Array<ULong>` in Kotlin, with each element boxed as a `ULong`. It requires no experimental unsigned-array opt-in.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# JNI constructs Kotlin ULong boxes and reads them through unbox-impl.
# Its class name also identifies typed ULong arrays at the JNI boundary.
-keep class kotlin.ULong {
<init>(long);
long unbox-impl();
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
#pragma once

#include "JNIConverter+Primitives.hpp"
#include <concepts>
#include <type_traits>
#include <vector>

namespace margelo::nitro {

template <typename CppElement, typename JavaArray>
requires(jni::IsInstantiationOf<jni::JPrimitiveArray, JavaArray>() ||
(std::same_as<JavaArray, JArrayUInt64> && std::same_as<CppElement, uint64_t>))
requires(jni::IsInstantiationOf<jni::JPrimitiveArray, JavaArray>())
struct JNIConverter<std::vector<CppElement>, jni::local_ref<JavaArray>> {
using Primitive = typename JavaArray::T;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include "JNIConverter.hpp"
#include "JUInt64.hpp"
#include "JULong.hpp"
#include <bit>
#include <cstdint>
#include <fbjni/fbjni.h>
Expand Down Expand Up @@ -103,15 +103,14 @@ template <>
struct JNIConverter<uint64_t, jni::local_ref<jni::JLong>> : JNIBoxedPrimitiveConverter<uint64_t, jni::JLong> {};

template <>
struct JNIConverter<uint64_t, jni::local_ref<JUInt64>> {
static jni::local_ref<JUInt64> toJNI(uint64_t value) {
auto boxed = jni::JLong::valueOf(JNIConverter<uint64_t, jlong>::toJNI(value));
auto raw = boxed.release();
auto object = static_cast<JUInt64::javaobject>(raw);
return jni::adopt_local(object);
}
static uint64_t fromJNI(jni::alias_ref<JUInt64> value) {
return JNIBoxedPrimitiveConverter<uint64_t, JUInt64>::fromJNI(value);
struct JNIConverter<uint64_t, jni::local_ref<JULong>> {
static jni::local_ref<JULong> toJNI(uint64_t value) {
return JULong::valueOf(JNIConverter<uint64_t, jlong>::toJNI(value));
}
static uint64_t fromJNI(jni::alias_ref<JULong> value) {
static const auto clazz = JULong::javaClassStatic();
static const auto method = clazz->getNonvirtualMethod<jlong()>("unbox-impl");
return JNIConverter<uint64_t, jlong>::fromJNI(method(value, clazz));
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ template <>
struct JSIConverter<jni::local_ref<jni::JLong>> : JSIBoxedJNIConverter<int64_t, jni::JLong> {};

template <>
struct JSIConverter<jni::local_ref<JUInt64>> : JSIBoxedJNIConverter<uint64_t, JUInt64> {};
struct JSIConverter<jni::local_ref<JULong>> : JSIBoxedJNIConverter<uint64_t, JULong> {};

template <>
struct JSIConverter<jni::local_ref<jni::JFloat>> : JSIBoxedJNIConverter<float, jni::JFloat> {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@
#include <NitroModules/JSIArrayHelpers.hpp>
#include <algorithm>
#include <array>
#include <concepts>
#include <type_traits>

namespace margelo::nitro {

template <typename JavaArray>
requires(jni::IsInstantiationOf<jni::JPrimitiveArray, JavaArray>() || std::same_as<JavaArray, JArrayUInt64>)
requires(jni::IsInstantiationOf<jni::JPrimitiveArray, JavaArray>())
struct JSIConverter<jni::local_ref<JavaArray>> {
using Primitive = typename JavaArray::T;
using Element = std::conditional_t<std::same_as<JavaArray, JArrayUInt64>, uint64_t, Primitive>;
using Element = Primitive;

static jni::local_ref<JavaArray> fromJSI(jsi::Runtime& runtime, const jsi::Value& value)
requires JSIDecodable<Element>
Expand Down
26 changes: 0 additions & 26 deletions packages/nitro-core/native/android/src/main/cpp/core/JUInt64.hpp

This file was deleted.

18 changes: 18 additions & 0 deletions packages/nitro-core/native/android/src/main/cpp/core/JULong.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include <fbjni/fbjni.h>

namespace margelo::nitro {

namespace jni = facebook::jni;

/** Kotlin's boxed unsigned value. Its class and JNI entry points are kept by consumer-rules.pro. */
struct JULong : jni::JavaClass<JULong> {
static constexpr auto kJavaDescriptor = "Lkotlin/ULong;";

static jni::local_ref<JULong> valueOf(jlong value) {
return newInstance(value);
}
};

} // namespace margelo::nitro
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,3 @@ package com.margelo.nitro.utils
/** Maps object array elements without an intermediate List. */
inline fun <Element, reified Result> Array<Element>.mapToArray(transform: (Element) -> Result): Array<Result> =
Array(size) { transform(this[it]) }

/** Maps primitive LongArray elements to an object array without an intermediate List. */
inline fun <reified Result> LongArray.mapToArray(transform: (Long) -> Result): Array<Result> = Array(size) { transform(this[it]) }

/** Maps object array elements to a primitive LongArray in one allocation. */
inline fun <Element> Array<Element>.mapToLongArray(transform: (Element) -> Long): LongArray = LongArray(size) { transform(this[it]) }
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ${createFileMetadataString(name)}
// Resolve beside this script so custom Nitrogen output directories work too.
def nitrogenDirectory = buildscript.sourceFile.parentFile
android.sourceSets.main.java.srcDirs += [new File(nitrogenDirectory, 'kotlin')]

`.trim()
return {
language: 'gradle',
Expand Down
6 changes: 6 additions & 0 deletions packages/nitrogen/src/syntax/kotlin/KotlinCallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,25 @@ class ${name} @DoNotStrip @Keep private constructor(hybridData: HybridData): Cxx
private class Function_cxx(private val callback: ${kotlinCallbackType}) {
@DoNotStrip
@Keep
@JvmName("call")
fun call(${bridgeParams.join(', ')}): ${bridgeReturnType} {
${indent(callKotlinBody, ' ')}
}
}

@DoNotStrip
@Keep
@JvmName("callSync_cxx")
private external fun callSync_cxx(${bridgeParams.join(', ')}): ${bridgeReturnType}

@DoNotStrip
@Keep
@JvmName("callAsync_cxx")
private external fun callAsync_cxx(${bridgeParams.join(', ')}): ${bridgeAsyncType}

@DoNotStrip
@Keep
@JvmName("callAndForget_cxx")
private external fun callAndForget_cxx(${bridgeParams.join(', ')}): Unit

companion object {
Expand All @@ -163,11 +167,13 @@ class ${name} @DoNotStrip @Keep private constructor(hybridData: HybridData): Cxx
@JvmStatic
@DoNotStrip
@Keep
@JvmName("fromCxx_cxx")
private external fun fromCxx_cxx(callback: CxxCallback): ${name}

@JvmStatic
@DoNotStrip
@Keep
@JvmName("fromFunction_cxx")
private external fun fromFunction_cxx(function: Function_cxx): ${name}
}
}
Expand Down
14 changes: 4 additions & 10 deletions packages/nitrogen/src/syntax/kotlin/KotlinHybridObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { createFbjniHybridObject } from './FbjniHybridObject.js'
import type { KotlinConstructor } from './KotlinConstructor.js'
import { getKotlinClassMembers } from './KotlinHybridClassBindings.js'
import { getKotlinPropertyAccessors, KotlinInstanceMethod } from './KotlinInstanceMethod.js'
import { getKotlinImplementation } from './KotlinMethodHelpers.js'
import { createKotlinBridgeDefinition, getKotlinImplementation } from './KotlinMethodHelpers.js'
import type { KotlinStaticMethod } from './KotlinStaticMethod.js'
import type { KotlinStaticProperty } from './KotlinStaticProperty.js'
import { wrapKotlinType } from './wrapKotlinType.js'
Expand Down Expand Up @@ -270,9 +270,7 @@ private val implementationCompanion: Companion
imports.push(implementation.kotlinImport)
}
for (const group of groups) {
const bridges = group.callables
.flatMap((callable) => callable.createBridge().kotlinMethods)
.map(({ method, body }) => method.getDefinition('kotlin', { body: body }))
const bridges = group.callables.flatMap((callable) => callable.createBridge().kotlinMethods).map(createKotlinBridgeDefinition)
const groupCode = `
// ${group.comment}
${bridges.join('\n\n')}
Expand All @@ -289,9 +287,7 @@ function getKotlinMethodCode(method: InstanceMethod): string {
doNotStrip: !kotlinMethod.needsKotlinBridge,
pureVirtual: true,
})
const bridges = kotlinMethod
.createBridge()
.kotlinMethods.map(({ method: bridge, body }) => bridge.getDefinition('kotlin', { body: body }))
const bridges = kotlinMethod.createBridge().kotlinMethods.map(createKotlinBridgeDefinition)
return [declaration.getDeclaration('kotlin'), ...bridges].join('\n\n')
}

Expand All @@ -303,9 +299,7 @@ function getKotlinPropertyCode(property: Property): string {
doNotStrip: isCalledFromJni,
virtual: true,
})
const bridges = accessors
.flatMap((accessor) => accessor.createBridge().kotlinMethods)
.map(({ method, body }) => method.getDefinition('kotlin', { body: body }))
const bridges = accessors.flatMap((accessor) => accessor.createBridge().kotlinMethods).map(createKotlinBridgeDefinition)
return [declaration, ...bridges].join('\n\n')
}

Expand Down
6 changes: 3 additions & 3 deletions packages/nitrogen/src/syntax/kotlin/KotlinInstanceMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface KotlinInstanceBridgeOptions {

/**
* The Kotlin member that C++ calls through JNI.
* If values need conversion, this is a private `_cxx` bridge;
* If values need conversion or JVM names are mangled, this is a private `_cxx` bridge;
* otherwise JNI calls the public abstract member directly.
*/
export class KotlinInstanceMethod extends InstanceMethod {
Expand All @@ -51,7 +51,7 @@ export class KotlinInstanceMethod extends InstanceMethod {
break
}
let name: string
if (callable.needsKotlinConversion) {
if (callable.needsKotlinBridge) {
name = `${jvmName}_cxx`
} else {
name = jvmName
Expand All @@ -76,7 +76,7 @@ export class KotlinInstanceMethod extends InstanceMethod {

/** Whether JNI calls a private bridge instead of the public member. */
get needsKotlinBridge(): boolean {
return this.callable.needsKotlinConversion
return this.callable.needsKotlinBridge
}

createBridge({ nonvirtual = false }: KotlinInstanceBridgeOptions = {}): KotlinMethodBridge {
Expand Down
17 changes: 14 additions & 3 deletions packages/nitrogen/src/syntax/kotlin/KotlinMethodHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ export interface KotlinMethodDefinition {
body: string
}

/** Keep JNI entry names stable when Kotlin value classes would otherwise mangle them. */
export function createKotlinBridgeDefinition({ method, body }: KotlinMethodDefinition): string {
const definition = method.getDefinition('kotlin', { body: body })
return `
@JvmName("${method.name}")
${definition}
`.trim()
}

export interface KotlinMethodBridge {
/** Private Kotlin functions that JNI calls instead of the public members. */
kotlinMethods: KotlinMethodDefinition[]
Expand All @@ -33,8 +42,8 @@ export interface KotlinMethodInfo {
cppReturnType: Type
hasNativeReturn: boolean
parameters: KotlinMethodParameter[]
/** Whether any value needs conversion between the public Kotlin API and JNI. */
needsKotlinConversion: boolean
/** Whether JNI needs a private bridge for value conversion or a stable JVM name. */
needsKotlinBridge: boolean
}

interface PrepareKotlinMethodOptions {
Expand All @@ -55,13 +64,15 @@ export function prepareKotlinMethod(nativeMethod: Method, { directReturn = true
}
})
const needsKotlinConversion = returnType.needsKotlinConversion || parameters.some((parameter) => parameter.type.needsKotlinConversion)
const requiresKotlinJvmName =
returnType.requiresKotlinJvmName === true || parameters.some((parameter) => parameter.type.requiresKotlinJvmName === true)
return {
nativeMethod: nativeMethod,
returnType: returnType,
cppReturnType: directReturn ? returnType : nativeMethod.returnType,
hasNativeReturn: !directReturn,
parameters: parameters,
needsKotlinConversion: needsKotlinConversion,
needsKotlinBridge: needsKotlinConversion || requiresKotlinJvmName,
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/nitrogen/src/syntax/kotlin/KotlinStruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ val ${p.escapedName}: ${type}
@DoNotStrip
@Keep
@Suppress("unused", "FunctionName")
@JvmName("get${name}_cxx")
private fun get${name}_cxx(): ${type} {
return ${value}
}
Expand Down Expand Up @@ -95,6 +96,7 @@ data class ${structType.structName}(
@Keep
@Suppress("unused")
@JvmStatic
@JvmName("fromCpp")
private fun fromCpp(${cxxCreateFunctionParameters}): ${structType.structName} {
return ${structType.structName}(${cxxCreateFunctionForward})
}
Expand Down
2 changes: 2 additions & 0 deletions packages/nitrogen/src/syntax/kotlin/KotlinVariant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ data class ${innerName}(@DoNotStrip val value: ${valueType}): ${kotlinName}(${in
${classCode} {
@DoNotStrip
@Suppress("unused", "FunctionName")
@JvmName("getValue_cxx")
private fun getValue_cxx(): ${bridgeType} {
return ${bridgeValue}
}
Expand Down Expand Up @@ -91,6 +92,7 @@ fun create(value: ${valueType}): ${kotlinName} = ${innerName}(value)

@JvmStatic
@DoNotStrip
@JvmName("fromCpp_${i}")
private fun fromCpp_${i}(value: ${bridgeType}): ${kotlinName} = ${innerName}(${publicValue})
`.trim()
})
Expand Down
Loading
Loading