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

Lines changed: 166 additions & 10 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy