Some fixes
This commit is contained in:
parent
67ad9b0ccc
commit
30f187a636
@ -65,6 +65,7 @@ System.Nullable`*
|
|||||||
System.SystemException
|
System.SystemException
|
||||||
System.OverflowException
|
System.OverflowException
|
||||||
|
|
||||||
|
System.Collections.ArrayList
|
||||||
System.Collections.BitArray
|
System.Collections.BitArray
|
||||||
System.Collections.Comparer
|
System.Collections.Comparer
|
||||||
System.Collections.DictionaryEntry
|
System.Collections.DictionaryEntry
|
||||||
|
@ -130,18 +130,14 @@ namespace system
|
|||||||
destinationArray, destinationIndex,
|
destinationArray, destinationIndex,
|
||||||
length);
|
length);
|
||||||
|
|
||||||
var srcArr = sourceArray.arr;
|
var destinationArray_arr = destinationArray.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();
|
|
||||||
|
|
||||||
object copy = Clone(destinationArray.arr, destinationArray.len);
|
object copy = Clone(destinationArray_arr, destinationArray.len);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CopyInternal(srcArr, sourceIndex,
|
CopyInternal(sourceArray.arr, sourceIndex,
|
||||||
dstArr, destinationIndex,
|
destinationArray_arr, destinationIndex,
|
||||||
elemType, length);
|
length);
|
||||||
copy = null;
|
copy = null;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@ -149,8 +145,8 @@ namespace system
|
|||||||
if (copy != null)
|
if (copy != null)
|
||||||
{
|
{
|
||||||
CopyInternal(copy, sourceIndex,
|
CopyInternal(copy, sourceIndex,
|
||||||
dstArr, destinationIndex,
|
destinationArray_arr, destinationIndex,
|
||||||
elemType, length);
|
length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,22 +196,25 @@ namespace system
|
|||||||
destinationArray, destinationIndex,
|
destinationArray, destinationIndex,
|
||||||
length);
|
length);
|
||||||
|
|
||||||
var srcArr = sourceArray.arr;
|
CopyInternal(sourceArray.arr, sourceIndex,
|
||||||
var dstArr = destinationArray.arr;
|
destinationArray.arr, destinationIndex,
|
||||||
var elemType = ((java.lang.Object) srcArr).getClass().getComponentType();
|
length);
|
||||||
if (elemType != ((java.lang.Object) dstArr).getClass().getComponentType())
|
|
||||||
throw new System.ArrayTypeMismatchException();
|
|
||||||
|
|
||||||
CopyInternal(srcArr, sourceIndex,
|
|
||||||
dstArr, destinationIndex,
|
|
||||||
elemType, length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CopyInternal(object srcArr, int srcIndex,
|
private static void CopyInternal(object srcArr, int srcIndex,
|
||||||
object dstArr, int dstIndex,
|
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;
|
ValueType srcObj, dstObj;
|
||||||
if ( object.ReferenceEquals(srcArr, dstArr)
|
if ( object.ReferenceEquals(srcArr, dstArr)
|
||||||
|
@ -204,7 +204,7 @@ namespace SpaceFlint.CilToJava
|
|||||||
var length = stackMap.PopStack(CilMain.Where);
|
var length = stackMap.PopStack(CilMain.Where);
|
||||||
stackMap.PushStack(length);
|
stackMap.PushStack(length);
|
||||||
if (length.Equals(JavaType.LongType))
|
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);
|
code.NewInstruction(0xBC /* newarray */, null, elemType.NewArrayType);
|
||||||
@ -311,10 +311,18 @@ namespace SpaceFlint.CilToJava
|
|||||||
if (elemType.PrimitiveType == TypeCode.Byte)
|
if (elemType.PrimitiveType == TypeCode.Byte)
|
||||||
{
|
{
|
||||||
// unsigned byte result should be truncated to 8-bits
|
// unsigned byte result should be truncated to 8-bits
|
||||||
stackMap.PushStack(JavaType.IntegerType);
|
// (unless already followed by "ldc.i4 255 ; and")
|
||||||
code.NewInstruction(0x12 /* ldc */, null, (int) 0xFF);
|
bool followedByAndWith255 =
|
||||||
code.NewInstruction(0x7E /* iand */, null, null);
|
CodeBuilder.IsLoadConstant(inst.Next) == 0xFF
|
||||||
stackMap.PopStack(CilMain.Where);
|
&& 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)
|
if (arrayType.IsValueClass || elemType.IsValueClass)
|
||||||
@ -405,8 +413,29 @@ namespace SpaceFlint.CilToJava
|
|||||||
// stelem.i2 with a char[] array, should be 'castore' not 'sastore'
|
// stelem.i2 with a char[] array, should be 'castore' not 'sastore'
|
||||||
elemType = arrayType.AdjustRank(-arrayType.ArrayRank);
|
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)
|
if (arrayType.IsValueClass || elemType.IsValueClass)
|
||||||
{
|
{
|
||||||
@ -415,32 +444,6 @@ namespace SpaceFlint.CilToJava
|
|||||||
|
|
||||||
code.NewInstruction(elemType.StoreArrayOpcode, null, null);
|
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.Ldc_I8: case Code.Ldc_R4: case Code.Ldc_R8:
|
||||||
case Code.Ldstr: case Code.Ldnull:
|
case Code.Ldstr: case Code.Ldnull:
|
||||||
|
|
||||||
LoadConstant(cilOp, cilInst.Operand);
|
LoadConstant(cilOp, cilInst);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Code.Ldfld: case Code.Ldflda: case Code.Stfld:
|
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_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:
|
case Code.Conv_R4: case Code.Conv_R8: case Code.Conv_R_Un:
|
||||||
|
|
||||||
CodeNumber.Conversion(code, cilOp);
|
CodeNumber.Conversion(code, cilOp, cilInst);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Code.Add: case Code.Sub: case Code.Mul: case Code.Neg:
|
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.Sub_Ovf: case Code.Sub_Ovf_Un:
|
||||||
case Code.Mul_Ovf: case Code.Mul_Ovf_Un:
|
case Code.Mul_Ovf: case Code.Mul_Ovf_Un:
|
||||||
|
|
||||||
CodeNumber.Calculation(code, cilOp);
|
CodeNumber.Calculation(code, cilOp, cilInst);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Code.Ldind_I1: case Code.Ldind_U1: case Code.Ldind_I2: case Code.Ldind_U2:
|
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;
|
JavaType pushType;
|
||||||
object pushValue;
|
object pushValue;
|
||||||
@ -50,6 +50,7 @@ namespace SpaceFlint.CilToJava
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var data = inst.Operand;
|
||||||
pushOpcode = 0x12; // ldc
|
pushOpcode = 0x12; // ldc
|
||||||
|
|
||||||
if (data is string stringVal) // Code.Ldstr
|
if (data is string stringVal) // Code.Ldstr
|
||||||
@ -72,6 +73,13 @@ namespace SpaceFlint.CilToJava
|
|||||||
pushValue = longVal;
|
pushValue = longVal;
|
||||||
pushType = JavaType.LongType;
|
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
|
else
|
||||||
{
|
{
|
||||||
pushType = JavaType.IntegerType;
|
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)
|
void CastToClass(object data)
|
||||||
{
|
{
|
||||||
var srcType = (CilType) code.StackMap.PopStack(CilMain.Where);
|
var srcType = (CilType) code.StackMap.PopStack(CilMain.Where);
|
||||||
|
@ -10,7 +10,8 @@ namespace SpaceFlint.CilToJava
|
|||||||
public static class CodeNumber
|
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 oldType = (CilType) code.StackMap.PopStack(CilMain.Where);
|
||||||
var (newType, overflow, unsigned) = ConvertOpCodeToTypeCode(cilOp);
|
var (newType, overflow, unsigned) = ConvertOpCodeToTypeCode(cilOp);
|
||||||
@ -32,7 +33,10 @@ namespace SpaceFlint.CilToJava
|
|||||||
op = ConvertToLong(code, oldType.PrimitiveType, newType);
|
op = ConvertToLong(code, oldType.PrimitiveType, newType);
|
||||||
|
|
||||||
else
|
else
|
||||||
op = ConvertToInteger(code, oldType.PrimitiveType, newType);
|
{
|
||||||
|
var opNext = cilInst.Next?.OpCode.Code ?? 0;
|
||||||
|
op = ConvertToInteger(code, oldType.PrimitiveType, newType, opNext);
|
||||||
|
}
|
||||||
|
|
||||||
if (op != -1)
|
if (op != -1)
|
||||||
code.NewInstruction((byte) op, null, null);
|
code.NewInstruction((byte) op, null, null);
|
||||||
@ -82,7 +86,7 @@ namespace SpaceFlint.CilToJava
|
|||||||
return -1; // no output
|
return -1; // no output
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (! unsigned) // && (newType == TypeCode.Double)
|
else if (! unsigned)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
// convert to double
|
// 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)
|
if (oldType == TypeCode.Double)
|
||||||
{
|
{
|
||||||
@ -208,10 +212,23 @@ namespace SpaceFlint.CilToJava
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newType == TypeCode.SByte)
|
if (newType == TypeCode.SByte)
|
||||||
|
{
|
||||||
|
// Stelem_I1 inserts 'i2b' (see CodeArrays::Store)
|
||||||
|
if (opNext == Code.Stelem_I1)
|
||||||
|
return 0x00; // nop
|
||||||
|
|
||||||
return 0x91; // i2b
|
return 0x91; // i2b
|
||||||
|
}
|
||||||
|
|
||||||
if (newType == TypeCode.Byte)
|
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.StackMap.PushStack(JavaType.IntegerType);
|
||||||
code.NewInstruction(0x12 /* ldc */, null, (int) 0xFF);
|
code.NewInstruction(0x12 /* ldc */, null, (int) 0xFF);
|
||||||
code.StackMap.PushStack(JavaType.IntegerType);
|
code.StackMap.PushStack(JavaType.IntegerType);
|
||||||
@ -221,7 +238,16 @@ namespace SpaceFlint.CilToJava
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newType == TypeCode.Int16)
|
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
|
return 0x93; // i2s
|
||||||
|
}
|
||||||
|
|
||||||
if (newType == TypeCode.UInt16)
|
if (newType == TypeCode.UInt16)
|
||||||
return 0x92; // i2c
|
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);
|
var stackTop1 = code.StackMap.PopStack(CilMain.Where);
|
||||||
if (cilOp == Code.Sub && CodeSpan.SubOffset(stackTop1, code))
|
if (cilOp == Code.Sub && CodeSpan.SubOffset(stackTop1, code))
|
||||||
return;
|
return;
|
||||||
|
@ -12,6 +12,8 @@ namespace SpaceFlint.JavaBinary
|
|||||||
{
|
{
|
||||||
wtr.Where.Push("method body");
|
wtr.Where.Push("method body");
|
||||||
|
|
||||||
|
EliminateNops();
|
||||||
|
|
||||||
int codeLength = FillInstructions(wtr);
|
int codeLength = FillInstructions(wtr);
|
||||||
if (codeLength > 0xFFFE)
|
if (codeLength > 0xFFFE)
|
||||||
throw wtr.Where.Exception("output method is too large");
|
throw wtr.Where.Exception("output method is too large");
|
||||||
@ -434,6 +436,7 @@ namespace SpaceFlint.JavaBinary
|
|||||||
if (inst.Data is int intOffset)
|
if (inst.Data is int intOffset)
|
||||||
{
|
{
|
||||||
// int data is a jump offset that can be calculated immediately
|
// int data is a jump offset that can be calculated immediately
|
||||||
|
// note that this prevents nop elimination; see EliminateNops
|
||||||
intOffset -= offset;
|
intOffset -= offset;
|
||||||
inst.Bytes[1] = (byte) (offset >> 8);
|
inst.Bytes[1] = (byte) (offset >> 8);
|
||||||
inst.Bytes[2] = (byte) offset;
|
inst.Bytes[2] = (byte) offset;
|
||||||
@ -614,6 +617,47 @@ namespace SpaceFlint.JavaBinary
|
|||||||
return (index <= 3) ? 1 : ((index <= 255) ? 2 : 4);
|
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>
|
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="..\Solution.project" />
|
<Import Project="..\Solution.project" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- keep the following line below the import of Solution.project -->
|
||||||
|
<DebugType>None</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Cmdline.fs" />
|
<Compile Include="Cmdline.fs" />
|
||||||
<Compile Include="Program.fs" />
|
<Compile Include="Program.fs" />
|
||||||
@ -24,5 +28,11 @@
|
|||||||
<HintPath>..\packages\FSharp.Core.4.7.2\lib\net45\FSharp.Core.dll</HintPath>
|
<HintPath>..\packages\FSharp.Core.4.7.2\lib\net45\FSharp.Core.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System.IO.Compression" />
|
<Reference Include="System.IO.Compression" />
|
||||||
|
<PackageReference Include="ILMerge" Version="3.0.29" />
|
||||||
</ItemGroup>
|
</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.
|
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
|
## Highlights
|
||||||
|
|
||||||
- 100% Java 8 bytecode with no native code.
|
- 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 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.
|
- 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
|
## 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();
|
TestSet();
|
||||||
TestStack();
|
TestStack();
|
||||||
TestHash();
|
TestHash();
|
||||||
|
TestArrayList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -140,5 +141,21 @@ namespace Tests
|
|||||||
Console.WriteLine(sum);
|
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.
|
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
|
# 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.
|
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