From 9c443eefe32087985a43595da0131cd2a539a262 Mon Sep 17 00:00:00 2001 From: spaceflint <> Date: Wed, 29 Sep 2021 17:15:04 +0300 Subject: [PATCH] Version 0.15 --- Baselib/src/System/Math.cs | 12 +++- Baselib/src/System/Random.cs | 95 ++++++++++++++++++++++++++++++ Baselib/src/System/RuntimeType.cs | 43 +++++++++++++- CilToJava/src/CilType.cs | 2 +- CilToJava/src/CodeBuilder.cs | 2 + CilToJava/src/CodeCall.cs | 3 +- CilToJava/src/CodeMisc.cs | 80 +++++++++++++++++++++++++ CilToJava/src/GenericUtil.cs | 77 ++++++++++++++++++------ Demos/Android_CS/Android_CS.csproj | 2 +- Demos/Android_FS/Android_FS.fsproj | 2 +- JavaBinary/src/JavaCode.cs | 5 ++ JavaBinary/src/JavaCodeWriter.cs | 74 ----------------------- README.md | 2 +- USAGE.md | 2 +- 14 files changed, 298 insertions(+), 103 deletions(-) create mode 100644 Baselib/src/System/Random.cs diff --git a/Baselib/src/System/Math.cs b/Baselib/src/System/Math.cs index c000447..e6c8e33 100644 --- a/Baselib/src/System/Math.cs +++ b/Baselib/src/System/Math.cs @@ -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; diff --git a/Baselib/src/System/Random.cs b/Baselib/src/System/Random.cs new file mode 100644 index 0000000..bddc3c9 --- /dev/null +++ b/Baselib/src/System/Random.cs @@ -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) uses Sample() + public virtual void NextBytes(Span 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(); + } +} diff --git a/Baselib/src/System/RuntimeType.cs b/Baselib/src/System/RuntimeType.cs index 2a1fc6a..bbcc7b7 100644 --- a/Baselib/src/System/RuntimeType.cs +++ b/Baselib/src/System/RuntimeType.cs @@ -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) diff --git a/CilToJava/src/CilType.cs b/CilToJava/src/CilType.cs index 5067de3..f15ccf7 100644 --- a/CilToJava/src/CilType.cs +++ b/CilToJava/src/CilType.cs @@ -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}'"); } diff --git a/CilToJava/src/CodeBuilder.cs b/CilToJava/src/CodeBuilder.cs index ab973b2..b2edc28 100644 --- a/CilToJava/src/CodeBuilder.cs +++ b/CilToJava/src/CodeBuilder.cs @@ -99,6 +99,8 @@ namespace SpaceFlint.CilToJava (code.MaxLocals, code.MaxStack) = locals.GetMaxLocalsAndStack(); locals.TrackUnconditionalBranch(null); + + OptimizeGeneratedCode(); } diff --git a/CilToJava/src/CodeCall.cs b/CilToJava/src/CodeCall.cs index a4651b6..5029123 100644 --- a/CilToJava/src/CodeCall.cs +++ b/CilToJava/src/CodeCall.cs @@ -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 diff --git a/CilToJava/src/CodeMisc.cs b/CilToJava/src/CodeMisc.cs index 8fc4134..d7984aa 100644 --- a/CilToJava/src/CodeMisc.cs +++ b/CilToJava/src/CodeMisc.cs @@ -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(); + + 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]); + } + } + } } diff --git a/CilToJava/src/GenericUtil.cs b/CilToJava/src/GenericUtil.cs index b7c09c0..8acde75 100644 --- a/CilToJava/src/GenericUtil.cs +++ b/CilToJava/src/GenericUtil.cs @@ -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 parameters, JavaCode code) + static void LoadGenericInstance_1_2(CilType loadType, int genericCount, + List 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 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, diff --git a/Demos/Android_CS/Android_CS.csproj b/Demos/Android_CS/Android_CS.csproj index e3a9420..d022eaa 100644 --- a/Demos/Android_CS/Android_CS.csproj +++ b/Demos/Android_CS/Android_CS.csproj @@ -10,7 +10,7 @@ $(ANDROID_HOME) $(ANDROID_SDK_ROOT) - $(AndroidHome)/platforms/android-28/android.jar + $(AndroidHome)/platforms/android-30/android.jar $(AndroidHome)/build-tools/30.0.2/ diff --git a/Demos/Android_FS/Android_FS.fsproj b/Demos/Android_FS/Android_FS.fsproj index fd5ab29..d4f3f8e 100644 --- a/Demos/Android_FS/Android_FS.fsproj +++ b/Demos/Android_FS/Android_FS.fsproj @@ -11,7 +11,7 @@ $(ANDROID_HOME) $(ANDROID_SDK_ROOT) - $(AndroidHome)/platforms/android-28/android.jar + $(AndroidHome)/platforms/android-30/android.jar $(AndroidHome)/build-tools/30.0.2/ diff --git a/JavaBinary/src/JavaCode.cs b/JavaBinary/src/JavaCode.cs index 9df402d..4d4e302 100644 --- a/JavaBinary/src/JavaCode.cs +++ b/JavaBinary/src/JavaCode.cs @@ -83,6 +83,11 @@ namespace SpaceFlint.JavaBinary + public static bool IsBranchOpcode (byte op) + => (instOperandType[op] & 0x40) == 0x40; + + + static JavaCode() { instOperandType = new byte[256]; diff --git a/JavaBinary/src/JavaCodeWriter.cs b/JavaBinary/src/JavaCodeWriter.cs index fbff37f..397e9d8 100644 --- a/JavaBinary/src/JavaCodeWriter.cs +++ b/JavaBinary/src/JavaCodeWriter.cs @@ -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 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]); - } - } } diff --git a/README.md b/README.md index 9ff43ab..62f6841 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/USAGE.md b/USAGE.md index 4879947..601dd46 100644 --- a/USAGE.md +++ b/USAGE.md @@ -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.