Some fixes
This commit is contained in:
parent
67ad9b0ccc
commit
30f187a636
@ -65,6 +65,7 @@ System.Nullable`*
|
||||
System.SystemException
|
||||
System.OverflowException
|
||||
|
||||
System.Collections.ArrayList
|
||||
System.Collections.BitArray
|
||||
System.Collections.Comparer
|
||||
System.Collections.DictionaryEntry
|
||||
|
@ -130,18 +130,14 @@ namespace system
|
||||
destinationArray, destinationIndex,
|
||||
length);
|
||||
|
||||
var srcArr = sourceArray.arr;
|
||||
var dstArr = destinationArray.arr;
|
||||
var elemType = ((java.lang.Object) srcArr).getClass().getComponentType();
|
||||
if (elemType != ((java.lang.Object) dstArr).getClass().getComponentType())
|
||||
throw new System.ArrayTypeMismatchException();
|
||||
var destinationArray_arr = destinationArray.arr;
|
||||
|
||||
object copy = Clone(destinationArray.arr, destinationArray.len);
|
||||
object copy = Clone(destinationArray_arr, destinationArray.len);
|
||||
try
|
||||
{
|
||||
CopyInternal(srcArr, sourceIndex,
|
||||
dstArr, destinationIndex,
|
||||
elemType, length);
|
||||
CopyInternal(sourceArray.arr, sourceIndex,
|
||||
destinationArray_arr, destinationIndex,
|
||||
length);
|
||||
copy = null;
|
||||
}
|
||||
finally
|
||||
@ -149,8 +145,8 @@ namespace system
|
||||
if (copy != null)
|
||||
{
|
||||
CopyInternal(copy, sourceIndex,
|
||||
dstArr, destinationIndex,
|
||||
elemType, length);
|
||||
destinationArray_arr, destinationIndex,
|
||||
length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -200,22 +196,25 @@ namespace system
|
||||
destinationArray, destinationIndex,
|
||||
length);
|
||||
|
||||
var srcArr = sourceArray.arr;
|
||||
var dstArr = destinationArray.arr;
|
||||
var elemType = ((java.lang.Object) srcArr).getClass().getComponentType();
|
||||
if (elemType != ((java.lang.Object) dstArr).getClass().getComponentType())
|
||||
throw new System.ArrayTypeMismatchException();
|
||||
|
||||
CopyInternal(srcArr, sourceIndex,
|
||||
dstArr, destinationIndex,
|
||||
elemType, length);
|
||||
CopyInternal(sourceArray.arr, sourceIndex,
|
||||
destinationArray.arr, destinationIndex,
|
||||
length);
|
||||
}
|
||||
|
||||
private static void CopyInternal(object srcArr, int srcIndex,
|
||||
object dstArr, int dstIndex,
|
||||
java.lang.Class elemType, int length)
|
||||
int length)
|
||||
{
|
||||
if (system.RuntimeType.IsValueClass(elemType))
|
||||
var srcElemType = ((java.lang.Object) srcArr).getClass().getComponentType();
|
||||
var dstElemType = ((java.lang.Object) dstArr).getClass().getComponentType();
|
||||
if (! dstElemType.isAssignableFrom(srcElemType))
|
||||
throw new System.ArrayTypeMismatchException();
|
||||
|
||||
bool isValueType = system.RuntimeType.IsValueClass(srcElemType);
|
||||
if (isValueType != system.RuntimeType.IsValueClass(dstElemType))
|
||||
throw new System.ArrayTypeMismatchException();
|
||||
|
||||
if (isValueType)
|
||||
{
|
||||
ValueType srcObj, dstObj;
|
||||
if ( object.ReferenceEquals(srcArr, dstArr)
|
||||
|
@ -204,7 +204,7 @@ namespace SpaceFlint.CilToJava
|
||||
var length = stackMap.PopStack(CilMain.Where);
|
||||
stackMap.PushStack(length);
|
||||
if (length.Equals(JavaType.LongType))
|
||||
CodeNumber.Conversion(code, Code.Conv_Ovf_I4);
|
||||
CodeNumber.Conversion(code, Code.Conv_Ovf_I4, null);
|
||||
}
|
||||
|
||||
code.NewInstruction(0xBC /* newarray */, null, elemType.NewArrayType);
|
||||
@ -311,10 +311,18 @@ namespace SpaceFlint.CilToJava
|
||||
if (elemType.PrimitiveType == TypeCode.Byte)
|
||||
{
|
||||
// unsigned byte result should be truncated to 8-bits
|
||||
stackMap.PushStack(JavaType.IntegerType);
|
||||
code.NewInstruction(0x12 /* ldc */, null, (int) 0xFF);
|
||||
code.NewInstruction(0x7E /* iand */, null, null);
|
||||
stackMap.PopStack(CilMain.Where);
|
||||
// (unless already followed by "ldc.i4 255 ; and")
|
||||
bool followedByAndWith255 =
|
||||
CodeBuilder.IsLoadConstant(inst.Next) == 0xFF
|
||||
&& inst.Next.Next?.OpCode.Code == Code.And;
|
||||
|
||||
if (! followedByAndWith255)
|
||||
{
|
||||
stackMap.PushStack(JavaType.IntegerType);
|
||||
code.NewInstruction(0x12 /* ldc */, null, (int) 0xFF);
|
||||
code.NewInstruction(0x7E /* iand */, null, null);
|
||||
stackMap.PopStack(CilMain.Where);
|
||||
}
|
||||
}
|
||||
|
||||
if (arrayType.IsValueClass || elemType.IsValueClass)
|
||||
@ -405,8 +413,29 @@ namespace SpaceFlint.CilToJava
|
||||
// stelem.i2 with a char[] array, should be 'castore' not 'sastore'
|
||||
elemType = arrayType.AdjustRank(-arrayType.ArrayRank);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Android AOT crashes the compilation if an immediate value
|
||||
// is stored into a byte or short array, and the value does
|
||||
// not fit within the range -128..127 or -32768..32767.
|
||||
// simply checing if the previous instruction loaded the
|
||||
// constant is not enough, because due to method inlining
|
||||
// by the Android ART JIT, the immediate value might actually
|
||||
// originate in a calling method.
|
||||
// so we always force the value into range using i2b/i2s.
|
||||
// see also: CodeNumber::ConvertToInteger
|
||||
|
||||
CheckImmediate(arrayType.PrimitiveType, inst);
|
||||
if ( arrayType.PrimitiveType == TypeCode.Boolean
|
||||
|| arrayType.PrimitiveType == TypeCode.SByte
|
||||
|| arrayType.PrimitiveType == TypeCode.Byte)
|
||||
{
|
||||
code.NewInstruction(0x91 /* i2b */, null, null);
|
||||
}
|
||||
else if ( arrayType.PrimitiveType == TypeCode.Int16)
|
||||
{
|
||||
code.NewInstruction(0x93 /* i2s */, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (arrayType.IsValueClass || elemType.IsValueClass)
|
||||
{
|
||||
@ -415,32 +444,6 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
code.NewInstruction(elemType.StoreArrayOpcode, null, null);
|
||||
}
|
||||
|
||||
|
||||
void CheckImmediate(TypeCode arrayPrimitiveType, Mono.Cecil.Cil.Instruction inst)
|
||||
{
|
||||
// Android AOT aborts the compilation with a crash if storing
|
||||
// an immediate value that does not fit in the target array.
|
||||
if ( inst != null && inst.Previous != null
|
||||
&& inst.Previous.OpCode.Code == Code.Ldc_I4)
|
||||
{
|
||||
var imm = (int) inst.Previous.Operand;
|
||||
if ( arrayType.PrimitiveType == TypeCode.Boolean
|
||||
|| arrayType.PrimitiveType == TypeCode.SByte
|
||||
|| arrayType.PrimitiveType == TypeCode.Byte)
|
||||
{
|
||||
if (imm < -128 || imm >= 128)
|
||||
code.NewInstruction(0x91 /* i2b */, null, null);
|
||||
}
|
||||
else if ( arrayType.PrimitiveType == TypeCode.Char
|
||||
|| arrayType.PrimitiveType == TypeCode.Int16
|
||||
|| arrayType.PrimitiveType == TypeCode.UInt16)
|
||||
{
|
||||
if (imm < -32768 || imm >= 32768)
|
||||
code.NewInstruction(0x93 /* i2s */, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -276,7 +276,7 @@ namespace SpaceFlint.CilToJava
|
||||
case Code.Ldc_I8: case Code.Ldc_R4: case Code.Ldc_R8:
|
||||
case Code.Ldstr: case Code.Ldnull:
|
||||
|
||||
LoadConstant(cilOp, cilInst.Operand);
|
||||
LoadConstant(cilOp, cilInst);
|
||||
break;
|
||||
|
||||
case Code.Ldfld: case Code.Ldflda: case Code.Stfld:
|
||||
@ -363,7 +363,7 @@ namespace SpaceFlint.CilToJava
|
||||
case Code.Conv_U: case Code.Conv_Ovf_U: case Code.Conv_Ovf_U_Un:
|
||||
case Code.Conv_R4: case Code.Conv_R8: case Code.Conv_R_Un:
|
||||
|
||||
CodeNumber.Conversion(code, cilOp);
|
||||
CodeNumber.Conversion(code, cilOp, cilInst);
|
||||
break;
|
||||
|
||||
case Code.Add: case Code.Sub: case Code.Mul: case Code.Neg:
|
||||
@ -374,7 +374,7 @@ namespace SpaceFlint.CilToJava
|
||||
case Code.Sub_Ovf: case Code.Sub_Ovf_Un:
|
||||
case Code.Mul_Ovf: case Code.Mul_Ovf_Un:
|
||||
|
||||
CodeNumber.Calculation(code, cilOp);
|
||||
CodeNumber.Calculation(code, cilOp, cilInst);
|
||||
break;
|
||||
|
||||
case Code.Ldind_I1: case Code.Ldind_U1: case Code.Ldind_I2: case Code.Ldind_U2:
|
||||
|
@ -36,7 +36,7 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
|
||||
|
||||
void LoadConstant(Code op, object data)
|
||||
void LoadConstant(Code op, Mono.Cecil.Cil.Instruction inst)
|
||||
{
|
||||
JavaType pushType;
|
||||
object pushValue;
|
||||
@ -50,6 +50,7 @@ namespace SpaceFlint.CilToJava
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = inst.Operand;
|
||||
pushOpcode = 0x12; // ldc
|
||||
|
||||
if (data is string stringVal) // Code.Ldstr
|
||||
@ -72,6 +73,13 @@ namespace SpaceFlint.CilToJava
|
||||
pushValue = longVal;
|
||||
pushType = JavaType.LongType;
|
||||
}
|
||||
else if (IsAndBeforeShift(inst, code))
|
||||
{
|
||||
// jvm shift instructions mask the shift count, so
|
||||
// eliminate AND-ing with 31 and 63 prior to a shift
|
||||
code.NewInstruction(0x00 /* nop */, null, null);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
pushType = JavaType.IntegerType;
|
||||
@ -96,6 +104,58 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
|
||||
|
||||
public static int? IsLoadConstant(Mono.Cecil.Cil.Instruction inst)
|
||||
{
|
||||
if (inst != null)
|
||||
{
|
||||
var op = inst.OpCode.Code;
|
||||
var data = inst.Operand;
|
||||
if (op == Code.Ldc_I4 && data is int intVal)
|
||||
return intVal;
|
||||
if (op == Code.Ldc_I4_S && data is sbyte sbyteVal)
|
||||
return sbyteVal;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static bool IsAndBeforeShift(Mono.Cecil.Cil.Instruction inst, JavaCode code)
|
||||
{
|
||||
// jvm shift instructions mask the shift count, so
|
||||
// eliminate AND-ing with 31 and 63 prior to a shift.
|
||||
|
||||
// the input inst should point to the first of three
|
||||
// instructions, and here we check if the sequence is:
|
||||
// ldc_i4 31 or 63; and; shift
|
||||
|
||||
// used by LoadConstant (see above), CodeNumber::Calculation
|
||||
|
||||
var next1 = inst.Next;
|
||||
if (next1 != null && next1.OpCode.Code == Code.And)
|
||||
{
|
||||
var next2 = next1.Next;
|
||||
if (next2 != null && ( next2.OpCode.Code == Code.Shl
|
||||
|| next2.OpCode.Code == Code.Shr
|
||||
|| next2.OpCode.Code == Code.Shr_Un))
|
||||
{
|
||||
var stackArray = code.StackMap.StackArray();
|
||||
if ( stackArray.Length >= 2
|
||||
&& IsLoadConstant(inst) is int shiftMask
|
||||
&& ( ( shiftMask == 31
|
||||
&& stackArray[0].Equals(JavaType.IntegerType))
|
||||
|| ( shiftMask == 63
|
||||
&& stackArray[0].Equals(JavaType.LongType))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CastToClass(object data)
|
||||
{
|
||||
var srcType = (CilType) code.StackMap.PopStack(CilMain.Where);
|
||||
|
@ -10,7 +10,8 @@ namespace SpaceFlint.CilToJava
|
||||
public static class CodeNumber
|
||||
{
|
||||
|
||||
public static void Conversion(JavaCode code, Code cilOp)
|
||||
public static void Conversion(JavaCode code, Code cilOp,
|
||||
Mono.Cecil.Cil.Instruction cilInst)
|
||||
{
|
||||
var oldType = (CilType) code.StackMap.PopStack(CilMain.Where);
|
||||
var (newType, overflow, unsigned) = ConvertOpCodeToTypeCode(cilOp);
|
||||
@ -32,7 +33,10 @@ namespace SpaceFlint.CilToJava
|
||||
op = ConvertToLong(code, oldType.PrimitiveType, newType);
|
||||
|
||||
else
|
||||
op = ConvertToInteger(code, oldType.PrimitiveType, newType);
|
||||
{
|
||||
var opNext = cilInst.Next?.OpCode.Code ?? 0;
|
||||
op = ConvertToInteger(code, oldType.PrimitiveType, newType, opNext);
|
||||
}
|
||||
|
||||
if (op != -1)
|
||||
code.NewInstruction((byte) op, null, null);
|
||||
@ -82,7 +86,7 @@ namespace SpaceFlint.CilToJava
|
||||
return -1; // no output
|
||||
}
|
||||
|
||||
else if (! unsigned) // && (newType == TypeCode.Double)
|
||||
else if (! unsigned)
|
||||
{
|
||||
//
|
||||
// convert to double
|
||||
@ -184,7 +188,7 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
|
||||
|
||||
static int ConvertToInteger(JavaCode code, TypeCode oldType, TypeCode newType)
|
||||
static int ConvertToInteger(JavaCode code, TypeCode oldType, TypeCode newType, Code opNext)
|
||||
{
|
||||
if (oldType == TypeCode.Double)
|
||||
{
|
||||
@ -208,10 +212,23 @@ namespace SpaceFlint.CilToJava
|
||||
}
|
||||
|
||||
if (newType == TypeCode.SByte)
|
||||
{
|
||||
// Stelem_I1 inserts 'i2b' (see CodeArrays::Store)
|
||||
if (opNext == Code.Stelem_I1)
|
||||
return 0x00; // nop
|
||||
|
||||
return 0x91; // i2b
|
||||
}
|
||||
|
||||
if (newType == TypeCode.Byte)
|
||||
{
|
||||
if (opNext == Code.Stelem_I1)
|
||||
{
|
||||
// if the next instruction is Stelem.I1, which inserts 'i2b'
|
||||
// (see CodeArrays::Store), then skip the masking below
|
||||
return 0x00; // nop
|
||||
}
|
||||
|
||||
code.StackMap.PushStack(JavaType.IntegerType);
|
||||
code.NewInstruction(0x12 /* ldc */, null, (int) 0xFF);
|
||||
code.StackMap.PushStack(JavaType.IntegerType);
|
||||
@ -221,7 +238,16 @@ namespace SpaceFlint.CilToJava
|
||||
}
|
||||
|
||||
if (newType == TypeCode.Int16)
|
||||
{
|
||||
if (opNext == Code.Stelem_I2)
|
||||
{
|
||||
// the next instruction is Stelem.I2, which inserts 'i2s'
|
||||
// (see CodeArrays::Store)
|
||||
return 0x00; // nop
|
||||
}
|
||||
|
||||
return 0x93; // i2s
|
||||
}
|
||||
|
||||
if (newType == TypeCode.UInt16)
|
||||
return 0x92; // i2c
|
||||
@ -333,8 +359,18 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
|
||||
|
||||
public static void Calculation(JavaCode code, Code cilOp)
|
||||
public static void Calculation(JavaCode code, Code cilOp,
|
||||
Mono.Cecil.Cil.Instruction cilInst)
|
||||
{
|
||||
if ( cilOp == Code.And
|
||||
&& CodeBuilder.IsAndBeforeShift(cilInst.Previous, code))
|
||||
{
|
||||
// jvm shift instructions mask the shift count, so
|
||||
// eliminate AND-ing with 31 and 63 prior to a shift
|
||||
code.NewInstruction(0x00 /* nop */, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
var stackTop1 = code.StackMap.PopStack(CilMain.Where);
|
||||
if (cilOp == Code.Sub && CodeSpan.SubOffset(stackTop1, code))
|
||||
return;
|
||||
|
@ -12,6 +12,8 @@ namespace SpaceFlint.JavaBinary
|
||||
{
|
||||
wtr.Where.Push("method body");
|
||||
|
||||
EliminateNops();
|
||||
|
||||
int codeLength = FillInstructions(wtr);
|
||||
if (codeLength > 0xFFFE)
|
||||
throw wtr.Where.Exception("output method is too large");
|
||||
@ -434,6 +436,7 @@ namespace SpaceFlint.JavaBinary
|
||||
if (inst.Data is int intOffset)
|
||||
{
|
||||
// int data is a jump offset that can be calculated immediately
|
||||
// note that this prevents nop elimination; see EliminateNops
|
||||
intOffset -= offset;
|
||||
inst.Bytes[1] = (byte) (offset >> 8);
|
||||
inst.Bytes[2] = (byte) offset;
|
||||
@ -614,6 +617,47 @@ namespace SpaceFlint.JavaBinary
|
||||
return (index <= 3) ? 1 : ((index <= 255) ? 2 : 4);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void EliminateNops()
|
||||
{
|
||||
// remove any nop instructions that are not a branch target,
|
||||
// and only if there are no exception tables. note that
|
||||
// removing nops that are a branch target, or nops in a method
|
||||
// with exception tables, would require updating the stack map,
|
||||
// branch instructions and exception tables.
|
||||
|
||||
if (Exceptions != null && Exceptions.Count != 0)
|
||||
return;
|
||||
var nops = new List<int>();
|
||||
|
||||
int n = Instructions.Count;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
byte op = Instructions[i].Opcode;
|
||||
if (op == 0x00 /* nop */)
|
||||
{
|
||||
// collect this nop only if it is not a branch target
|
||||
if (! StackMap.HasBranchFrame(Instructions[i].Label))
|
||||
{
|
||||
nops.Add(i);
|
||||
}
|
||||
}
|
||||
else if ((instOperandType[op] & 0x40) == 0x40) // jump inst
|
||||
{
|
||||
if (Instructions[i].Data is int)
|
||||
{
|
||||
// if jump instruction has an explicit byte offset,
|
||||
// we can't do nop elimination; see FillJumpTargets
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = nops.Count; i-- > 0; )
|
||||
Instructions.RemoveAt(nops[i]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,10 @@
|
||||
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\Solution.project" />
|
||||
<PropertyGroup>
|
||||
<!-- keep the following line below the import of Solution.project -->
|
||||
<DebugType>None</DebugType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Cmdline.fs" />
|
||||
<Compile Include="Program.fs" />
|
||||
@ -24,5 +28,11 @@
|
||||
<HintPath>..\packages\FSharp.Core.4.7.2\lib\net45\FSharp.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<PackageReference Include="ILMerge" Version="3.0.29" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<!-- use ILMerge to combine everything into PruneMerge.exe in the main output directory -->
|
||||
<Import Project="..\packages\ILMerge.3.0.29\build\ILMerge.props" />
|
||||
<Target Name="ILMerge" AfterTargets="AfterBuild" Condition="'$(Configuration)' == 'Release'">
|
||||
<Exec Command="$(ILMergeConsolePath) /ndebug /out:$(ObjDir)$(AssemblyName).exe $(OutputPath)$(AssemblyName).exe $(OutputPath)JavaBinary.dll $(OutputPath)FSharp.Core.dll" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
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.
|
||||
|
||||
https://www.spaceflint.com/bluebonnet
|
||||
|
||||
## Highlights
|
||||
|
||||
- 100% Java 8 bytecode with no native code.
|
||||
@ -58,8 +60,8 @@ There are some additional demos:
|
||||
- Note that the Android demos require the `ANDROID_HOME` environment directory, and the project is hard-coded to use Android platform version 28, and build-tools 30.0.2
|
||||
- Note also that the Android demos build an APK file, but do not install it.
|
||||
|
||||
See the [BNA](https://github.com/spaceflint7/bna) repository for another demo for Android.
|
||||
See the [BNA](https://github.com/spaceflint7/bna) and [Unjum](https://github.com/spaceflint7/unjum) repositories for more demos for Android.
|
||||
|
||||
## 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.
|
||||
|
@ -25,6 +25,7 @@ namespace Tests
|
||||
TestSet();
|
||||
TestStack();
|
||||
TestHash();
|
||||
TestArrayList();
|
||||
}
|
||||
|
||||
|
||||
@ -140,5 +141,21 @@ namespace Tests
|
||||
Console.WriteLine(sum);
|
||||
}
|
||||
|
||||
|
||||
private class ListElement
|
||||
{
|
||||
[java.attr.RetainType] public int v1;
|
||||
[java.attr.RetainType] public int v2;
|
||||
}
|
||||
|
||||
public void TestArrayList()
|
||||
{
|
||||
var list = new System.Collections.ArrayList(
|
||||
new ListElement[] {
|
||||
new ListElement { v1 = 1, v2 = 2 }
|
||||
});
|
||||
foreach (var e in list) System.Console.WriteLine(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
24
USAGE.md
24
USAGE.md
@ -46,6 +46,30 @@ Java functional interfaces are supported via an artificial delegate, for example
|
||||
|
||||
In this example, `java.lang.Thread.UncaughtExceptionHandler` is the functional interface, which gets an artificial delegate named `Delegate` as a nested type. The C# lambda is cast to this delegate, and then the `AsInterface` method is invoked, to convert the delegate to a Java interface.
|
||||
|
||||
# Attributes
|
||||
|
||||
Bluebonnet recognizes the following attributes:
|
||||
|
||||
- `[java.attr.DiscardAttribute]` on a top-level type (class/struct/interface/delegate) to exclude the type from output. For example, [Baselib/`Object.cs`](https://github.com/spaceflint7/bluebonnet/blob/master/Baselib/src/System/Object.cs) declares a `java.lang.Object` type with a `getClass` method, but there is no need to actually emit a Java class for `java.lang.Object`. For an example of this outside of Baselib, see [BNA/`Import.cs`](https://github.com/spaceflint7/bna/blob/master/BNA/src/Import.cs).
|
||||
|
||||
- `[java.attr.RetainTypeAttribute]` on a field data member indicates not to box the field. This is useful for fields that participate in a code hot path, as it eliminates double-references when accessing the field. It should not be used with fields which may be referenced directly from code outside their containing assembly.
|
||||
|
||||
- `[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).
|
||||
|
||||
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.
|
||||
|
||||
# Performance Considerations
|
||||
|
||||
For code that runs in a hot path, consider looking at the generated Java byte code to make sure there are no performance issues. Things to consider:
|
||||
|
||||
- Fields of a reference type are generally held in a double-reference, to permit taking their reference. If such a field is used in a hot path, consider applying the `RetainType` attribute (see above).
|
||||
|
||||
- Boxing a primitive or a reference type (as either a field or an array element) is a relatively expensive operation which allocates a wrapper object. For example, `a[i] += 2;` generates code that takes the address of an array element. To generate code which is less expensive when translated to Java, it could be re-written as `var v = a[i] + 2; a[i] = v;`
|
||||
|
||||
Note that running Bluebonnet with a single argument that points to a Java archive will print a disassembly of the classes and methods within the archive.
|
||||
|
||||
# Inexact Implementation
|
||||
|
||||
Here are some known differences, deficiencies and incompatibilities of the Bluebonnet .NET implementation, compared to a proper .NET implementation, in no particular order.
|
||||
|
Loading…
x
Reference in New Issue
Block a user