Skip to content

Commit

Permalink
[jnienv-gen] Add possible C#9 function pointer backend
Browse files Browse the repository at this point in the history
Context: 926e4bc
Context: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/function-pointers
Context? dotnet#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 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 *should* 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:

	$ 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.

We may not continue investigation of C#9 Function Pointers
for `JNIEnv` binding purposes, but we can preserve this code for
future investigation.
  • Loading branch information
jonpryor committed Jan 4, 2022
1 parent d3f0c5c commit e9fd048
Show file tree
Hide file tree
Showing 4 changed files with 3,507 additions and 18 deletions.
176 changes: 166 additions & 10 deletions build-tools/jnienv-gen/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ static void GenerateFile (TextWriter o)
o.WriteLine ();
o.WriteLine ("using JNIEnvPtr = System.IntPtr;");
o.WriteLine ();
o.WriteLine ("#if FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_JI_PINVOKES");
o.WriteLine ("#if FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_JI_PINVOKES || FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS");
o.WriteLine ("\tusing jinstanceFieldID = System.IntPtr;");
o.WriteLine ("\tusing jstaticFieldID = System.IntPtr;");
o.WriteLine ("\tusing jinstanceMethodID = System.IntPtr;");
o.WriteLine ("\tusing jstaticMethodID = System.IntPtr;");
o.WriteLine ("\tusing jobject = System.IntPtr;");
o.WriteLine ("#endif // FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_JI_PINVOKES");
o.WriteLine ("#endif // FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_JI_PINVOKES || FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS");
o.WriteLine ();
o.WriteLine ("namespace Java.Interop {");
GenerateJniNativeInterface (o);
Expand All @@ -126,6 +126,7 @@ static void GenerateFile (TextWriter o)
WriteSection (o, HandleStyle.JIIntPtr, "FEATURE_JNIENVIRONMENT_JI_INTPTRS", "Java.Interop.JIIntPtrs");
WriteSection (o, HandleStyle.JIIntPtrPinvokeWithErrors, "FEATURE_JNIENVIRONMENT_JI_PINVOKES", "Java.Interop.JIPinvokes");
WriteSection (o, HandleStyle.XAIntPtr, "FEATURE_JNIENVIRONMENT_XA_INTPTRS", "Java.Interop.XAIntPtrs");
WriteSection (o, HandleStyle.JIFunctionPtrWithErrors, "FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS", "Java.Interop.JIFunctionPointers");
}

static void WriteSection (TextWriter o, HandleStyle style, string define, string specificNamespace)
Expand All @@ -139,7 +140,7 @@ static void WriteSection (TextWriter o, HandleStyle style, string define, string
o.WriteLine ("#endif");
o.WriteLine ("{");
o.WriteLine ();
if (style != HandleStyle.JIIntPtrPinvokeWithErrors) {
if (style != HandleStyle.JIIntPtrPinvokeWithErrors && style != HandleStyle.JIFunctionPtrWithErrors) {
GenerateDelegates (o, style);
o.WriteLine ();
}
Expand All @@ -166,26 +167,56 @@ static void GenerateDelegates (TextWriter o, HandleStyle style)

static void GenerateJniNativeInterface (TextWriter o)
{
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.");
o.WriteLine ("#pragma warning disable 0169 // Field never used; ignore since these fields make the structure have the right layout.");
o.WriteLine ();

o.WriteLine ("#if FEATURE_JNIENVIRONMENT_SAFEHANDLES || FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_XA_INTPTRS");
o.WriteLine ("\t[StructLayout (LayoutKind.Sequential)]");
o.WriteLine ("\tpartial struct JniNativeInterfaceStruct {");
o.WriteLine ();

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

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.");
o.WriteLine ("#pragma warning disable 0169 // Field never used; ignore since these fields make the structure have the right layout.");

for (int i = 0; i < 4; i++)
o.WriteLine ("\t\tprivate IntPtr reserved{0}; // void*", i);

foreach (var e in JNIEnvEntries) {
o.WriteLine ("\t\tpublic IntPtr {0};{1} // {2}", e.Name, new string (' ', maxName - e.Name.Length), e.Prototype);
}
o.WriteLine ("#pragma warning restore 0169");
o.WriteLine ("#pragma warning restore 0649");
o.WriteLine ("\t}");
o.WriteLine ("#endif // FEATURE_JNIENVIRONMENT_SAFEHANDLES || FEATURE_JNIENVIRONMENT_JI_INTPTRS || FEATURE_JNIENVIRONMENT_XA_INTPTRS");
o.WriteLine ();

o.WriteLine ("#if FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS");
o.WriteLine ("\t[StructLayout (LayoutKind.Sequential)]");
o.WriteLine ("\tunsafe partial struct JNIEnv {");

for (int i = 0; i < 4; i++)
o.WriteLine ("\t\tprivate IntPtr reserved{0}; // void*", i);

foreach (var e in JNIEnvEntries) {
if (e.Parameters.Length > 0 &&
"va_list" == e.Parameters [e.Parameters.Length-1].Type.GetManagedType (HandleStyle.JIFunctionPtrWithErrors, isReturn: false, isPinvoke: true)) {
o.WriteLine ("\t\tpublic IntPtr {0};{1} // {2}", e.Name, new string (' ', maxName - e.Name.Length), e.Prototype);
continue;
}
o.Write ("\t\tpublic delegate* unmanaged <IntPtr /* env */");
foreach (var p in e.Parameters) {
o.Write (", ");
o.Write (p.Type.GetMarshalType (HandleStyle.JIFunctionPtrWithErrors, isReturn: false, isPinvoke: true));
o.Write ($" /* {p.Name} */");
}
o.Write (", ");
o.Write (e.ReturnType.GetMarshalType (HandleStyle.JIFunctionPtrWithErrors, isReturn: true, isPinvoke: true));
o.WriteLine ($"> {e.Name};");
}
o.WriteLine ("\t}");
o.WriteLine ("#endif // FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS");

o.WriteLine ();
o.WriteLine ("#pragma warning restore 0169");
o.WriteLine ("#pragma warning restore 0649");
}

static string Initialize (JniFunction e, string prefix, string delegateType)
Expand Down Expand Up @@ -379,9 +410,12 @@ static void GenerateJniEnv (TextWriter o, string type, string visibility, Handle
o.WriteLine (")");
o.WriteLine ("\t\t{");
NullCheckParameters (o, entry.Parameters, style);
PrepareParameters (o, entry.Parameters, style);
if (style == HandleStyle.JIIntPtrPinvokeWithErrors) {
if (entry.Throws)
o.WriteLine ("\t\t\tIntPtr thrown;");
} else if (style == HandleStyle.JIFunctionPtrWithErrors) {
o.WriteLine ($"\t\t\tIntPtr __env = JniEnvironment.EnvironmentPointer;");
} else {
o.WriteLine ("\t\t\tvar __info = JniEnvironment.CurrentInfo;");
}
Expand All @@ -392,17 +426,27 @@ static void GenerateJniEnv (TextWriter o, string type, string visibility, Handle
o.Write ("NativeMethods.{0} (JniEnvironment.EnvironmentPointer{1}",
GetPinvokeName (entry.Name),
entry.Throws ? ", out thrown" : "");
} else if (style == HandleStyle.JIFunctionPtrWithErrors) {
o.Write ($"(*((JNIEnv**)__env))->{entry.Name} (__env");
} else {
o.Write ("__info.Invoker.{0} (__info.EnvironmentPointer", entry.Name);
}
for (int i = 0; i < entry.Parameters.Length; i++) {
var p = entry.Parameters [i];
o.Write (", ");
if (p.Type.GetManagedType (style, isReturn: false).StartsWith ("out ", StringComparison.Ordinal))
var needOut = p.Type.GetManagedType (style, isReturn: false).StartsWith ("out ", StringComparison.Ordinal);
if (needOut && style == HandleStyle.JIFunctionPtrWithErrors) {
o.Write ("&");
} else if (needOut) {
o.Write ("out ");
}
o.Write (p.Type.GetManagedToMarshalExpression (style, Escape (entry.Parameters [i].Name)));
}
o.WriteLine (");");
if (style == HandleStyle.JIFunctionPtrWithErrors && entry.Throws) {
o.WriteLine ("\t\t\tIntPtr thrown = (*((JNIEnv**)__env))->ExceptionOccurred (__env);");
}
CleanupParameters (o, entry.Parameters, style);
RaiseException (o, entry, style);
if (is_void) {
} else {
Expand Down Expand Up @@ -436,14 +480,38 @@ static void NullCheckParameters (TextWriter o, ParamInfo[] ps, HandleStyle style
o.WriteLine ();
}

static void PrepareParameters (TextWriter o, ParamInfo[] ps, HandleStyle style)
{
bool haveChecks = false;
foreach (var e in ps) {
foreach (var s in e.Type.GetManagedToMarshalPrepareStatements (style, Escape (e.Name))) {
haveChecks = true;
o.WriteLine ($"\t\t\t{s}");
}
}
if (haveChecks)
o.WriteLine ();
}

static void CleanupParameters (TextWriter o, ParamInfo[] ps, HandleStyle style)
{
foreach (var e in ps) {
foreach (var s in e.Type.GetManagedToMarshalCleanupStatements (style, Escape (e.Name))) {
o.WriteLine ($"\t\t\t{s}");
}
}
}

static void RaiseException (TextWriter o, JniFunction entry, HandleStyle style)
{
if (!entry.Throws)
return;

o.WriteLine ();
o.WriteLine ("\t\t\tException __e = JniEnvironment.GetExceptionForLastThrowable ({0});",
style == HandleStyle.JIIntPtrPinvokeWithErrors ? "thrown" : "");
(style == HandleStyle.JIIntPtrPinvokeWithErrors || style == HandleStyle.JIFunctionPtrWithErrors)
? "thrown"
: "");
o.WriteLine ("\t\t\tif (__e != null)");
o.WriteLine ("\t\t\t\tExceptionDispatchInfo.Capture (__e).Throw ();");
o.WriteLine ();
Expand Down Expand Up @@ -728,6 +796,9 @@ public virtual string[] VerifyParameter (HandleStyle style, string variable)
{
return new string [0];
}

public virtual string[] GetManagedToMarshalPrepareStatements (HandleStyle style, string variable) => Array.Empty<string> ();
public virtual string[] GetManagedToMarshalCleanupStatements (HandleStyle style, string variable) => Array.Empty<string> ();
}

class BuiltinTypeInfo : TypeInfo {
Expand Down Expand Up @@ -822,6 +893,9 @@ public StringTypeInfo (string jni)

public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
{
if (style == HandleStyle.JIFunctionPtrWithErrors && isPinvoke) {
return "IntPtr";
}
return "string";
}

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

public override string GetManagedToMarshalExpression (HandleStyle style, string variable)
{
switch (style) {
case HandleStyle.JIFunctionPtrWithErrors:
return $"_{variable}_ptr";
default:
return variable;
}
}

public override string[] GetMarshalToManagedStatements (HandleStyle style, string variable, JniFunction entry)
{
switch (style) {
Expand All @@ -841,6 +925,7 @@ public override string[] GetMarshalToManagedStatements (HandleStyle style, strin
};
case HandleStyle.JIIntPtr:
case HandleStyle.JIIntPtrPinvokeWithErrors:
case HandleStyle.JIFunctionPtrWithErrors:
return new [] {
string.Format ("JniEnvironment.LogCreateLocalRef ({0});", variable),
string.Format ("return new JniObjectReference ({0}, JniObjectReferenceType.Local);", variable),
Expand All @@ -859,6 +944,30 @@ public override string[] VerifyParameter (HandleStyle style, string variable)
string.Format ("\tthrow new ArgumentNullException (\"{0}\");", variableName),
};
}

public override string[] GetManagedToMarshalPrepareStatements (HandleStyle style, string variable)
{
switch (style) {
case HandleStyle.JIFunctionPtrWithErrors:
return new[]{
$"var _{variable}_ptr = Marshal.StringToCoTaskMemUTF8 ({variable});",
};
default:
return base.GetManagedToMarshalPrepareStatements (style, variable);
}
}

public override string[] GetManagedToMarshalCleanupStatements (HandleStyle style, string variable)
{
switch (style) {
case HandleStyle.JIFunctionPtrWithErrors:
return new[]{
$"Marshal.ZeroFreeCoTaskMemUTF8 (_{variable}_ptr);",
};
default:
return base.GetManagedToMarshalCleanupStatements (style, variable);
}
}
}

class JniReleaseArrayElementsModeTypeInfo : TypeInfo {
Expand Down Expand Up @@ -920,6 +1029,7 @@ public override string GetManagedType (HandleStyle style, bool isReturn, bool is
case HandleStyle.SafeHandle:
case HandleStyle.JIIntPtr:
case HandleStyle.JIIntPtrPinvokeWithErrors:
case HandleStyle.JIFunctionPtrWithErrors:
return type;
case HandleStyle.XAIntPtr:
return "IntPtr";
Expand All @@ -933,6 +1043,7 @@ public override string GetManagedToMarshalExpression (HandleStyle style, string
case HandleStyle.SafeHandle:
case HandleStyle.JIIntPtr:
case HandleStyle.JIIntPtrPinvokeWithErrors:
case HandleStyle.JIFunctionPtrWithErrors:
return string.Format ("{0}.ID", variable);
}
return variable;
Expand All @@ -947,6 +1058,7 @@ public override string[] VerifyParameter (HandleStyle style, string variable)
case HandleStyle.SafeHandle:
case HandleStyle.JIIntPtr:
case HandleStyle.JIIntPtrPinvokeWithErrors:
case HandleStyle.JIFunctionPtrWithErrors:
return new [] {
string.Format ("if ({0} == null)", variable),
string.Format ("\tthrow new ArgumentNullException (\"{0}\");", variableName),
Expand All @@ -969,6 +1081,7 @@ public override string[] GetMarshalToManagedStatements (HandleStyle style, strin
case HandleStyle.SafeHandle:
case HandleStyle.JIIntPtr:
case HandleStyle.JIIntPtrPinvokeWithErrors:
case HandleStyle.JIFunctionPtrWithErrors:
return new[] {
string.Format ("if ({0} == IntPtr.Zero)", variable),
string.Format ("\treturn null;"),
Expand Down Expand Up @@ -1045,6 +1158,7 @@ public override string GetMarshalType (HandleStyle style, bool isReturn, bool is
return isReturn ? safeType : "JniReferenceSafeHandle";
case HandleStyle.JIIntPtr:
case HandleStyle.JIIntPtrPinvokeWithErrors:
case HandleStyle.JIFunctionPtrWithErrors:
case HandleStyle.XAIntPtr:
return "jobject";
}
Expand All @@ -1057,6 +1171,7 @@ public override string GetManagedType (HandleStyle style, bool isReturn, bool is
case HandleStyle.SafeHandle:
case HandleStyle.JIIntPtr:
case HandleStyle.JIIntPtrPinvokeWithErrors:
case HandleStyle.JIFunctionPtrWithErrors:
return "JniObjectReference";
case HandleStyle.XAIntPtr:
return "IntPtr";
Expand All @@ -1071,6 +1186,7 @@ public override string GetManagedToMarshalExpression (HandleStyle style, string
return string.Format ("{0}.SafeHandle", variable);
case HandleStyle.JIIntPtr:
case HandleStyle.JIIntPtrPinvokeWithErrors:
case HandleStyle.JIFunctionPtrWithErrors:
return string.Format ("{0}.Handle", variable);
case HandleStyle.XAIntPtr:
return variable;
Expand All @@ -1084,6 +1200,7 @@ public override string[] GetMarshalToManagedStatements (HandleStyle style, strin
case HandleStyle.SafeHandle:
case HandleStyle.JIIntPtr:
case HandleStyle.JIIntPtrPinvokeWithErrors:
case HandleStyle.JIFunctionPtrWithErrors:
return new [] {
string.Format ("return new JniObjectReference ({0}, {1});", variable, refType),
};
Expand All @@ -1104,6 +1221,7 @@ public override string[] VerifyParameter (HandleStyle style, string variable)
case HandleStyle.SafeHandle:
case HandleStyle.JIIntPtr:
case HandleStyle.JIIntPtrPinvokeWithErrors:
case HandleStyle.JIFunctionPtrWithErrors:
return new [] {
string.Format ("if (!{0}.IsValid)", variable),
string.Format ("\tthrow new ArgumentException (\"Handle must be valid.\", \"{0}\");", variableName),
Expand Down Expand Up @@ -1160,13 +1278,50 @@ public JavaVMPointerTypeInfo (string jni)

public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
{
if (style == HandleStyle.JIFunctionPtrWithErrors && isPinvoke) {
return "IntPtr*";
}
return "out IntPtr";
}

public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
{
return "out IntPtr";
}

public override string GetManagedToMarshalExpression (HandleStyle style, string variable)
{
switch (style) {
case HandleStyle.JIFunctionPtrWithErrors:
return $"_{variable}_ptr";
default:
return variable;
}
}

public override string[] GetManagedToMarshalPrepareStatements (HandleStyle style, string variable)
{
switch (style) {
case HandleStyle.JIFunctionPtrWithErrors:
return new[]{
$"IntPtr _{variable}_ptr = IntPtr.Zero;",
};
default:
return base.GetManagedToMarshalPrepareStatements (style, variable);
}
}

public override string[] GetManagedToMarshalCleanupStatements (HandleStyle style, string variable)
{
switch (style) {
case HandleStyle.JIFunctionPtrWithErrors:
return new[]{
$"{variable} = _{variable}_ptr;",
};
default:
return base.GetManagedToMarshalCleanupStatements (style, variable);
}
}
}

class ParamInfo
Expand Down Expand Up @@ -1204,6 +1359,7 @@ enum HandleStyle {
JIIntPtr,
JIIntPtrPinvokeWithErrors,
XAIntPtr,
JIFunctionPtrWithErrors,
}
}

Loading

0 comments on commit e9fd048

Please sign in to comment.