Version 0.15

This commit is contained in:
spaceflint 2021-09-29 17:15:04 +03:00
parent 8d9552a154
commit 9c443eefe3
14 changed files with 298 additions and 103 deletions

View File

@ -4,6 +4,11 @@ namespace system
public static class Math public static class Math
{ {
public const double E = 2.7182818284590451;
public const double PI = 3.1415926535897931;
public const double Tau = 6.2831853071795862;
public static int Sign(sbyte a) => java.lang.Integer.signum(a); public static int Sign(sbyte a) => java.lang.Integer.signum(a);
public static sbyte Abs(sbyte a) => (sbyte) ((a < 0) ? -a : a); public static sbyte Abs(sbyte a) => (sbyte) ((a < 0) ? -a : a);
public static sbyte Min(sbyte a, sbyte b) => (a <= b) ? a : b; public static sbyte Min(sbyte a, sbyte b) => (a <= b) ? a : b;
@ -159,6 +164,11 @@ namespace system
public static class MathF public static class MathF
{ {
public const float E = 2.71828175f;
public const float PI = 3.14159274f;
public const float Tau = 6.28318548f;
public static int Sign(float a) => (int) java.lang.Math.signum(a); public static int Sign(float a) => (int) java.lang.Math.signum(a);
public static float Abs(float a) => (a < 0) ? -a : a; public static float Abs(float a) => (a < 0) ? -a : a;
public static float Min(float a, float b) => (a <= b) ? a : b; public static float Min(float a, float b) => (a <= b) ? a : b;
@ -178,7 +188,7 @@ namespace system
public static float Acosh(float a) => (float) public static float Acosh(float a) => (float)
java.lang.Math.log(a + java.lang.Math.sqrt((double) a * a - 1.0)); java.lang.Math.log(a + java.lang.Math.sqrt((double) a * a - 1.0));
public static float Atan(float a) => (float) java.lang.Math.atan(a); public static float Atan(float a) => (float) java.lang.Math.atan(a);
public static float Atan2(float x, double y) => (float) java.lang.Math.atan2(x, y); public static float Atan2(float x, float y) => (float) java.lang.Math.atan2(x, y);
public static float Atanh(float a) => public static float Atanh(float a) =>
(float) java.lang.Math.log((1 + a) / (1 - a)) * 0.5f; (float) java.lang.Math.log((1 + a) / (1 - a)) * 0.5f;

View File

@ -0,0 +1,95 @@
namespace system
{
public class Random
{
[java.attr.RetainType] private java.util.Random _random;
// if this is an instance of Random itself, rather than a
// derived class, then don't bother with calls to Sample()
[java.attr.RetainType] private bool isSystemRandom;
public Random()
{
isSystemRandom = (GetType() == typeof(System.Random));
_random = new java.util.Random();
}
public Random(int Seed)
{
isSystemRandom = (GetType() == typeof(System.Random));
_random = new java.util.Random(Seed);
}
// NextBytes(byte[]) does NOT use Sample()
public virtual void NextBytes(byte[] buffer)
{
ThrowHelper.ThrowIfNull(buffer);
int n = buffer.Length;
for (int i = 0; i < n; i++)
buffer[i] = (byte) (_random.nextInt() % 256);
}
// Next() does NOT use Sample()
public virtual int Next() => _random.nextInt();
// NextBytes(Span<byte>) uses Sample()
public virtual void NextBytes(Span<byte> buffer)
{
throw new System.PlatformNotSupportedException();
/*int n = buffer.Length;
for (int i = 0; i < n; i++)
buffer[i] = (byte) (Sample() * 256);*/
}
// Next(int) uses Sample()
public virtual int Next(int maxValue) => Next(0, maxValue);
// Next(int,int) uses Sample()
public virtual int Next(int minValue, int maxValue)
{
if (maxValue < minValue)
ThrowHelper.ThrowArgumentOutOfRangeException();
var range = (long) maxValue - minValue;
if (range <= int.MaxValue && isSystemRandom)
return _random.nextInt((int) range) + minValue;
return minValue + (int) (Sample() * range);
}
// NextInt64 uses Sample()
public virtual long NextInt64() => NextInt64(0, long.MaxValue);
// NextInt64(long) uses Sample()
public virtual long NextInt64(long maxValue) => NextInt64(0, maxValue);
// NextInt64(long, long) uses Sample()
public virtual long NextInt64(long minValue, long maxValue)
{
if (maxValue < minValue)
ThrowHelper.ThrowArgumentOutOfRangeException();
return isSystemRandom ? (_random.nextLong() % maxValue)
: (long) (Sample() * long.MaxValue);
}
// NextSingle uses Sample()
public virtual float NextSingle() => (float) Sample();
// NextDouble uses Sample()
public virtual double NextDouble() => Sample();
protected virtual double Sample() => _random.nextDouble();
}
}

View File

@ -14,8 +14,8 @@ namespace system
// //
[Serializable] [Serializable]
public partial class RuntimeType : system.reflection.TypeInfo, public sealed partial class RuntimeType : system.reflection.TypeInfo,
ISerializable, ICloneable ISerializable, ICloneable
{ {
private sealed class GenericData private sealed class GenericData
@ -353,7 +353,8 @@ namespace system
// invoked by code generated by GenericUtil::LoadGeneric // invoked by code generated by GenericUtil::LoadGeneric
public Type Argument(int index) => Generic.ArgumentTypes[index]; public static Type Argument(RuntimeType runtimeType, int index)
=> runtimeType.Generic.ArgumentTypes[index];
@ -985,12 +986,48 @@ namespace system
public static Type GetType(java.lang.Class cls) public static Type GetType(java.lang.Class cls)
=> GetType(cls, (Type[]) null); => GetType(cls, (Type[]) null);
// GetType() for a generic type with one type argument
public static Type GetType(java.lang.Class cls, Type arg) public static Type GetType(java.lang.Class cls, Type arg)
=> GetType(cls, new Type[] { arg }); => GetType(cls, new Type[] { arg });
public static Type GetType(java.lang.Class cls, java.lang.Class arg) public static Type GetType(java.lang.Class cls, java.lang.Class arg)
=> GetType(cls, new Type[] { GetType(arg) }); => GetType(cls, new Type[] { GetType(arg) });
// GetType() for a generic type with two type arguments
public static Type GetType(java.lang.Class cls, Type arg0, Type arg1)
=> GetType(cls, new Type[] { arg0, arg1 });
public static Type GetType(java.lang.Class cls, java.lang.Class arg0, Type arg1)
=> GetType(cls, new Type[] { GetType(arg0), arg1 });
public static Type GetType(java.lang.Class cls, Type arg0, java.lang.Class arg1)
=> GetType(cls, new Type[] { arg0, GetType(arg1) });
public static Type GetType(java.lang.Class cls, java.lang.Class arg0, java.lang.Class arg1)
=> GetType(cls, new Type[] { GetType(arg0), GetType(arg1) });
// GetType() for a generic type with three to eight type arguments
public static Type GetType(java.lang.Class cls, Type arg0, Type arg1, Type arg2)
=> GetType(cls, new Type[] { arg0, arg1, arg2 });
public static Type GetType(java.lang.Class cls, Type arg0, Type arg1, Type arg2, Type arg3)
=> GetType(cls, new Type[] { arg0, arg1, arg2, arg3 });
public static Type GetType(java.lang.Class cls, Type arg0, Type arg1, Type arg2, Type arg3, Type arg4)
=> GetType(cls, new Type[] { arg0, arg1, arg2, arg3, arg4 });
public static Type GetType(java.lang.Class cls, Type arg0, Type arg1, Type arg2, Type arg3, Type arg4, Type arg5)
=> GetType(cls, new Type[] { arg0, arg1, arg2, arg3, arg4, arg5 });
public static Type GetType(java.lang.Class cls, Type arg0, Type arg1, Type arg2, Type arg3, Type arg4, Type arg5, Type arg6)
=> GetType(cls, new Type[] { arg0, arg1, arg2, arg3, arg4, arg5, arg6 });
public static Type GetType(java.lang.Class cls, Type arg0, Type arg1, Type arg2, Type arg3, Type arg4, Type arg5, Type arg6, Type arg7)
=> GetType(cls, new Type[] { arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 });
public override Type MakeGenericType(params Type[] typeArguments) public override Type MakeGenericType(params Type[] typeArguments)

View File

@ -135,7 +135,7 @@ namespace SpaceFlint.CilToJava
var modifier = (fromType as IModifierType).ModifierType.FullName; var modifier = (fromType as IModifierType).ModifierType.FullName;
if (modifier == "System.Runtime.CompilerServices.IsVolatile") if (modifier == "System.Runtime.CompilerServices.IsVolatile")
Flags |= VOLATILE; Flags |= VOLATILE;
else else if (modifier != "System.Runtime.CompilerServices.IsExternalInit")
throw CilMain.Where.Exception($"unknown modifier '{modifier}'"); throw CilMain.Where.Exception($"unknown modifier '{modifier}'");
} }

View File

@ -99,6 +99,8 @@ namespace SpaceFlint.CilToJava
(code.MaxLocals, code.MaxStack) = locals.GetMaxLocalsAndStack(); (code.MaxLocals, code.MaxStack) = locals.GetMaxLocalsAndStack();
locals.TrackUnconditionalBranch(null); locals.TrackUnconditionalBranch(null);
OptimizeGeneratedCode();
} }

View File

@ -999,7 +999,8 @@ namespace SpaceFlint.CilToJava
// //
// in either case, we want to discard the 'call' instructions // in either case, we want to discard the 'call' instructions
// (see also ConvertCallToNop() below). for case (1), we translate // (see also ConvertCallToNop() below). for case (1), we translate
// into a call to system.Util.GetType() to obtain a System.Type. // into a call to system.RuntimeType.GetType() to obtain a System.Type,
// this is done via a call to LoadMaybeGeneric.
// for case (2), we convert to a simple 'ldc' instruction. // for case (2), we convert to a simple 'ldc' instruction.
// //
// (3) this instruction is used to load a reference to an array // (3) this instruction is used to load a reference to an array

View File

@ -764,6 +764,86 @@ namespace SpaceFlint.CilToJava
theClass.Methods.Add(outerMethod); theClass.Methods.Add(outerMethod);
} }
private void OptimizeGeneratedCode ()
{
var Instructions = code.Instructions;
var StackMap = code.StackMap;
int n = Instructions.Count;
//
// nop optimization:
//
// 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.
//
var nops = new List<int>();
bool discardNops =
(code.Exceptions == null || code.Exceptions.Count == 0);
// constants optimization:
//
// find occurrences of iconst_0 or iconst_1 followed by i2l
// (which must not be a branch target), and convert to lconst_xx
//
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 (JavaCode.IsBranchOpcode(op)) // jump inst
{
if (Instructions[i].Data is int)
{
// if jump instruction has an explicit byte offset,
// we can't do nop elimination; see FillJumpTargets
// in JavaBinary.JavaCodeWriter
discardNops = false;
}
}
else if (op == 0x85 /* i2l */ && i > 0)
{
var prevInst = Instructions[i - 1];
if ( prevInst.Opcode == 0x12 /* ldc */
&& prevInst.Data is int intValue
&& (intValue == 0 || intValue == 1)
&& (! StackMap.HasBranchFrame(
Instructions[i].Label)))
{
// convert ldc of 0 or 1 into lconst_0 or lconst_1
prevInst.Opcode = (byte) (intValue + 0x09);
// convert current instruction to nop,
// and mark to discard it if discarding nops
Instructions[i].Opcode = 0x00; // nop
nops.Add(i);
}
}
}
if (discardNops)
{
for (int i = nops.Count; i-- > 0; )
Instructions.RemoveAt(nops[i]);
}
}
} }
} }

View File

@ -508,9 +508,10 @@ namespace SpaceFlint.CilToJava
code.NewInstruction(0x12 /* ldc */, null, -loadIndex - 1); code.NewInstruction(0x12 /* ldc */, null, -loadIndex - 1);
code.StackMap.PushStack(JavaType.IntegerType); code.StackMap.PushStack(JavaType.IntegerType);
// call system.RuntimeType.Argument(int typeArgumentIndex) // call system.RuntimeType.Argument(RuntimeType runtimeType, int typeArgumentIndex)
code.NewInstruction(0xB6 /* invokevirtual */, CilType.SystemRuntimeTypeType, code.NewInstruction(0xB8 /* invokestatic */, CilType.SystemRuntimeTypeType,
new JavaMethodRef("Argument", CilType.SystemTypeType, JavaType.IntegerType)); new JavaMethodRef("Argument", CilType.SystemTypeType,
CilType.SystemRuntimeTypeType, JavaType.IntegerType));
code.StackMap.PopStack(CilMain.Where); // integer code.StackMap.PopStack(CilMain.Where); // integer
code.StackMap.PopStack(CilMain.Where); // generic type field code.StackMap.PopStack(CilMain.Where); // generic type field
@ -534,58 +535,86 @@ namespace SpaceFlint.CilToJava
static void LoadGenericInstance(CilType loadType, List<JavaFieldRef> parameters, JavaCode code) static void LoadGenericInstance_1_2(CilType loadType, int genericCount,
List<JavaFieldRef> parameters,
JavaCode code)
{ {
int count = loadType.GenericParameters.Count; // handling for the case of a generic instance with one or two
if (count == 1) // type arguments. each argument, if it is a concrete argument,
{ // call GetType(class, class). if it is a generic argument,
// specific handling for the common case of a generic instance // call GetType(class, type).
// with just one type argument. if this is a concrete argument, // note that the first class argument to GetType was alredy
// call GetType(class, class). if this is a generic argument, // inserted by our caller, LoadMaybeGeneric().
// call GetType(class, type). note that the first class argument
// to GetType was alredy inserted by our caller, LoadMaybeGeneric
for (int i = 0; i < genericCount; i++)
{
var genericMark = CilMain.GenericStack.Mark(); var genericMark = CilMain.GenericStack.Mark();
var (argType, argIndex) = CilMain.GenericStack.Resolve( var (argType, argIndex) = CilMain.GenericStack.Resolve(
loadType.GenericParameters[0].JavaName); loadType.GenericParameters[i].JavaName);
if (argIndex == 0 && (! argType.HasGenericParameters)) if (argIndex == 0 && (! argType.HasGenericParameters))
{ {
// GetType(java.lang.Class, java.lang.Class) // GetType(java.lang.Class, java.lang.Class)
code.NewInstruction(0x12 /* ldc */, argType.AsWritableClass, null); code.NewInstruction(0x12 /* ldc */, argType.AsWritableClass, null);
code.StackMap.PushStack(CilType.ClassType); code.StackMap.PushStack(CilType.ClassType);
// second parameter of type java.lang.Class // second or third parameter of type java.lang.Class
parameters.Add(parameters[0]); parameters.Add(parameters[0]);
} }
else else
{ {
// GetType(java.lang.Class, system.Type) // GetType(java.lang.Class, system.Type)
LoadGeneric(argType, argIndex, code); LoadGeneric(argType, argIndex, code);
// second parameter of type system.Type // second or third parameter of type system.Type
parameters.Add(new JavaFieldRef("", CilType.SystemTypeType)); parameters.Add(new JavaFieldRef("", CilType.SystemTypeType));
} }
CilMain.GenericStack.Release(genericMark); CilMain.GenericStack.Release(genericMark);
} }
}
static void LoadGenericInstance_3_N(CilType loadType, int genericCount,
List<JavaFieldRef> parameters,
JavaCode code)
{
if (genericCount <= 8)
{
// handling for the less common case of a generic instace with
// less than eight arguments. we don't check if the provided
// type arguments are concrete or generic, and call a GetType()
// override for the appropriate number of parameters.
// note that the first class argument to GetType was alredy
// inserted by our caller, LoadMaybeGeneric().
for (int i = 0; i < genericCount; i++)
{
LoadMaybeGeneric(loadType.GenericParameters[i], code);
// next parameter always has type system.Type
parameters.Add(new JavaFieldRef("", CilType.SystemTypeType));
}
}
else else
{ {
// generic handling for the less common case of a generic instace // generic handling for the less common case of a generic instace
// with more than one argument. we don't check if the provided // with more than eight arguments. we don't check if the provided
// type arguments are concrete or generic. we build an array of // type arguments are concrete or generic. we build an array of
// system.Type references, and call GetType(class, system.Type[]). // system.Type references, and call GetType(class, system.Type[]).
// note that the first class argument to GetType was alredy
// inserted by our caller, LoadMaybeGeneric().
var arrayOfType = CilType.SystemTypeType.AdjustRank(1); var arrayOfType = CilType.SystemTypeType.AdjustRank(1);
parameters.Add(new JavaFieldRef("", arrayOfType)); parameters.Add(new JavaFieldRef("", arrayOfType));
code.NewInstruction(0x12 /* ldc */, null, count); code.NewInstruction(0x12 /* ldc */, null, genericCount);
code.StackMap.PushStack(JavaType.IntegerType); code.StackMap.PushStack(JavaType.IntegerType);
code.NewInstruction(0xBD /* anewarray */, CilType.SystemTypeType, null); code.NewInstruction(0xBD /* anewarray */, CilType.SystemTypeType, null);
code.StackMap.PopStack(CilMain.Where); code.StackMap.PopStack(CilMain.Where);
code.StackMap.PushStack(arrayOfType); code.StackMap.PushStack(arrayOfType);
for (int i = 0; i < count; i++) for (int i = 0; i < genericCount; i++)
{ {
code.NewInstruction(0x59 /* dup */, null, null); code.NewInstruction(0x59 /* dup */, null, null);
code.StackMap.PushStack(arrayOfType); code.StackMap.PushStack(arrayOfType);
@ -631,7 +660,17 @@ namespace SpaceFlint.CilToJava
if (loadType.HasGenericParameters) if (loadType.HasGenericParameters)
{ {
LoadGenericInstance(loadType, parameters, code); int genericCount = loadType.GenericParameters.Count;
if (genericCount <= 2)
{
LoadGenericInstance_1_2(loadType, genericCount,
parameters, code);
}
else
{
LoadGenericInstance_3_N(loadType, genericCount,
parameters, code);
}
} }
code.NewInstruction(0xB8 /* invokestatic */, CilType.SystemRuntimeTypeType, code.NewInstruction(0xB8 /* invokestatic */, CilType.SystemRuntimeTypeType,

View File

@ -10,7 +10,7 @@
<!-- Android SDK path --> <!-- Android SDK path -->
<AndroidHome Condition="'$(ANDROID_HOME)' != ''">$(ANDROID_HOME)</AndroidHome> <AndroidHome Condition="'$(ANDROID_HOME)' != ''">$(ANDROID_HOME)</AndroidHome>
<AndroidHome Condition="'$(ANDROID_SDK_ROOT)' != ''">$(ANDROID_SDK_ROOT)</AndroidHome> <AndroidHome Condition="'$(ANDROID_SDK_ROOT)' != ''">$(ANDROID_SDK_ROOT)</AndroidHome>
<AndroidJar>$(AndroidHome)/platforms/android-28/android.jar</AndroidJar> <AndroidJar>$(AndroidHome)/platforms/android-30/android.jar</AndroidJar>
<AndroidBin>$(AndroidHome)/build-tools/30.0.2/</AndroidBin> <AndroidBin>$(AndroidHome)/build-tools/30.0.2/</AndroidBin>
</PropertyGroup> </PropertyGroup>
<Import Project="..\..\Solution.project" /> <Import Project="..\..\Solution.project" />

View File

@ -11,7 +11,7 @@
<!-- Android SDK path --> <!-- Android SDK path -->
<AndroidHome Condition="'$(ANDROID_HOME)' != ''">$(ANDROID_HOME)</AndroidHome> <AndroidHome Condition="'$(ANDROID_HOME)' != ''">$(ANDROID_HOME)</AndroidHome>
<AndroidHome Condition="'$(ANDROID_SDK_ROOT)' != ''">$(ANDROID_SDK_ROOT)</AndroidHome> <AndroidHome Condition="'$(ANDROID_SDK_ROOT)' != ''">$(ANDROID_SDK_ROOT)</AndroidHome>
<AndroidJar>$(AndroidHome)/platforms/android-28/android.jar</AndroidJar> <AndroidJar>$(AndroidHome)/platforms/android-30/android.jar</AndroidJar>
<AndroidBin>$(AndroidHome)/build-tools/30.0.2/</AndroidBin> <AndroidBin>$(AndroidHome)/build-tools/30.0.2/</AndroidBin>
</PropertyGroup> </PropertyGroup>
<Import Project="..\..\Solution.project" /> <Import Project="..\..\Solution.project" />

View File

@ -83,6 +83,11 @@ namespace SpaceFlint.JavaBinary
public static bool IsBranchOpcode (byte op)
=> (instOperandType[op] & 0x40) == 0x40;
static JavaCode() static JavaCode()
{ {
instOperandType = new byte[256]; instOperandType = new byte[256];

View File

@ -12,9 +12,6 @@ namespace SpaceFlint.JavaBinary
{ {
wtr.Where.Push("method body"); wtr.Where.Push("method body");
PerformOptimizations();
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");
@ -486,7 +483,6 @@ 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;
@ -667,76 +663,6 @@ namespace SpaceFlint.JavaBinary
return (index <= 3) ? 1 : ((index <= 255) ? 2 : 4); return (index <= 3) ? 1 : ((index <= 255) ? 2 : 4);
} }
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,
// 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]);
}
} }
} }

View File

@ -59,7 +59,7 @@ There are some additional demos:
- Change to the `Demos` directory inside the solution directory. - Change to the `Demos` directory inside the solution directory.
- Restore packages using [nuget](https://www.nuget.org/downloads): `nuget restore` - Restore packages using [nuget](https://www.nuget.org/downloads): `nuget restore`
- Build and run each demo: `msbuild -p:Configuration=Release -t:RunDemo` - Build and run each demo: `msbuild -p:Configuration=Release -t:RunDemo`
- 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 30, 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) and [Unjum](https://github.com/spaceflint7/unjum) repositories for more demos for Android. See the [BNA](https://github.com/spaceflint7/bna) and [Unjum](https://github.com/spaceflint7/unjum) repositories for more demos for Android.

View File

@ -109,7 +109,7 @@ Here are some known differences, deficiencies and incompatibilities of the Blueb
- Reflection information for generic types depends on the existence of the `Signature` attribute in java class files. - Reflection information for generic types depends on the existence of the `Signature` attribute in java class files.
- `BeforeFieldInit` is not honored; the static initializer for a class will be called at the discretion of the JVM. if it is a generic class, the static initializer is called when the generic type is first referenced. - `BeforeFieldInit` is not honored; the static initializer for a class will be called at the discretion of the JVM. If it is a generic class, the static initializer is called when the generic type is first referenced.
- The type system is weaker than .NET when it comes to generic types, and in some casts and assignments are permitted between generic objects that differ only in their type arguments. - The type system is weaker than .NET when it comes to generic types, and in some casts and assignments are permitted between generic objects that differ only in their type arguments.