Skip to content

Commit

Permalink
[generator] Cache static final field values.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpobst committed Aug 28, 2024
1 parent 51b784a commit e56c65d
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 10 deletions.
13 changes: 9 additions & 4 deletions src/Xamarin.SourceWriter/Models/TypeReferenceWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,17 @@ public TypeReferenceWriter (string ns, string name)

public virtual void WriteTypeReference (CodeWriter writer)
{
if (Namespace.HasValue ())
writer.Write ($"{Namespace}.{Name}{NullableOperator} ");
else
writer.Write ($"{Name}{NullableOperator} ");
writer.Write ($"{ToString ()} ");
}

string NullableOperator => Nullable ? "?" : string.Empty;

public override string ToString ()
{
if (Namespace.HasValue ())
return $"{Namespace}.{Name}{NullableOperator}";

return $"{Name}{NullableOperator}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
[global::Java.Interop.JniTypeSignature ("java/code/MyClass", GenerateJavaPeer=false)]
public partial class MyClass {
private static java.code.Example? _field_cache;

// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
public static java.code.Example? field {
get {
if (_field_cache != null) return (java.code.Example)_field_cache;

const string __id = "field.Ljava/code/Example;";

var __v = _members.StaticFields.GetObjectValue (__id);
return (java.code.Example?)(_field_cache = global::Java.Interop.JniEnvironment.Runtime.ValueManager.GetValue<java.code.Example? >(ref __v, JniObjectReferenceOptions.Copy));
}
}

static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/MyClass", typeof (MyClass));

protected MyClass (ref JniObjectReference reference, JniObjectReferenceOptions options) : base (ref reference, options)
{
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
[global::Java.Interop.JniTypeSignature ("java/code/MyClass", GenerateJavaPeer=false)]
public partial class MyClass {
private static int? _field_cache;

// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
public static int field {
get {
if (_field_cache != null) return (int)_field_cache;

const string __id = "field.I";

var __v = _members.StaticFields.GetInt32Value (__id);
return (int)(_field_cache = __v);
}
}

static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/MyClass", typeof (MyClass));

protected MyClass (ref JniObjectReference reference, JniObjectReferenceOptions options) : base (ref reference, options)
{
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)]
public partial class MyClass {
private static java.code.Example? _field_cache;

// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
[Register ("field")]
public static java.code.Example? field {
get {
if (_field_cache != null) return (java.code.Example)_field_cache;

const string __id = "field.Ljava/code/Example;";

var __v = _members.StaticFields.GetObjectValue (__id);
return (java.code.Example?)(_field_cache = global::Java.Lang.Object.GetObject<java.code.Example> (__v.Handle, JniHandleOwnership.TransferLocalRef));
}
}

static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/MyClass", typeof (MyClass));

internal static IntPtr class_ref {
get { return _members.JniPeerType.PeerReference.Handle; }
}

protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
{
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)]
public partial class MyClass {
private static int? _field_cache;

// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
[Register ("field")]
public static int field {
get {
if (_field_cache != null) return (int)_field_cache;

const string __id = "field.I";

var __v = _members.StaticFields.GetInt32Value (__id);
return (int)(_field_cache = __v);
}
}

static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/MyClass", typeof (MyClass));

internal static IntPtr class_ref {
get { return _members.JniPeerType.PeerReference.Handle; }
}

protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
{
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)]
public partial class MyClass {

// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
[Register ("field")]
public static java.code.Example field {
get {
const string __id = "field.Ljava/code/Example;";

var __v = _members.StaticFields.GetObjectValue (__id);
return global::Java.Lang.Object.GetObject<java.code.Example> (__v.Handle, JniHandleOwnership.TransferLocalRef);
}
}

static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/MyClass", typeof (MyClass));

internal static IntPtr class_ref {
get { return _members.JniPeerType.PeerReference.Handle; }
}

protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
{
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)]
public partial class MyClass {

// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
[Register ("field")]
public static int field {
get {
const string __id = "field.I";

var __v = _members.StaticFields.GetInt32Value (__id);
return __v;
}
}

static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/MyClass", typeof (MyClass));

internal static IntPtr class_ref {
get { return _members.JniPeerType.PeerReference.Handle; }
}

protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
{
}

}
42 changes: 42 additions & 0 deletions tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1644,5 +1644,47 @@ public void WriteBoundMethodAbstractDeclarationWithGenericReturn ()
// Ignore nullable operator so this test works on all generators ;)
Assert.AreEqual (expected.NormalizeLineEndings (), writer.ToString ().NormalizeLineEndings ().Replace ("?", ""));
}

[Test]
public void WriteCachedReferenceTypeField ()
{
options.SymbolTable.AddType (new TestClass (null, "Java.Lang.Object"));
var eClass = new TestClass ("Java.Lang.Object", "java.code.Example");
options.SymbolTable.AddType (eClass);

var klass = new TestClass ("Object", "java.code.MyClass");

var field = SupportTypeBuilder.CreateField ("field", options, "java.code.Example", true);
field.IsFinal = true;

klass.Fields.Add (field);

generator.Context.ContextTypes.Push (klass);
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

AssertTargetedExpected (nameof (WriteCachedReferenceTypeField), writer.ToString ());
}

[Test]
public void WriteCachedValueTypeField ()
{
options.SymbolTable.AddType (new TestClass (null, "Java.Lang.Object"));
var eClass = new TestClass ("Java.Lang.Object", "java.code.Example");
options.SymbolTable.AddType (eClass);

var klass = new TestClass ("Object", "java.code.MyClass");

var field = SupportTypeBuilder.CreateField ("field", options, "int", true);
field.IsFinal = true;

klass.Fields.Add (field);

generator.Context.ContextTypes.Push (klass);
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

AssertTargetedExpected (nameof (WriteCachedValueTypeField), writer.ToString ());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public class Field : ApiVersionsSupport.IApiAvailability, ISourceLineInfo

public bool NeedsProperty => !IsStatic || !IsFinal || string.IsNullOrEmpty (Value) || Symbol.IsArray || !primitive_types.Contains (Symbol.JavaName);

public string CachedMemberName => $"_{Name}_cache";

public bool Validate (CodeGenerationOptions opt, GenericParameterDefinitionList type_params, CodeGeneratorContext context)
{
Symbol = opt.SymbolTable.Lookup (TypeName, type_params);
Expand Down
36 changes: 30 additions & 6 deletions tools/generator/SourceWriters/BoundFieldAsProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class BoundFieldAsProperty : PropertyWriter
{
readonly Field field;
readonly CodeGenerationOptions opt;
readonly FieldWriter? cached_field;

public BoundFieldAsProperty (GenBase type, Field field, CodeGenerationOptions opt)
{
Expand Down Expand Up @@ -59,10 +60,23 @@ public BoundFieldAsProperty (GenBase type, Field field, CodeGenerationOptions op

if (!field.IsConst)
HasSet = true;

// This is considerably harder to support if we don't have NRT, due to the
// differences in handling nullable value and reference types.
if (field.IsConst && opt.SupportNullableReferenceTypes)
cached_field = new FieldWriter {
Name = field.CachedMemberName,
Type = new TypeReferenceWriter (fieldType.TrimEnd ('?')) { Nullable = true },
IsStatic = true,
IsPrivate = true,
UseExplicitPrivateKeyword = type is InterfaceGen,
};
}

public override void Write (CodeWriter writer)
{
cached_field?.Write (writer);

// This is just a temporary hack to write the [GeneratedEnum] attribute before the // Metadata.xml
// comment so that we are 100% equal to pre-refactor.
var generated_attr = Attributes.OfType<GeneratedEnumAttr> ().FirstOrDefault ();
Expand All @@ -82,6 +96,13 @@ public override void WriteAttributes (CodeWriter writer)

protected override void WriteGetterBody (CodeWriter writer)
{
var cached_field_type = cached_field is not null ? new TypeReferenceWriter (cached_field.Type.Namespace, cached_field.Type.Name) : null;

if (cached_field is not null) {
writer.WriteLine ($"if ({field.CachedMemberName} != null) return ({cached_field_type}){field.CachedMemberName};");
writer.WriteLine ();
}

writer.WriteLine ($"const string __id = \"{field.JavaName}.{field.Symbol.JniName}\";");
writer.WriteLine ();

Expand All @@ -93,24 +114,27 @@ protected override void WriteGetterBody (CodeWriter writer)

writer.WriteLine ($"var __v = {field.Symbol.ReturnCast}_members.{indirect}.{invoke} (__id{(field.IsStatic ? "" : ", this")});");

var cache_setter = cached_field is not null ? $"({PropertyType})({field.CachedMemberName} = " : "";
var cache_setter_end = cached_field is not null ? ")" : "";

if (opt.CodeGenerationTarget == CodeGenerationTarget.JavaInterop1) {
if (field.Symbol.NativeType == field.Symbol.FullName) {
writer.WriteLine ("return __v;");
writer.WriteLine ($"return {cache_setter}__v{cache_setter_end};");
return;
}
writer.Write ("return global::Java.Interop.JniEnvironment.Runtime.ValueManager.GetValue<");
writer.Write ($"return {cache_setter}global::Java.Interop.JniEnvironment.Runtime.ValueManager.GetValue<");
PropertyType.WriteTypeReference (writer);
writer.Write (">(ref __v, JniObjectReferenceOptions.Copy)");
writer.WriteLine (";");
writer.WriteLine ($"{cache_setter_end};");
return;
}

if (field.Symbol.IsArray) {
writer.WriteLine ($"return global::Android.Runtime.JavaArray<{opt.GetOutputName (field.Symbol.ElementType)}>.FromJniHandle (__v.Handle, JniHandleOwnership.TransferLocalRef);");
writer.WriteLine ($"return {cache_setter}global::Android.Runtime.JavaArray<{opt.GetOutputName (field.Symbol.ElementType)}>.FromJniHandle (__v.Handle, JniHandleOwnership.TransferLocalRef){cache_setter_end};");
} else if (field.Symbol.NativeType != field.Symbol.FullName) {
writer.WriteLine ($"return {field.Symbol.ReturnCast}{(field.Symbol.FromNative (opt, invokeType != "Object" ? "__v" : "__v.Handle", true) + opt.GetNullForgiveness (field))};");
writer.WriteLine ($"return {cache_setter}{field.Symbol.ReturnCast}{(field.Symbol.FromNative (opt, invokeType != "Object" ? "__v" : "__v.Handle", true) + opt.GetNullForgiveness (field))}{cache_setter_end};");
} else {
writer.WriteLine ("return __v;");
writer.WriteLine ($"return {cache_setter}__v{cache_setter_end};");
}
}

Expand Down
1 change: 1 addition & 0 deletions tools/generator/generator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>$(DotNetTargetFramework)</TargetFramework>
<OutputType>Exe</OutputType>
<DefineConstants>$(DefineConstants);GENERATOR;HAVE_CECIL;JCW_ONLY_TYPE_NAMES</DefineConstants>
<Nullable>annotations</Nullable>
</PropertyGroup>

<Import Project="..\..\TargetFrameworkDependentValues.props" />
Expand Down

0 comments on commit e56c65d

Please sign in to comment.