[Kotlin]: Add a KotlinRecord concept, factor struct conversion with it
In the Dawn code generators, both structures and methods are considered
"records" that have a bunch of named parameter/members with types. The
conversion of structures and method records from Kotlin to C++ are very
similar. To merge them both, a KotlinRecord concept is added that is a
C++ structure for a the JNI objects representing the values of the
Kotlin records.
A macro is added that creates the KotlinRecord structure for a record
(either a structure, or a method).
A macro is added that creates a conversion function from a KotlinRecord
to a structure.
These two macros are used to factor out all of the common logic out of
structures.cpp. The logic in methods.cpp will be factored out in a
follow-up CL.
A few new primitive types are added to dawn_kotlin.json.
Helper CallGetter functions are added in structures.cpp to call the
correct JNIEnv::Call*Method depending on what return type we expect.
A couple hacks for handling the jlong ANativeWindow are added. We should
remove them with a manually written method.
Bug: 352711433
Change-Id: Iaa89c9e7e85f724aa3fd98136acbad176f68e522
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/198236
Reviewed-by: Sonakshi Saxena <nexa@google.com>
Reviewed-by: Jim Blackler <jimblackler@google.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index 856bef9..4a1d466 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -853,7 +853,7 @@
and argument.type.category == 'structure'):
return argument
- return {"type": method.return_type}
+ return {"type": method.return_type, "name": None}
# TODO(b/352047733): Replace methods that require special handling with an exceptions list.
def include_method(method):
@@ -1663,6 +1663,7 @@
imported_templates += [
"art/api_jni_types.kt",
+ "art/kotlin_record_conversion.cpp",
]
renders.append(
diff --git a/generator/templates/art/kotlin_record_conversion.cpp b/generator/templates/art/kotlin_record_conversion.cpp
new file mode 100644
index 0000000..e118ce6
--- /dev/null
+++ b/generator/templates/art/kotlin_record_conversion.cpp
@@ -0,0 +1,142 @@
+//* Copyright 2024 The Dawn & Tint Authors
+//*
+//* Redistribution and use in source and binary forms, with or without
+//* modification, are permitted provided that the following conditions are met:
+//*
+//* 1. Redistributions of source code must retain the above copyright notice, this
+//* list of conditions and the following disclaimer.
+//*
+//* 2. Redistributions in binary form must reproduce the above copyright notice,
+//* this list of conditions and the following disclaimer in the documentation
+//* and/or other materials provided with the distribution.
+//*
+//* 3. Neither the name of the copyright holder nor the names of its
+//* contributors may be used to endorse or promote products derived from
+//* this software without specific prior written permission.
+//*
+//* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+{% from 'art/api_jni_types.kt' import arg_to_jni_type with context %}
+
+{% macro define_kotlin_record_structure(struct_name, members) %}
+ struct {{struct_name}} {
+ {% for member in kotlin_record_members(members) %}
+ //* HACK: Hardcode that ANativeWindow is a jlong instead of an actual pointer. Instead
+ //* of this, we should have manually written method that directly creates the
+ //* wgpu::Surface from the Java Surface.
+ {% if member.name.get() == "window" and member.type.name.get() == "void *"%}
+ jlong {{ as_varName(member.name) }};
+ {% else %}
+ {{ arg_to_jni_type(member) }} {{ as_varName(member.name) }};
+ {% endif %}
+ {% endfor%}
+ };
+{% endmacro %}
+
+{% macro define_kotlin_to_struct_conversion(function_name, kotlin_name, struct_name, members) %}
+ inline void {{function_name}}(JNIContext* c, const {{kotlin_name}}& inStruct, {{struct_name}}* outStruct) {
+ JNIEnv* env = c->env;
+ *outStruct = {};
+ {% for member in kotlin_record_members(members) %} {
+ auto& in = inStruct.{{member.name.camelCase()}};
+ auto& out = outStruct->{{member.name.camelCase()}};
+ {% if member.length == 'strlen' %}
+ if (in != nullptr) {
+ out = c->GetStringUTFChars(in);
+ } else {
+ out = nullptr;
+ }
+
+ {% elif member.constant_length == 1 %}
+ {% if member.type.category == 'structure' %}
+ if (in != nullptr) {
+ auto converted = c->Alloc<{{as_cType(member.type.name)}}>();
+ ToNative(c, env, in, converted);
+ out = converted;
+ }
+ {% elif member.type.name.get() == 'void' %}
+ out = reinterpret_cast<void*>(in);
+ {% else %}
+ {{ unreachable_code() }}
+ {% endif %}
+
+ {% elif member.length %}
+ auto& outLength = outStruct->{{member.length.name.camelCase()}};
+
+ {% if member.constant_length %}
+ {{ unreachable_code() }}
+ {% endif %}
+
+ //* Convert container, including the length field.
+ {% if member.type.name.get() == 'uint32_t' %}
+ //* This container type is represented in Kotlin as a primitive array.
+ out = reinterpret_cast<const uint32_t*>(c->GetIntArrayElements(in));
+ outLength = env->GetArrayLength(in);
+
+ {% else %}
+ //* These container types are represented in Kotlin as arrays of objects.
+ outLength = env->GetArrayLength(in);
+ auto array = c->AllocArray<{{ as_cType(member.type.name) }}>(outLength);
+ out = array;
+
+ {% if member.type.category in ['bitmask', 'enum'] %}
+ jclass memberClass = env->FindClass("{{ jni_name(member.type) }}");
+ jmethodID getValue = env->GetMethodID(memberClass, "getValue", "()I");
+ for (int idx = 0; idx != outLength; idx++) {
+ jobject element = env->GetObjectArrayElement(in, idx);
+ array[idx] = static_cast<{{ as_cType(member.type.name) }}>(
+ env->CallIntMethod(element, getValue));
+ }
+ {% elif member.type.category == 'object' %}
+ jclass memberClass = env->FindClass("{{ jni_name(member.type) }}");
+ jmethodID getHandle = env->GetMethodID(memberClass, "getHandle", "()J");
+ for (int idx = 0; idx != outLength; idx++) {
+ jobject element = env->GetObjectArrayElement(in, idx);
+ array[idx] = reinterpret_cast<{{ as_cType(member.type.name) }}>(
+ env->CallLongMethod(element, getHandle));
+ }
+ {% elif member.type.category == 'structure' %}
+ for (int idx = 0; idx != outLength; idx++) {
+ ToNative(c, env, env->GetObjectArrayElement(in, idx), &array[idx]);
+ }
+ {% else %}
+ {{ unreachable_code() }}
+ {% endif %}
+ {% endif %}
+
+ //* From here members are single values.
+ {% elif member.type.category == 'object' %}
+ if (in != nullptr) {
+ jclass memberClass = env->FindClass("{{ jni_name(member.type) }}");
+ jmethodID getHandle = env->GetMethodID(memberClass, "getHandle", "()J");
+ out = reinterpret_cast<{{as_cType(member.type.name)}}>(
+ env->CallLongMethod(in, getHandle));
+ } else {
+ out = nullptr;
+ }
+ {% elif member.type.category == 'structure' %}
+ //* Mandatory structure.
+ ToNative(c, env, in, &out);
+
+ {% elif member.name.get() == "window" and member.type.name.get() == "void *" %}
+ //* HACK: Hardcode that ANativeWindow is a jlong instead of an actual pointer. Instead
+ //* of this, we should have manually written method that directly creates the
+ //* wgpu::Surface from the Java Surface.
+ out = reinterpret_cast<{{as_cType(member.type.name)}}>(static_cast<uintptr_t>(in));
+
+ {% elif member.type.category in ["native", "enum", "bitmask"] %}
+ out = static_cast<{{as_cType(member.type.name)}}>(in);
+ {% else %}
+ {{ unreachable_code() }}
+ {% endif %}
+ } {% endfor %}
+ }
+{% endmacro %}
diff --git a/generator/templates/art/structures.cpp b/generator/templates/art/structures.cpp
index e2b565a..f40f7d6 100644
--- a/generator/templates/art/structures.cpp
+++ b/generator/templates/art/structures.cpp
@@ -25,7 +25,7 @@
//* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% from 'art/api_jni_types.kt' import convert_to_kotlin, jni_signature with context %}
-
+{% from 'art/kotlin_record_conversion.cpp' import define_kotlin_record_structure, define_kotlin_to_struct_conversion with context %}
#include "structures.h"
#include <jni.h>
@@ -39,6 +39,36 @@
namespace dawn::kotlin_api {
+// Helper functions to call the correct JNIEnv::Call*Method depending on what return type we expect.
+void CallGetter(JNIEnv* env, jmethodID getter, jobject obj, jboolean* result) {
+ *result = env->CallBooleanMethod(obj, getter);
+}
+void CallGetter(JNIEnv* env, jmethodID getter, jobject obj, jbyte* result) {
+ *result = env->CallByteMethod(obj, getter);
+}
+void CallGetter(JNIEnv* env, jmethodID getter, jobject obj, jchar* result) {
+ *result = env->CallCharMethod(obj, getter);
+}
+void CallGetter(JNIEnv* env, jmethodID getter, jobject obj, jshort* result) {
+ *result = env->CallShortMethod(obj, getter);
+}
+void CallGetter(JNIEnv* env, jmethodID getter, jobject obj, jint* result) {
+ *result = env->CallIntMethod(obj, getter);
+}
+void CallGetter(JNIEnv* env, jmethodID getter, jobject obj, jlong* result) {
+ *result = env->CallLongMethod(obj, getter);
+}
+void CallGetter(JNIEnv* env, jmethodID getter, jobject obj, jfloat* result) {
+ *result = env->CallFloatMethod(obj, getter);
+}
+void CallGetter(JNIEnv* env, jmethodID getter, jobject obj, jdouble* result) {
+ *result = env->CallDoubleMethod(obj, getter);
+}
+template <typename T>
+void CallGetter(JNIEnv* env, jmethodID getter, jobject obj, T** result) {
+ *result = reinterpret_cast<T*>(env->CallObjectMethod(obj, getter));
+}
+
// Special-case noop handling of the two callback info that are part of other structures.
// TODO(352710628) support converting callback info.
void ToNative(JNIContext* c, JNIEnv* env, jobject obj, WGPUDeviceLostCallbackInfo* info) {
@@ -114,129 +144,26 @@
return converted;
}
+ {% set Struct = as_cType(structure.name) %}
+ {% set KotlinRecord = "KotlinRecord" + structure.name.CamelCase() %}
+ {{ define_kotlin_record_structure(KotlinRecord, structure.members)}}
+ {{ define_kotlin_to_struct_conversion("ConvertInternal", KotlinRecord, Struct, structure.members)}}
+
void ToNative(JNIContext* c, JNIEnv* env, jobject obj, {{ as_cType(structure.name) }}* converted) {
jclass clz = env->FindClass("{{ jni_name(structure) }}");
- //* Convert each member in turn from corresponding members of the Kotlin object obtained via
- //* JNI calls
+ //* Use getters to fill in the Kotlin record that will get converted to our struct.
+ {{KotlinRecord}} kotlinRecord;
{% for member in kotlin_record_members(structure.members) %} {
- jmethodID method = env->GetMethodID(
- clz, "get{{ member.name.CamelCase() }}", "(){{ jni_signature(member) }}");
- {% if member.length == 'strlen' %}
- jobject mObj = env->CallObjectMethod(obj, method);
- if (mObj) {
- converted->{{ member.name.camelCase() }} =
- c->GetStringUTFChars(reinterpret_cast<jstring>(mObj));
- } else {
- converted->{{ member.name.camelCase() }} = nullptr;
- }
- {% elif member.constant_length == 1 %}
- {% if member.type.category == 'structure' %}
- //* Convert optional structure if present.
- jobject mObj = env->CallObjectMethod(obj, method);
- if (mObj) {
- auto convertedMember = c->Alloc<{{ as_cType(member.type.name) }}>();
- ToNative(c, env, mObj, convertedMember);
- converted->{{ member.name.camelCase() }} = convertedMember;
- }
- {% elif member.type.name.get() == 'void' %}
- converted->{{ member.name.camelCase() }} =
- reinterpret_cast<void*>(env->CallLongMethod(obj, method));
- {% else %}
- {{ unreachable_code() }}
- {% endif %}
-
- {% elif member.length %}
- {% if member.constant_length %}
- {{ unreachable_code() }}
- {% endif %}
- //* Convert container, including the length field.
- {% if member.type.name.get() == 'uint32_t' %} {
- //* This container type is represented in Kotlin as a primitive array.
- jintArray array = static_cast<jintArray>(env->CallObjectMethod(obj, method));
- converted->{{ member.name.camelCase() }} =
- reinterpret_cast<const {{ as_cType(member.type.name) }}*>(
- c->GetIntArrayElements(array));
- converted->{{ member.length.name.camelCase() }} = env->GetArrayLength(array);
- }
- {% else %} {
- //* These container types are represented in Kotlin as arrays of objects.
- auto in = static_cast<jobjectArray>(env->CallObjectMethod(obj, method));
- size_t length = env->GetArrayLength(in);
- auto out = c->AllocArray<{{ as_cType(member.type.name) }}>(length);
- {% if member.type.category in ['bitmask', 'enum'] %} {
- jclass memberClass = env->FindClass("{{ jni_name(member.type) }}");
- jmethodID getValue = env->GetMethodID(memberClass, "getValue", "()I");
- for (int idx = 0; idx != length; idx++) {
- jobject element = env->GetObjectArrayElement(in, idx);
- out[idx] = static_cast<{{ as_cType(member.type.name) }}>(
- env->CallIntMethod(element, getValue));
- }
- }
- {% elif member.type.category == 'object' %} {
- jclass memberClass = env->FindClass("{{ jni_name(member.type) }}");
- jmethodID getHandle = env->GetMethodID(memberClass, "getHandle", "()J");
- for (int idx = 0; idx != length; idx++) {
- jobject element = env->GetObjectArrayElement(in, idx);
- out[idx] = reinterpret_cast<{{ as_cType(member.type.name) }}>(
- env->CallLongMethod(element, getHandle));
- }
- }
- {% elif member.type.category == 'structure' %}
- for (int idx = 0; idx != length; idx++) {
- ToNative(c, env, env->GetObjectArrayElement(in, idx), out + idx);
- }
- {% else %}
- {{ unreachable_code() }}
- {% endif %}
- converted->{{ member.name.camelCase() }} = out;
- converted->{{ member.length.name.camelCase() }} = length;
- }
- {% endif %}
-
- //* From here members are single values.
- {% elif member.type.category == 'object' %}
- jobject mObj = env->CallObjectMethod(obj, method);
- if (mObj) {
- jclass memberClass = env->FindClass("{{ jni_name(member.type) }}");
- jmethodID getHandle = env->GetMethodID(memberClass, "getHandle", "()J");
- converted->{{ member.name.camelCase() }} =
- reinterpret_cast<{{ as_cType(member.type.name) }}>(
- env->CallLongMethod(mObj, getHandle));
- } else {
- converted->{{ member.name.camelCase() }} = nullptr;
- }
- {% else %}
- {% if member.type.category == 'structure' or member.type.category == 'callback info' %}
- //* Mandatory structure.
- ToNative(c, env, env->CallObjectMethod(obj, method),
- &converted->{{ member.name.camelCase() }});
- {% elif member.type.name.get() == 'void *' %}
- converted->{{ member.name.camelCase() }} =
- reinterpret_cast<void*>(static_cast<intptr_t>(env->CallLongMethod(obj, method)));
- {% else %}
- converted->{{ member.name.camelCase() }} =
- static_cast<{{ as_cType(member.type.name) }}>(
- {% if member.type.name.get() == 'bool' %}
- env->CallBooleanMethod
- {% elif member.type.name.get() == 'uint16_t' %}
- env->CallShortMethod
- {% elif member.type.name.get() in ['int', 'int32_t', 'uint32_t']
- or member.type.category in ['bitmask', 'enum'] %}
- env->CallIntMethod
- {% elif member.type.name.get() == 'float' %}
- env->CallFloatMethod
- {% elif member.type.name.get() in ['size_t', 'uint64_t'] %}
- env->CallLongMethod
- {% elif member.type.name.get() == 'double' %}
- env->CallDoubleMethod
- {% else %}
- {{ unreachable_code() }}
- {% endif %} (obj, method));
- {% endif %}
- {% endif %}
+ jmethodID getter = env->GetMethodID(clz,
+ "get{{member.name.CamelCase()}}",
+ "(){{jni_signature(member)}}");
+ CallGetter(env, getter, obj, &kotlinRecord.{{as_varName(member.name)}});
} {% endfor %}
+ //* Fill all struct members from the Kotlin record.
+ ConvertInternal(c, kotlinRecord, converted);
+
//* Set up the chain type and links for child objects.
{% if structure.chained %}
converted->chain = {.sType = WGPUSType_{{ structure.name.CamelCase() }}};
@@ -252,8 +179,7 @@
out->chain.next = converted->nextInChain;
converted->nextInChain = &out->chain;
}
- }
- {% endfor %}
+ }{% endfor %}
}
{% endfor %}
diff --git a/src/dawn/dawn_kotlin.json b/src/dawn/dawn_kotlin.json
index 3db8ed3..0860452 100644
--- a/src/dawn/dawn_kotlin.json
+++ b/src/dawn/dawn_kotlin.json
@@ -36,8 +36,9 @@
"bool": "jboolean",
"double": "jdouble",
"float": "jfloat",
- "int32_t": "jint",
+ "double": "jdouble",
"size_t": "jlong",
+ "int32_t": "jint",
"uint16_t": "jshort",
"uint32_t": "jint",
"uint64_t": "jlong",