Optimizations

This commit is contained in:
spaceflint 2021-07-05 19:32:34 +03:00
parent 30f187a636
commit 3d84879ed8
10 changed files with 504 additions and 48 deletions

View File

@ -320,11 +320,15 @@ namespace SpaceFlint.CilToJava
public static List<CilInterfaceMethod> CollectAll(TypeDefinition fromType)
{
var list = new List<CilInterfaceMethod>();
Process(fromType, list);
return list;
var map = new Dictionary<string, List<CilInterfaceMethod>>();
Process(fromType, map);
void Process(TypeDefinition fromType, List<CilInterfaceMethod> list)
var newList = new List<CilInterfaceMethod>();
foreach (var oldList in map.Values)
newList.AddRange(oldList);
return newList;
void Process(TypeDefinition fromType, Dictionary<string, List<CilInterfaceMethod>> map)
{
foreach (var fromMethod in fromType.Methods)
{
@ -343,19 +347,26 @@ namespace SpaceFlint.CilToJava
var outputMethod = new CilInterfaceMethod(inputMethod);
bool dup = false;
foreach (var oldMethod in list)
if (map.TryGetValue(inputMethod.Name, out var list))
{
if ( oldMethod.Method.Name == outputMethod.Method.Name
&& oldMethod.EqualParameters(outputMethod))
bool dup = false;
foreach (var oldMethod in list)
{
dup = true;
break;
if (oldMethod.EqualParameters(outputMethod))
{
dup = true;
break;
}
}
if (! dup)
list.Add(outputMethod);
}
else
{
var list2 = new List<CilInterfaceMethod>();
list2.Add(outputMethod);
map.Add(inputMethod.Name, list2);
}
if (! dup)
list.Add(outputMethod);
CilMain.GenericStack.Release(genericMark);
}
@ -369,7 +380,7 @@ namespace SpaceFlint.CilToJava
var genericMark = CilMain.GenericStack.Mark();
CilMain.GenericStack.EnterType(fromBaseTypeDef);
Process(fromBaseTypeDef, list);
Process(fromBaseTypeDef, map);
CilMain.GenericStack.Release(genericMark);
}

View File

@ -708,13 +708,21 @@ namespace SpaceFlint.CilToJava
static Dictionary<MethodReference, MethodDefinition> _MethodDefs =
new Dictionary<MethodReference, MethodDefinition>();
internal static MethodDefinition AsDefinition(MethodReference _ref)
{
if (_ref.IsDefinition)
return _ref as MethodDefinition;
var def = _ref.Resolve();
if (def != null)
if (_MethodDefs.TryGetValue(_ref, out var def))
return def;
def = _ref.Resolve();
if (def != null)
{
_MethodDefs.Add(_ref, def);
return def;
}
throw CilMain.Where.Exception(
$"could not resolve method '{_ref.Name}' from assembly '{_ref.DeclaringType.Scope}'");
}

View File

@ -25,6 +25,10 @@ namespace SpaceFlint.CilToJava
{
if (! _Types.TryGetValue(fromType, out var converted))
{
converted = GetCachedPrimitive(fromType);
if (converted != null)
return converted;
CilMain.Where.Push($"type '{fromType.FullName}'");
converted = new CilType();
@ -89,11 +93,12 @@ namespace SpaceFlint.CilToJava
}
else
{
var metadataType = AsDefinition(fromType).MetadataType;
var defType = AsDefinition(fromType);
var metadataType = defType.MetadataType;
bool isValueType = (metadataType == MetadataType.ValueType);
if (isValueType || (metadataType == MetadataType.Class))
{
ImportClass(fromType, isValueType);
ImportClass(fromType, defType, isValueType);
}
else
{
@ -143,12 +148,10 @@ namespace SpaceFlint.CilToJava
void ImportClass(TypeReference fromType, bool isValue)
void ImportClass(TypeReference fromType, TypeDefinition defType, bool isValue)
{
int numGeneric = ImportGenericParameters(fromType);
var defType = AsDefinition(fromType);
if (defType.HasCustomAttribute(
"System.Runtime.Remoting.Contexts.SynchronizationAttribute", true))
throw CilMain.Where.Exception($"attribute [Synchronization] is not supported");
@ -226,9 +229,7 @@ namespace SpaceFlint.CilToJava
Flags |= INTERFACE;
else if (IsDelegateClass(defType))
{
Flags |= DELEGATE;
}
}
PrimitiveType = TypeCode.Empty;
@ -434,7 +435,8 @@ namespace SpaceFlint.CilToJava
for (int i = 0; i <= n; i++)
{
if (SuperTypes[i].HasGenericParameters || SuperTypes[i].HasGenericSuperType)
//if (SuperTypes[i].HasGenericParameters || SuperTypes[i].HasGenericSuperType)
if (SuperTypes[i].IsGenericThisOrSuper)
Flags |= HAS_GEN_SUP;
}
}
@ -607,21 +609,67 @@ namespace SpaceFlint.CilToJava
static Dictionary<TypeReference, TypeDefinition> _TypeDefs =
new Dictionary<TypeReference, TypeDefinition>();
internal static TypeDefinition AsDefinition(TypeReference _ref)
{
if (_ref.IsDefinition)
return _ref as TypeDefinition;
var def = _ref.Resolve();
if (def != null)
if (_TypeDefs.TryGetValue(_ref, out var def))
return def;
if (_ref.GetElementType() is GenericParameter)
return AsDefinition(_ref.Module.TypeSystem.Object);
def = _ref.Resolve();
if (def == null && _ref.GetElementType() is GenericParameter)
def = AsDefinition(_ref.Module.TypeSystem.Object);
if (def != null)
{
_TypeDefs.Add(_ref, def);
return def;
}
throw CilMain.Where.Exception(
$"could not resolve type '{_ref}' from assembly '{_ref.Scope}'");
}
static CilType[] _Primitives = new CilType[32];
private static CilType GetCachedPrimitive(TypeReference fromType)
{
var metadataType = fromType.MetadataType;
switch (metadataType)
{
case MetadataType.Void:
case MetadataType.Boolean:
case MetadataType.Char:
case MetadataType.SByte:
case MetadataType.Byte:
case MetadataType.Int16:
case MetadataType.UInt16:
case MetadataType.Int32:
case MetadataType.UInt32:
case MetadataType.Int64:
case MetadataType.UInt64:
case MetadataType.Single:
case MetadataType.Double:
case MetadataType.String:
case MetadataType.IntPtr:
case MetadataType.UIntPtr:
case MetadataType.Object:
var resultType = _Primitives[(int) metadataType];
if (resultType == null)
{
resultType = new CilType();
resultType.Import(AsDefinition(fromType));
_Primitives[(int) metadataType] = resultType;
}
return resultType;
}
return null;
}
protected void SetBoxedFlags(bool clonedAtTop)
=> Flags |= (VALUE | BYREF) | (clonedAtTop ? CLONED_TOP : 0);

View File

@ -525,6 +525,22 @@ namespace SpaceFlint.CilToJava
int SaveMethodArguments(CilMethod callMethod)
{
// pop method arguments off the stack and into temporary locals.
// this is needed when we need to manipulate the object reference,
// which was pushed before the arguments, in some way. for example,
//
// .Net has a 'newobj' which allocates the object and then calls the
// constructor, but we have to pop all arguments, insert a jvm 'new'
// instruction, then push arguments before the call to constructor.
//
// another example is a virtual call which must manipulate the pushed
// object reference in some way. see calls to this method throughout
// this source file for details.
//
// this creates noise of 'load'/'store' instructions in the generated
// code. but disassembly using 'dexdump' shows that the Android 'D8'
// compiler generally optimizes all of this away.
int localIndex = -1;
int i = callMethod.Parameters.Count - (callMethod.HasDummyClassArg ? 1 : 0);
while (i-- > 0)
@ -564,6 +580,8 @@ namespace SpaceFlint.CilToJava
void LoadMethodArguments(CilMethod callMethod, int localIndex)
{
// push methods arguments, saved by SaveMethodArguments.
int i = callMethod.Parameters.Count - (callMethod.HasDummyClassArg ? 1 : 0);
while (i-- > 0)
{

View File

@ -631,11 +631,7 @@ namespace SpaceFlint.CilToJava
else if (! TestForBranch(code, castClass, cilInst.Next))
{
ushort nextLabel = (ushort) cilInst.Next.Offset;
int localIndex = locals.GetTempIndex(stackTop);
TestAndCast(code, castClass, stackTop, nextLabel, localIndex);
locals.FreeTempIndex(localIndex);
TestAndCast(code, castClass, stackTop, nextLabel, locals);
}
}
else
@ -669,14 +665,41 @@ namespace SpaceFlint.CilToJava
//
void TestAndCast(JavaCode code, JavaType castClass, JavaType stackTop,
ushort nextLabel, int localIndex)
ushort nextLabel, CodeLocals locals)
{
code.NewInstruction(stackTop.StoreOpcode, null, (int) localIndex);
int localIndex = -1;
if (cilInst?.Previous is Mono.Cecil.Cil.Instruction prevInst)
{
(_, localIndex) = locals.GetLocalFromLoadInst(
prevInst.OpCode.Code, prevInst.Operand);
}
code.NewInstruction(0x01 /* aconst_null */, null, null);
bool usingTempIndex;
if (localIndex != -1)
{
// in the common case, the the preceding instruction loads
// a local or an argument with a known index number, so we
// don't have a allocate a temporary local variable. but
// we will have to swap the values after pushing null
usingTempIndex = false;
code.NewInstruction(0x01 /* aconst_null */, null, null);
code.NewInstruction(0x5F /* swap */, null, null);
}
else
{
// otherwise we need to allocate a temporary variable, save
// the top of stack into it, push null, and load the temp
usingTempIndex = true;
localIndex = locals.GetTempIndex(stackTop);
code.NewInstruction(stackTop.StoreOpcode, null, localIndex);
code.NewInstruction(0x01 /* aconst_null */, null, null);
code.NewInstruction(stackTop.LoadOpcode, null, localIndex);
}
// the stack top has two values: null and objref
code.StackMap.PushStack(castClass);
code.NewInstruction(stackTop.LoadOpcode, null, (int) localIndex);
code.StackMap.PushStack(stackTop);
code.NewInstruction(0xC1 /* instanceof */, castClass, null);
@ -688,10 +711,13 @@ namespace SpaceFlint.CilToJava
code.NewInstruction(0x57 /* pop */, null, null);
code.StackMap.PopStack(CilMain.Where);
code.NewInstruction(stackTop.LoadOpcode, null, (int) localIndex);
code.NewInstruction(stackTop.LoadOpcode, null, localIndex);
code.NewInstruction(0xC0 /* checkcast */, castClass, null);
code.StackMap.PushStack(castClass);
if (usingTempIndex)
locals.FreeTempIndex(localIndex);
}
}

View File

@ -635,8 +635,10 @@ namespace SpaceFlint.CilToJava
public (CilType, int) GetLocalFromLoadInst(Code op, object data)
{
int localIndex;
if (op >= Code.Ldloc_0 && op <= Code.Ldloc_3)
if (op >= Code.Ldloc_0 && op <= Code.Ldloc_3)
localIndex = VariableIndex(op - Code.Ldloc_0);
else if (op >= Code.Ldarg_0 && op <= Code.Ldarg_3)
localIndex = ArgumentIndex(op - Code.Ldarg_0);
else if (data is ParameterDefinition dataArg)
localIndex = ArgumentIndex(dataArg.Sequence);
else if (data is VariableDefinition dataVar)

View File

@ -12,6 +12,7 @@ namespace SpaceFlint.JavaBinary
{
wtr.Where.Push("method body");
PerformOptimizations();
EliminateNops();
int codeLength = FillInstructions(wtr);
@ -619,6 +620,35 @@ namespace SpaceFlint.JavaBinary
void PerformOptimizations()
{
int n = Instructions.Count;
var prevInst = Instructions[0];
for (int i = 1; i < n; i++)
{
var currInst = Instructions[i];
// find occurrences of iconst_0 or iconst_1 followed by i2l
// (which must not be a branch target), and convert to lconst_xx
if (currInst.Opcode == 0x85 /* i2l */)
{
if ( prevInst.Opcode == 0x12 /* ldc */
&& prevInst.Data is int intValue
&& (intValue == 0 || intValue == 1)
&& (! StackMap.HasBranchFrame(currInst.Label)))
{
// convert ldc of 0 or 1 into lconst_0 or lconst_1
prevInst.Opcode = (byte) (intValue + 0x09);
currInst.Opcode = 0x00; // nop
}
}
prevInst = currInst;
}
}
void EliminateNops()
{
// remove any nop instructions that are not a branch target,

View File

@ -1,6 +1,6 @@
# Bluebonnet
This is an initial release of a partial implementation of the .NET platform on top of the Java Virtual Machine, and compatible with Android runtime. The **Bluebonnet** bytecode compiler translates .NET [CIL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) into [Java bytecode](https://en.wikipedia.org/wiki/Java_bytecode) in Java classes, and additional run-time support is provided by the **Baselib** library.
This is a partial implementation of the .NET platform on top of the Java Virtual Machine, and compatible with Android runtime. The **Bluebonnet** bytecode compiler translates .NET [CIL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) into [Java bytecode](https://en.wikipedia.org/wiki/Java_bytecode) in Java classes, and additional run-time support is provided by the **Baselib** library.
https://www.spaceflint.com/bluebonnet
@ -12,16 +12,18 @@ https://www.spaceflint.com/bluebonnet
- Simple interoperability with Java APIs.
- Tested with C# and F# programs.
## Requirements
## Requirements for Building
- Java 8 is required during building, to import Java classes from the `rt.jar` file.
- Importing from Java 9 modules is not yet supported.
- The translated code can run on Java 8 or later version.
- Alternatively, `(ANDROID_HOME)/platforms/android-XX/android.jar` from Android SDK can be copied as `(JAVA_HOME)\jre\lib\rt.jar` file.
- Android build tools with support for `D8`/`R8` desugaring.
- Tested with build tools version 30.0.2 and platform API version 28.
- .NET Framework 4.7.2
- Tested only on Windows at this time. Not thoroughly tested with .NET Core.
- Tested with build tools version 30.0.2 and platform API version 30.
- .NET Framework (4.7 or later)
- Tested only on Windows at this time. May not necessarily build with .NET Core.
[Recent releases](https://github.com/spaceflint7/bluebonnet/releases) include a pre-built `Android.dll` reference assembly, created from the latest Android SDK, as well as all binaries needed for running Bluebonnet. See the Usage section below.
## Building
@ -64,4 +66,4 @@ See the [BNA](https://github.com/spaceflint7/bna) and [Unjum](https://github.com
## Usage
For more information about using Bluebonnet, please see the [USAGE.md](USAGE.md) file. That document also records any known differences and deficiencies, compared to a proper .NET implementation.
For more information about using Bluebonnet, please see the [USAGE.md](USAGE.md) file. That document also records any known differences and deficiencies, compared to a proper .NET implementation. To use Bluebonnet in Android Studio, see [USAGE-ANDROID.md](USAGE-ANDROID.md).

311
USAGE-ANDROID.md Normal file
View File

@ -0,0 +1,311 @@
#### Goal
- Set up development of an Android app using a .NET language
- Either in Visual Studio or using the command line `dotnet` tool.
- Use Android Studio to build the app.
- With a Gradle plugin to compile .NET code.
- And a Gradle build task to convert the .NET code to Java compiled form.
- Most of development should be possible on Windows without requiring an Android device.
- Platform-specific parts can be qualified with preprocessor constants.
#### Environment Variables
- Set the environment variable `MSBUILD_EXE` to point to `MSBuild.exe` program file.
- For example, `C:\Program Files (x86)\Microsoft Visual Studio\\2019\Community\MSBuild\Current\Bin\MSBuild.exe`
- Set the environment variable `BLUEBONNET_DIR` to point to a directory containing `Bluebonnet.exe`, `PruneMerge.exe`, `Baselib.jar` and `Android.dll`.
- These files can be downloaded from the [Bluebonnet releases](https://github.com/spaceflint7/bluebonnet/releases) page.
#### .NET Project Using DotNet Tool
- Create a new directory for the project, and change to this directory.
- Create a .NET Core project called `DotNet`:
- `dotnet new console -n DotNet`
- You may use some other project type or language instead of a C# console app.
- The project must be named `DotNet`.
- Edit `DotNet.csproj` to add a reference to Android DLL created by Bluebonnet:
<ItemGroup>
<Reference Include="Android">
<HintPath>$(BLUEBONNET_DIR)\Android.dll</HintPath>
</Reference>
</ItemGroup>
- Or, you can create the project using Visual Studio:
#### .NET Project Using Visual Studio
- Create a new project.
- Select a __C# Console Application__ template (.NET Framework or .NET Core).
- Specify `DotNet` for the project name, and check the box to __Place solution and project in the same directory__.
- Or specify some other project name and clear that checkbox, but make sure:
- The solution name is `DotNet`.
- In __Project Settings__ in Visual Studio, set the __Assembly name__ to `DotNet`.
- Complete the creation of the project and solution.
- Add a reference to `Android.dll` using Visual Studio, or by editing the project file as shown above.
#### Activity Class
- Leave the `Program.cs` file as it is.
- Otherwise you may get a compilation error about a missing `Main` method.
- The class in this file will be discarded from the output, unless explicitly referenced.
- Create a new file named `MainActivity.cs` and paste the following into it:
#if ANDROID
namespace com.whatever.example
{
public sealed class MainActivity : android.app.Activity
{
protected override void onCreate (android.os.Bundle savedInstanceState)
{
android.util.Log.i("EXAMPLE", ">>>>>>>> EXAMPLE ACTIVITY <<<<<<<<");
base.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
#pragma warning disable IDE1006 // Must begin with uppercase letter
#pragma warning disable CA2211 // Non-constant fields should not be visible
[java.attr.Discard] // discard in output
public class R
{
[java.attr.Discard] // discard in output
public class layout
{
[java.attr.RetainType] public static int activity_main;
}
}
}
#endif
#### Android Project in Android Studio
- Create a new project.
- Select the __Empty Activity__ template.
- This generates activity definitions in the `AndroidManifest.xml` file.
- For this example, set __Package name__ to `com.whatever.example`.
- Set the __Save location__ to the project root directory.
- Ignore the warning that this directory is not empty.
- Select __Java__ for the language and a reasonable minimum platform API version (e.g. __18__).
- Complete the creation of the project.
- Delete the Java class generated by Android Studio for the main activity.
- It should be located in the project class `app/java/com.whatever.example/MainActivity`
- Alternatively, delete the entire directory of Java source files - `app/src/main/java`
#### Gradle Build Script
- Open the __module__-specific build script.
- In Android Studio, this is the `build.gradle` file annotated with __Module__ (rather than Project).
- In the directory structure, this is `app/build.gradle` (rather than the top-level file with the same name).
- Note that the right file begins with a `plugins` section followed by an `android` section.
- Optional: Between the `android` section, and the `dependencies` section, insert:
buildDir = "${project.rootDir}/build/${project.name}"
- This sets the output build directory just below the top-level of the project.
- Otherwise the default is the `app/build` directory.
- At the top of the `dependencies` section, insert:
implementation files("$buildDir/dotnet/dotnet.jar")
- After the `dependencies` section, append:
task buildDotNet {
doLast {
delete("${buildDir}/dotnet/DotNet.jar")
exec {
workingDir "${project.rootDir}"
commandLine System.env.MSBUILD_EXE ?: 'msbuild.exe',
'DotNet', '-r',
'-p:OutputType=Library',
'-p:Configuration=Release',
'-p:DefineConstants=ANDROID',
"-p:OutputPath=$buildDir/dotnet",
"-p:IntermediateOutputPath=$buildDir/dotnet/intermediate/"
}
exec {
commandLine "${System.env.BLUEBONNET_DIR}/Bluebonnet.exe",
"${buildDir}/dotnet/DotNet.dll",
"${buildDir}/dotnet/DotNet0.jar"
}
def manifest = new XmlSlurper().parse(android.sourceSets.main.manifest.srcFile)
exec {
commandLine "${System.env.BLUEBONNET_DIR}/PruneMerge.exe",
"${buildDir}/dotnet/DotNet0.jar",
"${System.env.BLUEBONNET_DIR}/Baselib.jar",
"${buildDir}/dotnet/DotNet.jar",
":${manifest.@package}${manifest.application.activity.'@android:name'}"
}
}
}
preBuild.dependsOn buildDotNet
- Note the use of environment variables in the build script:
- `MSBUILD_EXE` should specify the path to the `MSBuild.exe` program.
- `BLUEBONNET_DIR` should specify the directory containing __Bluebonnet__ binaries.
- These environment variables should be made visible to Android Studio.
#### Gradle Build Script - Sample
- After updating your `app/build.gradle` file, it should look (more or less) like this:
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
defaultConfig {
applicationId "com.whatever.example"
minSdkVersion 18
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
buildDir = "${project.rootDir}/build/${project.name}"
dependencies {
implementation files("$buildDir/dotnet/dotnet.jar")
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
task buildDotNet {
doLast {
delete("${buildDir}/dotnet/DotNet.jar")
exec {
workingDir "${project.rootDir}"
commandLine System.env.MSBUILD_EXE ?: 'msbuild.exe',
'DotNet', '-r',
'-p:OutputType=Library',
'-p:Configuration=Release',
'-p:DefineConstants=ANDROID',
"-p:OutputPath=$buildDir/dotnet",
"-p:IntermediateOutputPath=$buildDir/dotnet/intermediate/"
}
exec {
commandLine "${System.env.BLUEBONNET_DIR}/Bluebonnet.exe",
"${buildDir}/dotnet/DotNet.dll",
"${buildDir}/dotnet/DotNet0.jar"
}
def manifest = new XmlSlurper().parse(android.sourceSets.main.manifest.srcFile)
exec {
commandLine "${System.env.BLUEBONNET_DIR}/PruneMerge.exe",
"${buildDir}/dotnet/DotNet0.jar",
"${System.env.BLUEBONNET_DIR}/Baselib.jar",
"${buildDir}/dotnet/DotNet.jar",
":${manifest.@package}${manifest.application.activity.'@android:name'}"
}
}
}
preBuild.dependsOn buildDotNet
#### Gradle Build Script - Overview
- In place of Java source files, a new dependency was added on a JAR file - `dotnet.jar`.
- The `buildDotNet` task was defined to perform the following build commands:
- Run `MSBuild` on the .NET project, in `Release` configuration, with the preprocessor define `ANDROID`
- Run `Bluebonnet` on the resulting `dotnet.dll` to create `dotnet0.jar`
- Extract the class name for the main activity from the file `AndroidManifest.xml`
- Run `PruneMerge` to merge `Baselib.jar` (from Bluebonnet) and `dotnet0.jar` into the final `dotnet.jar`
- All unreferenced classes are discard from the output.
- The `buildDotNet` task was set to execute before the Gradle `preBuild` task.
#### Test It
- Compile and run the project in Android Studio.
- If all goes well, the example app should start in the emulator, and display Hello World!
- This layout comes from the `activity_main.xml` layout file, generated by Android Studio.
- `onCreate()` in the C# `MainActivity` class calls `setContentView` to inflate this layout.
- The __Logcat__ tab should show the debug log message printed by `MainActivity.onCreate()`

View File

@ -12,7 +12,7 @@ By default, all types/classes in the input are processed; one or more wildcard `
If `input_file` is a Java archive, then the output written to `output_file` is a [reference assembly](https://docs.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies) declaring all types in the input. For example,
Bluebonnet (java_home)\jre\lib\rt.jar .obj\Javalib.dll
Bluebonnet $JAVA_HOME\jre\lib\rt.jar .obj\Javalib.dll
#### .NET Assembly to Java Code
@ -56,7 +56,7 @@ Bluebonnet recognizes the following attributes:
- `[java.attr.AsInterfaceAttribute]` on a class causes it to be written in output as an interface. This allows the creation of default method implementations even with versions of C# that do not support this feature. See for example [Baselib/`IDisposable.cs`](https://github.com/spaceflint7/bluebonnet/blob/master/Baselib/src/System/IDisposable.cs).
- `[java.attr.RetainNameAttribute]` on a type or method indicates that renaming should be inhibited. For example, an interface method would be renamed to "<interfaceName><methodName>", to allow a class to implement multiple interfaces. However, this is not appropriate for implementing a Java interface whose methods already have a pre-set method name. See for example [Baselib/`IDisposable.cs`](https://github.com/spaceflint7/bluebonnet/blob/master/Baselib/src/System/IDisposable.cs).
- `[java.attr.RetainNameAttribute]` on a type or method indicates that renaming should be inhibited. For example, an interface method would be renamed to the concatenation of interface name and method name, to allow a class to implement multiple interfaces. However, this is not appropriate for implementing a Java interface whose methods already have a pre-set method name. See for example [Baselib/`IDisposable.cs`](https://github.com/spaceflint7/bluebonnet/blob/master/Baselib/src/System/IDisposable.cs).
These attributes are emitted when Bluebonnet exports Java declarations to a .NET assembly, so they are available when such an assembly is referenced during compilation.