Skip to content

[generator] Cache static final field values. #1248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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