Skip to content

Commit 312fbf4

Browse files
authored
[jnienv-gen] Add possible C#9 function pointer backend (#938)
Context: 926e4bc Context: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/function-pointers Context? #666 Commit 926e4bc allowed `jnienv-gen` to emit multiple different JNIEnv invocation strategies at the same time, allowing `tests/invocation-overhead` to try them "all at once" for side-by- side comparisons. Add support for a new JNIEnv invocation strategy which relies on C#9 Function Pointers, a'la: partial struct JNIEnv { public delegate* unmanaged <IntPtr /* env */, jobject> ExceptionOccurred; } partial class JniEnvironment { partial class Exceptions { public static unsafe JniObjectReference ExceptionOccurred () { IntPtr __env = JniEnvironment.EnvironmentPointer; var tmp = (*((JNIEnv**)__env))->ExceptionOccurred (__env); return new JniObjectReference (tmp, JniObjectReferenceType.Local); } } } This *could* allow for performance better than "JIPinvokeTiming", as it avoids P/Invoke overheads to a set of `java_interop_*` C functions (926e4bc), while *also* avoiding the overheads involved with using `Marshal.GetDelegateForFunctionPointer()` as used by "JIIntPtrs". …but it doesn't necessarily provide better performance: $ JI_JVM_PATH=$HOME/android-toolchain/jdk-11/lib/jli/libjli.dylib dotnet tests/invocation-overhead/bin/Debug/net6.0/invocation-overhead.dll # SafeTiming timing: 00:00:04.2123508 # Average Invocation: 0.00042123508ms # XAIntPtrTiming timing: 00:00:02.1625501 # Average Invocation: 0.00021625500999999998ms # JIIntPtrTiming timing: 00:00:02.3620239 # Average Invocation: 0.00023620239ms # JIPinvokeTiming timing: 00:00:01.8993587 # Average Invocation: 0.00018993587ms # JIFunctionPointersTiming timing: 00:00:02.0278083 # Average Invocation: 0.00020278083ms (Compare and contrast with 926e4bc, circa 2015!) Of particular note is that the Average Invocation time for JIFunctionPointersTiming takes 7% longer than JIPinvokeTiming. Though that's slightly reversed when a *Release* build of `invocation-overhead.dll` is used: % JI_JVM_PATH=$HOME/android-toolchain/jdk-11/lib/jli/libjli.dylib dotnet tests/invocation-overhead/bin/Release/net6.0/invocation-overhead.dll # SafeTiming timing: 00:00:03.4128431 # Average Invocation: 0.00034128431000000003ms # XAIntPtrTiming timing: 00:00:01.8857456 # Average Invocation: 0.00018857455999999999ms # JIIntPtrTiming timing: 00:00:01.9075412 # Average Invocation: 0.00019075412ms # JIPinvokeTiming timing: 00:00:01.6993644 # Average Invocation: 0.00016993643999999998ms # JIFunctionPointersTiming timing: 00:00:01.6561349 # Average Invocation: 0.00016561349ms With a Release build, the Average Invocation time for JIFunctionPointersTiming takes 97% of the time as JIPinvokeTiming, i.e. is 3% faster. We may or may not continue investigation of C#9 Function Pointers for `JNIEnv` binding purposes. We will preserve this code for future investigation.
1 parent d3f0c5c commit 312fbf4

File tree

4 files changed

+3507
-18
lines changed

4 files changed

+3507
-18
lines changed

build-tools/jnienv-gen/Generator.cs

+166-10
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ static void GenerateFile (TextWriter o)
111111
o.WriteLine ();
112112
o.WriteLine ("using JNIEnvPtr = System.IntPtr;");
113113
o.WriteLine ();
114-
o.WriteLine ("#if FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_JI_PINVOKES");
114+
o.WriteLine ("#if FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_JI_PINVOKES || FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS");
115115
o.WriteLine ("\tusing jinstanceFieldID = System.IntPtr;");
116116
o.WriteLine ("\tusing jstaticFieldID = System.IntPtr;");
117117
o.WriteLine ("\tusing jinstanceMethodID = System.IntPtr;");
118118
o.WriteLine ("\tusing jstaticMethodID = System.IntPtr;");
119119
o.WriteLine ("\tusing jobject = System.IntPtr;");
120-
o.WriteLine ("#endif // FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_JI_PINVOKES");
120+
o.WriteLine ("#endif // FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_JI_PINVOKES || FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS");
121121
o.WriteLine ();
122122
o.WriteLine ("namespace Java.Interop {");
123123
GenerateJniNativeInterface (o);
@@ -126,6 +126,7 @@ static void GenerateFile (TextWriter o)
126126
WriteSection (o, HandleStyle.JIIntPtr, "FEATURE_JNIENVIRONMENT_JI_INTPTRS", "Java.Interop.JIIntPtrs");
127127
WriteSection (o, HandleStyle.JIIntPtrPinvokeWithErrors, "FEATURE_JNIENVIRONMENT_JI_PINVOKES", "Java.Interop.JIPinvokes");
128128
WriteSection (o, HandleStyle.XAIntPtr, "FEATURE_JNIENVIRONMENT_XA_INTPTRS", "Java.Interop.XAIntPtrs");
129+
WriteSection (o, HandleStyle.JIFunctionPtrWithErrors, "FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS", "Java.Interop.JIFunctionPointers");
129130
}
130131

131132
static void WriteSection (TextWriter o, HandleStyle style, string define, string specificNamespace)
@@ -139,7 +140,7 @@ static void WriteSection (TextWriter o, HandleStyle style, string define, string
139140
o.WriteLine ("#endif");
140141
o.WriteLine ("{");
141142
o.WriteLine ();
142-
if (style != HandleStyle.JIIntPtrPinvokeWithErrors) {
143+
if (style != HandleStyle.JIIntPtrPinvokeWithErrors && style != HandleStyle.JIFunctionPtrWithErrors) {
143144
GenerateDelegates (o, style);
144145
o.WriteLine ();
145146
}
@@ -166,26 +167,56 @@ static void GenerateDelegates (TextWriter o, HandleStyle style)
166167

167168
static void GenerateJniNativeInterface (TextWriter o)
168169
{
170+
o.WriteLine ("#pragma warning disable 0649 // Field is assigned to, and will always have its default value `null`; ignore as it'll be set in native code.");
171+
o.WriteLine ("#pragma warning disable 0169 // Field never used; ignore since these fields make the structure have the right layout.");
172+
o.WriteLine ();
173+
169174
o.WriteLine ("#if FEATURE_JNIENVIRONMENT_SAFEHANDLES || FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_XA_INTPTRS");
170175
o.WriteLine ("\t[StructLayout (LayoutKind.Sequential)]");
171176
o.WriteLine ("\tpartial struct JniNativeInterfaceStruct {");
172177
o.WriteLine ();
173178

174179
int maxName = JNIEnvEntries.Max (e => e.Name.Length);
175180

176-
o.WriteLine ("#pragma warning disable 0649 // Field is assigned to, and will always have its default value `null`; ignore as it'll be set in native code.");
177-
o.WriteLine ("#pragma warning disable 0169 // Field never used; ignore since these fields make the structure have the right layout.");
178-
179181
for (int i = 0; i < 4; i++)
180182
o.WriteLine ("\t\tprivate IntPtr reserved{0}; // void*", i);
181183

182184
foreach (var e in JNIEnvEntries) {
183185
o.WriteLine ("\t\tpublic IntPtr {0};{1} // {2}", e.Name, new string (' ', maxName - e.Name.Length), e.Prototype);
184186
}
185-
o.WriteLine ("#pragma warning restore 0169");
186-
o.WriteLine ("#pragma warning restore 0649");
187187
o.WriteLine ("\t}");
188188
o.WriteLine ("#endif // FEATURE_JNIENVIRONMENT_SAFEHANDLES || FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_XA_INTPTRS");
189+
o.WriteLine ();
190+
191+
o.WriteLine ("#if FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS");
192+
o.WriteLine ("\t[StructLayout (LayoutKind.Sequential)]");
193+
o.WriteLine ("\tunsafe partial struct JNIEnv {");
194+
195+
for (int i = 0; i < 4; i++)
196+
o.WriteLine ("\t\tprivate IntPtr reserved{0}; // void*", i);
197+
198+
foreach (var e in JNIEnvEntries) {
199+
if (e.Parameters.Length > 0 &&
200+
"va_list" == e.Parameters [e.Parameters.Length-1].Type.GetManagedType (HandleStyle.JIFunctionPtrWithErrors, isReturn: false, isPinvoke: true)) {
201+
o.WriteLine ("\t\tpublic IntPtr {0};{1} // {2}", e.Name, new string (' ', maxName - e.Name.Length), e.Prototype);
202+
continue;
203+
}
204+
o.Write ("\t\tpublic delegate* unmanaged <IntPtr /* env */");
205+
foreach (var p in e.Parameters) {
206+
o.Write (", ");
207+
o.Write (p.Type.GetMarshalType (HandleStyle.JIFunctionPtrWithErrors, isReturn: false, isPinvoke: true));
208+
o.Write ($" /* {p.Name} */");
209+
}
210+
o.Write (", ");
211+
o.Write (e.ReturnType.GetMarshalType (HandleStyle.JIFunctionPtrWithErrors, isReturn: true, isPinvoke: true));
212+
o.WriteLine ($"> {e.Name};");
213+
}
214+
o.WriteLine ("\t}");
215+
o.WriteLine ("#endif // FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS");
216+
217+
o.WriteLine ();
218+
o.WriteLine ("#pragma warning restore 0169");
219+
o.WriteLine ("#pragma warning restore 0649");
189220
}
190221

191222
static string Initialize (JniFunction e, string prefix, string delegateType)
@@ -379,9 +410,12 @@ static void GenerateJniEnv (TextWriter o, string type, string visibility, Handle
379410
o.WriteLine (")");
380411
o.WriteLine ("\t\t{");
381412
NullCheckParameters (o, entry.Parameters, style);
413+
PrepareParameters (o, entry.Parameters, style);
382414
if (style == HandleStyle.JIIntPtrPinvokeWithErrors) {
383415
if (entry.Throws)
384416
o.WriteLine ("\t\t\tIntPtr thrown;");
417+
} else if (style == HandleStyle.JIFunctionPtrWithErrors) {
418+
o.WriteLine ($"\t\t\tIntPtr __env = JniEnvironment.EnvironmentPointer;");
385419
} else {
386420
o.WriteLine ("\t\t\tvar __info = JniEnvironment.CurrentInfo;");
387421
}
@@ -392,17 +426,27 @@ static void GenerateJniEnv (TextWriter o, string type, string visibility, Handle
392426
o.Write ("NativeMethods.{0} (JniEnvironment.EnvironmentPointer{1}",
393427
GetPinvokeName (entry.Name),
394428
entry.Throws ? ", out thrown" : "");
429+
} else if (style == HandleStyle.JIFunctionPtrWithErrors) {
430+
o.Write ($"(*((JNIEnv**)__env))->{entry.Name} (__env");
395431
} else {
396432
o.Write ("__info.Invoker.{0} (__info.EnvironmentPointer", entry.Name);
397433
}
398434
for (int i = 0; i < entry.Parameters.Length; i++) {
399435
var p = entry.Parameters [i];
400436
o.Write (", ");
401-
if (p.Type.GetManagedType (style, isReturn: false).StartsWith ("out ", StringComparison.Ordinal))
437+
var needOut = p.Type.GetManagedType (style, isReturn: false).StartsWith ("out ", StringComparison.Ordinal);
438+
if (needOut && style == HandleStyle.JIFunctionPtrWithErrors) {
439+
o.Write ("&");
440+
} else if (needOut) {
402441
o.Write ("out ");
442+
}
403443
o.Write (p.Type.GetManagedToMarshalExpression (style, Escape (entry.Parameters [i].Name)));
404444
}
405445
o.WriteLine (");");
446+
if (style == HandleStyle.JIFunctionPtrWithErrors && entry.Throws) {
447+
o.WriteLine ("\t\t\tIntPtr thrown = (*((JNIEnv**)__env))->ExceptionOccurred (__env);");
448+
}
449+
CleanupParameters (o, entry.Parameters, style);
406450
RaiseException (o, entry, style);
407451
if (is_void) {
408452
} else {
@@ -436,14 +480,38 @@ static void NullCheckParameters (TextWriter o, ParamInfo[] ps, HandleStyle style
436480
o.WriteLine ();
437481
}
438482

483+
static void PrepareParameters (TextWriter o, ParamInfo[] ps, HandleStyle style)
484+
{
485+
bool haveChecks = false;
486+
foreach (var e in ps) {
487+
foreach (var s in e.Type.GetManagedToMarshalPrepareStatements (style, Escape (e.Name))) {
488+
haveChecks = true;
489+
o.WriteLine ($"\t\t\t{s}");
490+
}
491+
}
492+
if (haveChecks)
493+
o.WriteLine ();
494+
}
495+
496+
static void CleanupParameters (TextWriter o, ParamInfo[] ps, HandleStyle style)
497+
{
498+
foreach (var e in ps) {
499+
foreach (var s in e.Type.GetManagedToMarshalCleanupStatements (style, Escape (e.Name))) {
500+
o.WriteLine ($"\t\t\t{s}");
501+
}
502+
}
503+
}
504+
439505
static void RaiseException (TextWriter o, JniFunction entry, HandleStyle style)
440506
{
441507
if (!entry.Throws)
442508
return;
443509

444510
o.WriteLine ();
445511
o.WriteLine ("\t\t\tException __e = JniEnvironment.GetExceptionForLastThrowable ({0});",
446-
style == HandleStyle.JIIntPtrPinvokeWithErrors ? "thrown" : "");
512+
(style == HandleStyle.JIIntPtrPinvokeWithErrors || style == HandleStyle.JIFunctionPtrWithErrors)
513+
? "thrown"
514+
: "");
447515
o.WriteLine ("\t\t\tif (__e != null)");
448516
o.WriteLine ("\t\t\t\tExceptionDispatchInfo.Capture (__e).Throw ();");
449517
o.WriteLine ();
@@ -728,6 +796,9 @@ public virtual string[] VerifyParameter (HandleStyle style, string variable)
728796
{
729797
return new string [0];
730798
}
799+
800+
public virtual string[] GetManagedToMarshalPrepareStatements (HandleStyle style, string variable) => Array.Empty<string> ();
801+
public virtual string[] GetManagedToMarshalCleanupStatements (HandleStyle style, string variable) => Array.Empty<string> ();
731802
}
732803

733804
class BuiltinTypeInfo : TypeInfo {
@@ -822,6 +893,9 @@ public StringTypeInfo (string jni)
822893

823894
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
824895
{
896+
if (style == HandleStyle.JIFunctionPtrWithErrors && isPinvoke) {
897+
return "IntPtr";
898+
}
825899
return "string";
826900
}
827901

@@ -830,6 +904,16 @@ public override string GetManagedType (HandleStyle style, bool isReturn, bool is
830904
return "string";
831905
}
832906

907+
public override string GetManagedToMarshalExpression (HandleStyle style, string variable)
908+
{
909+
switch (style) {
910+
case HandleStyle.JIFunctionPtrWithErrors:
911+
return $"_{variable}_ptr";
912+
default:
913+
return variable;
914+
}
915+
}
916+
833917
public override string[] GetMarshalToManagedStatements (HandleStyle style, string variable, JniFunction entry)
834918
{
835919
switch (style) {
@@ -841,6 +925,7 @@ public override string[] GetMarshalToManagedStatements (HandleStyle style, strin
841925
};
842926
case HandleStyle.JIIntPtr:
843927
case HandleStyle.JIIntPtrPinvokeWithErrors:
928+
case HandleStyle.JIFunctionPtrWithErrors:
844929
return new [] {
845930
string.Format ("JniEnvironment.LogCreateLocalRef ({0});", variable),
846931
string.Format ("return new JniObjectReference ({0}, JniObjectReferenceType.Local);", variable),
@@ -859,6 +944,30 @@ public override string[] VerifyParameter (HandleStyle style, string variable)
859944
string.Format ("\tthrow new ArgumentNullException (\"{0}\");", variableName),
860945
};
861946
}
947+
948+
public override string[] GetManagedToMarshalPrepareStatements (HandleStyle style, string variable)
949+
{
950+
switch (style) {
951+
case HandleStyle.JIFunctionPtrWithErrors:
952+
return new[]{
953+
$"var _{variable}_ptr = Marshal.StringToCoTaskMemUTF8 ({variable});",
954+
};
955+
default:
956+
return base.GetManagedToMarshalPrepareStatements (style, variable);
957+
}
958+
}
959+
960+
public override string[] GetManagedToMarshalCleanupStatements (HandleStyle style, string variable)
961+
{
962+
switch (style) {
963+
case HandleStyle.JIFunctionPtrWithErrors:
964+
return new[]{
965+
$"Marshal.ZeroFreeCoTaskMemUTF8 (_{variable}_ptr);",
966+
};
967+
default:
968+
return base.GetManagedToMarshalCleanupStatements (style, variable);
969+
}
970+
}
862971
}
863972

864973
class JniReleaseArrayElementsModeTypeInfo : TypeInfo {
@@ -920,6 +1029,7 @@ public override string GetManagedType (HandleStyle style, bool isReturn, bool is
9201029
case HandleStyle.SafeHandle:
9211030
case HandleStyle.JIIntPtr:
9221031
case HandleStyle.JIIntPtrPinvokeWithErrors:
1032+
case HandleStyle.JIFunctionPtrWithErrors:
9231033
return type;
9241034
case HandleStyle.XAIntPtr:
9251035
return "IntPtr";
@@ -933,6 +1043,7 @@ public override string GetManagedToMarshalExpression (HandleStyle style, string
9331043
case HandleStyle.SafeHandle:
9341044
case HandleStyle.JIIntPtr:
9351045
case HandleStyle.JIIntPtrPinvokeWithErrors:
1046+
case HandleStyle.JIFunctionPtrWithErrors:
9361047
return string.Format ("{0}.ID", variable);
9371048
}
9381049
return variable;
@@ -947,6 +1058,7 @@ public override string[] VerifyParameter (HandleStyle style, string variable)
9471058
case HandleStyle.SafeHandle:
9481059
case HandleStyle.JIIntPtr:
9491060
case HandleStyle.JIIntPtrPinvokeWithErrors:
1061+
case HandleStyle.JIFunctionPtrWithErrors:
9501062
return new [] {
9511063
string.Format ("if ({0} == null)", variable),
9521064
string.Format ("\tthrow new ArgumentNullException (\"{0}\");", variableName),
@@ -969,6 +1081,7 @@ public override string[] GetMarshalToManagedStatements (HandleStyle style, strin
9691081
case HandleStyle.SafeHandle:
9701082
case HandleStyle.JIIntPtr:
9711083
case HandleStyle.JIIntPtrPinvokeWithErrors:
1084+
case HandleStyle.JIFunctionPtrWithErrors:
9721085
return new[] {
9731086
string.Format ("if ({0} == IntPtr.Zero)", variable),
9741087
string.Format ("\treturn null;"),
@@ -1045,6 +1158,7 @@ public override string GetMarshalType (HandleStyle style, bool isReturn, bool is
10451158
return isReturn ? safeType : "JniReferenceSafeHandle";
10461159
case HandleStyle.JIIntPtr:
10471160
case HandleStyle.JIIntPtrPinvokeWithErrors:
1161+
case HandleStyle.JIFunctionPtrWithErrors:
10481162
case HandleStyle.XAIntPtr:
10491163
return "jobject";
10501164
}
@@ -1057,6 +1171,7 @@ public override string GetManagedType (HandleStyle style, bool isReturn, bool is
10571171
case HandleStyle.SafeHandle:
10581172
case HandleStyle.JIIntPtr:
10591173
case HandleStyle.JIIntPtrPinvokeWithErrors:
1174+
case HandleStyle.JIFunctionPtrWithErrors:
10601175
return "JniObjectReference";
10611176
case HandleStyle.XAIntPtr:
10621177
return "IntPtr";
@@ -1071,6 +1186,7 @@ public override string GetManagedToMarshalExpression (HandleStyle style, string
10711186
return string.Format ("{0}.SafeHandle", variable);
10721187
case HandleStyle.JIIntPtr:
10731188
case HandleStyle.JIIntPtrPinvokeWithErrors:
1189+
case HandleStyle.JIFunctionPtrWithErrors:
10741190
return string.Format ("{0}.Handle", variable);
10751191
case HandleStyle.XAIntPtr:
10761192
return variable;
@@ -1084,6 +1200,7 @@ public override string[] GetMarshalToManagedStatements (HandleStyle style, strin
10841200
case HandleStyle.SafeHandle:
10851201
case HandleStyle.JIIntPtr:
10861202
case HandleStyle.JIIntPtrPinvokeWithErrors:
1203+
case HandleStyle.JIFunctionPtrWithErrors:
10871204
return new [] {
10881205
string.Format ("return new JniObjectReference ({0}, {1});", variable, refType),
10891206
};
@@ -1104,6 +1221,7 @@ public override string[] VerifyParameter (HandleStyle style, string variable)
11041221
case HandleStyle.SafeHandle:
11051222
case HandleStyle.JIIntPtr:
11061223
case HandleStyle.JIIntPtrPinvokeWithErrors:
1224+
case HandleStyle.JIFunctionPtrWithErrors:
11071225
return new [] {
11081226
string.Format ("if (!{0}.IsValid)", variable),
11091227
string.Format ("\tthrow new ArgumentException (\"Handle must be valid.\", \"{0}\");", variableName),
@@ -1160,13 +1278,50 @@ public JavaVMPointerTypeInfo (string jni)
11601278

11611279
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
11621280
{
1281+
if (style == HandleStyle.JIFunctionPtrWithErrors && isPinvoke) {
1282+
return "IntPtr*";
1283+
}
11631284
return "out IntPtr";
11641285
}
11651286

11661287
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
11671288
{
11681289
return "out IntPtr";
11691290
}
1291+
1292+
public override string GetManagedToMarshalExpression (HandleStyle style, string variable)
1293+
{
1294+
switch (style) {
1295+
case HandleStyle.JIFunctionPtrWithErrors:
1296+
return $"_{variable}_ptr";
1297+
default:
1298+
return variable;
1299+
}
1300+
}
1301+
1302+
public override string[] GetManagedToMarshalPrepareStatements (HandleStyle style, string variable)
1303+
{
1304+
switch (style) {
1305+
case HandleStyle.JIFunctionPtrWithErrors:
1306+
return new[]{
1307+
$"IntPtr _{variable}_ptr = IntPtr.Zero;",
1308+
};
1309+
default:
1310+
return base.GetManagedToMarshalPrepareStatements (style, variable);
1311+
}
1312+
}
1313+
1314+
public override string[] GetManagedToMarshalCleanupStatements (HandleStyle style, string variable)
1315+
{
1316+
switch (style) {
1317+
case HandleStyle.JIFunctionPtrWithErrors:
1318+
return new[]{
1319+
$"{variable} = _{variable}_ptr;",
1320+
};
1321+
default:
1322+
return base.GetManagedToMarshalCleanupStatements (style, variable);
1323+
}
1324+
}
11701325
}
11711326

11721327
class ParamInfo
@@ -1204,6 +1359,7 @@ enum HandleStyle {
12041359
JIIntPtr,
12051360
JIIntPtrPinvokeWithErrors,
12061361
XAIntPtr,
1362+
JIFunctionPtrWithErrors,
12071363
}
12081364
}
12091365

0 commit comments

Comments
 (0)