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 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 sbyte Abs(sbyte a) => (sbyte) ((a < 0) ? -a : a);
public static sbyte Min(sbyte a, sbyte b) => (a <= b) ? a : b;
@ -159,6 +164,11 @@ namespace system
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 float Abs(float a) => (a < 0) ? -a : a;
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)
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 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) =>
(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]
public partial class RuntimeType : system.reflection.TypeInfo,
ISerializable, ICloneable
public sealed partial class RuntimeType : system.reflection.TypeInfo,
ISerializable, ICloneable
{
private sealed class GenericData
@ -353,7 +353,8 @@ namespace system
// 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)
=> GetType(cls, (Type[]) null);
// GetType() for a generic type with one type argument
public static Type GetType(java.lang.Class cls, Type arg)
=> GetType(cls, new Type[] { arg });
public static Type GetType(java.lang.Class cls, java.lang.Class 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)

View File

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

View File

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

View File

@ -999,7 +999,8 @@ namespace SpaceFlint.CilToJava
//
// in either case, we want to discard the 'call' instructions
// (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.
//
// (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);
}
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.StackMap.PushStack(JavaType.IntegerType);
// call system.RuntimeType.Argument(int typeArgumentIndex)
code.NewInstruction(0xB6 /* invokevirtual */, CilType.SystemRuntimeTypeType,
new JavaMethodRef("Argument", CilType.SystemTypeType, JavaType.IntegerType));
// call system.RuntimeType.Argument(RuntimeType runtimeType, int typeArgumentIndex)
code.NewInstruction(0xB8 /* invokestatic */, CilType.SystemRuntimeTypeType,
new JavaMethodRef("Argument", CilType.SystemTypeType,
CilType.SystemRuntimeTypeType, JavaType.IntegerType));
code.StackMap.PopStack(CilMain.Where); // integer
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;
if (count == 1)
{
// specific handling for the common case of a generic instance
// with just one type argument. if this is a concrete argument,
// call GetType(class, class). if this is a generic argument,
// call GetType(class, type). note that the first class argument
// to GetType was alredy inserted by our caller, LoadMaybeGeneric
// handling for the case of a generic instance with one or two
// type arguments. each argument, if it is a concrete argument,
// call GetType(class, class). if it is a generic argument,
// 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 (argType, argIndex) = CilMain.GenericStack.Resolve(
loadType.GenericParameters[0].JavaName);
loadType.GenericParameters[i].JavaName);
if (argIndex == 0 && (! argType.HasGenericParameters))
{
// GetType(java.lang.Class, java.lang.Class)
code.NewInstruction(0x12 /* ldc */, argType.AsWritableClass, null);
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]);
}
else
{
// GetType(java.lang.Class, system.Type)
LoadGeneric(argType, argIndex, code);
// second parameter of type system.Type
// second or third parameter of type system.Type
parameters.Add(new JavaFieldRef("", CilType.SystemTypeType));
}
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
{
// 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
// 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);
parameters.Add(new JavaFieldRef("", arrayOfType));
code.NewInstruction(0x12 /* ldc */, null, count);
code.NewInstruction(0x12 /* ldc */, null, genericCount);
code.StackMap.PushStack(JavaType.IntegerType);
code.NewInstruction(0xBD /* anewarray */, CilType.SystemTypeType, null);
code.StackMap.PopStack(CilMain.Where);
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.StackMap.PushStack(arrayOfType);
@ -631,7 +660,17 @@ namespace SpaceFlint.CilToJava
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,

View File

@ -10,7 +10,7 @@
<!-- Android SDK path -->
<AndroidHome Condition="'$(ANDROID_HOME)' != ''">$(ANDROID_HOME)</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>
</PropertyGroup>
<Import Project="..\..\Solution.project" />

View File

@ -11,7 +11,7 @@
<!-- Android SDK path -->
<AndroidHome Condition="'$(ANDROID_HOME)' != ''">$(ANDROID_HOME)</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>
</PropertyGroup>
<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()
{
instOperandType = new byte[256];

View File

@ -12,9 +12,6 @@ namespace SpaceFlint.JavaBinary
{
wtr.Where.Push("method body");
PerformOptimizations();
EliminateNops();
int codeLength = FillInstructions(wtr);
if (codeLength > 0xFFFE)
throw wtr.Where.Exception("output method is too large");
@ -486,7 +483,6 @@ 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;
@ -667,76 +663,6 @@ namespace SpaceFlint.JavaBinary
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.
- Restore packages using [nuget](https://www.nuget.org/downloads): `nuget restore`
- 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.
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.
- `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.