diff --git a/.github/workflows/IKVM.yml b/.github/workflows/IKVM.yml index d7e07166a1..d1f1d0169e 100644 --- a/.github/workflows/IKVM.yml +++ b/.github/workflows/IKVM.yml @@ -282,6 +282,7 @@ jobs: run: - IKVM.ByteCode.Tests - IKVM.Tests + - IKVM.Java.Tests - IKVM.Tools.Importer.Tests - IKVM.Tools.Exporter.Tests - IKVM.Tools.Tests @@ -304,6 +305,8 @@ jobs: exclude: - tfm: net461 sys: linux + - run: IKVM.Tests.Java + tfm: net6.0 - run: IKVM.Tools.Exporter.Tests tfm: net6.0 - run: IKVM.Tools.Importer.Tests @@ -362,34 +365,26 @@ jobs: if: runner.os == 'Windows' shell: pwsh run: | - $dir="C:\work" + $dir="C:\w" mkdir $dir mkdir $dir\temp mkdir $dir\dotnet - mkdir $dir\nuget - mkdir $dir\nuget\packages mkdir $dir\ikvm - mkdir $dir\ikvm\dist Add-Content $env:GITHUB_ENV "`nIKVMPATH=$dir\ikvm" Add-Content $env:GITHUB_ENV "`nTMP=$dir\temp`nTEMP=$dir\temp`nTMPDIR=$dir\temp" Add-Content $env:GITHUB_ENV "`nDOTNET_INSTALL_DIR=$dir\dotnet" - Add-Content $env:GITHUB_ENV "`nNUGET_PACKAGES=$dir\nuget\packages" - name: Set Paths (Linux) if: runner.os == 'Linux' shell: pwsh run: | - $dir="${{ runner.temp }}/work" + $dir="${{ runner.temp }}/w" mkdir $dir mkdir $dir/temp mkdir $dir/dotnet - mkdir $dir/nuget - mkdir $dir/nuget/packages mkdir $dir/ikvm - mkdir $dir/ikvm/dist Add-Content $env:GITHUB_ENV "`nIKVMPATH=$dir/ikvm" Add-Content $env:GITHUB_ENV "`nTMP=$dir/temp`nTEMP=$dir/temp`nTMPDIR=$dir/temp" Add-Content $env:GITHUB_ENV "`nDOTNET_INSTALL_DIR=$dir/dotnet" - Add-Content $env:GITHUB_ENV "`nNUGET_PACKAGES=$dir/nuget/packages" - name: Setup .NET 3.1 uses: actions/setup-dotnet@v3 with: @@ -409,14 +404,14 @@ jobs: uses: actions/download-artifact@v3 with: name: tests - path: ${{ env.IKVMPATH }}/dist + path: ${{ env.IKVMPATH }} - name: Restore Tests run: tar xzvf tests.tar.gz - working-directory: ${{ env.IKVMPATH }}/dist + working-directory: ${{ env.IKVMPATH }} - name: Delete Tests shell: pwsh run: ri tests.tar.gz - working-directory: ${{ env.IKVMPATH }}/dist + working-directory: ${{ env.IKVMPATH }} - name: Execute Tests timeout-minutes: 120 shell: pwsh @@ -448,13 +443,17 @@ jobs: dotnet test -f $tfm --blame -v 2 --results-directory "TestResults" --logger:"console;verbosity=detailed" --logger:trx --collect "Code Coverage" $tests } } - working-directory: ${{ env.IKVMPATH }}/dist + working-directory: ${{ env.IKVMPATH }} + - name: Archive Test Results + if: always() && startsWith(env.RET, 'TestResults--') + run: tar czvf ${{ env.TMPDIR }}/TestResults.tar.gz TestResults + working-directory: ${{ env.IKVMPATH }} - name: Upload Test Results if: always() && startsWith(env.RET, 'TestResults--') uses: actions/upload-artifact@v3 with: name: ${{ env.RET }} - path: ${{ env.IKVMPATH }}/dist/TestResults + path: ${{ env.TMPDIR }}/TestResults.tar.gz release: name: Release if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/hotfix/') @@ -538,14 +537,6 @@ jobs: env: GITHUB_REPOS: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Push NuGet (Azure DevOps) - shell: pwsh - run: | - dotnet nuget add source $env:AZUREDEVOPS_REPOS --name az --username az --password $env:AZUREDEVOPS_TOKEN --store-password-in-clear-text - dotnet nuget push dist/nuget/*.nupkg --source az --api-key az --skip-duplicate --no-symbols - env: - AZUREDEVOPS_REPOS: https://pkgs.dev.azure.com/ikvm-revived/ikvm/_packaging/ikvm-ci/nuget/v3/index.json - AZUREDEVOPS_TOKEN: ${{ secrets.AZUREDEVOPS_PAT }} - name: Push NuGet if: github.ref == 'refs/heads/main' || github.event.head_commit.message == '+push' shell: pwsh diff --git a/IKVM.deps.props b/IKVM.deps.props index cc8c723926..2280d2e86b 100644 --- a/IKVM.deps.props +++ b/IKVM.deps.props @@ -3,6 +3,7 @@ + diff --git a/IKVM.sln b/IKVM.sln index 58acd14991..67013badae 100644 --- a/IKVM.sln +++ b/IKVM.sln @@ -283,10 +283,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tools.Importer", "src\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tools.Importer.Tests", "src\IKVM.Tools.Importer.Tests\IKVM.Tools.Importer.Tests.csproj", "{6C942D7C-654A-4CBE-B3A9-887A37D2FA4C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tests.Java", "src\IKVM.Tests.Java\IKVM.Tests.Java.msbuildproj", "{87CB99AE-16D1-4D58-BEAC-72485E02B321}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.ByteCode.Tests", "src\IKVM.ByteCode.Tests\IKVM.ByteCode.Tests.csproj", "{60E804E4-B25B-4C9A-A3E6-F83D618CFD26}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Java.Tests", "src\IKVM.Java.Tests\IKVM.Java.Tests.msbuildproj", "{B1B3F6B2-8A32-4C49-A9D2-154CD084CD9D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -605,14 +605,14 @@ Global {6C942D7C-654A-4CBE-B3A9-887A37D2FA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU {6C942D7C-654A-4CBE-B3A9-887A37D2FA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C942D7C-654A-4CBE-B3A9-887A37D2FA4C}.Release|Any CPU.Build.0 = Release|Any CPU - {87CB99AE-16D1-4D58-BEAC-72485E02B321}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87CB99AE-16D1-4D58-BEAC-72485E02B321}.Debug|Any CPU.Build.0 = Debug|Any CPU - {87CB99AE-16D1-4D58-BEAC-72485E02B321}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87CB99AE-16D1-4D58-BEAC-72485E02B321}.Release|Any CPU.Build.0 = Release|Any CPU {60E804E4-B25B-4C9A-A3E6-F83D618CFD26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {60E804E4-B25B-4C9A-A3E6-F83D618CFD26}.Debug|Any CPU.Build.0 = Debug|Any CPU {60E804E4-B25B-4C9A-A3E6-F83D618CFD26}.Release|Any CPU.ActiveCfg = Release|Any CPU {60E804E4-B25B-4C9A-A3E6-F83D618CFD26}.Release|Any CPU.Build.0 = Release|Any CPU + {B1B3F6B2-8A32-4C49-A9D2-154CD084CD9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1B3F6B2-8A32-4C49-A9D2-154CD084CD9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1B3F6B2-8A32-4C49-A9D2-154CD084CD9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1B3F6B2-8A32-4C49-A9D2-154CD084CD9D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index a9a2156703..dd45a4823c 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ IKVM includes build-time support for translating Java libraries to .NET assembli - + ``` @@ -102,7 +102,7 @@ The output assembly will be generated as part of your project's build process an ```xml - + MyAssembly 3.2.1.0 3.0.0.0 diff --git a/ext/helloworld-2.0.jar b/ext/helloworld/helloworld-2.0.jar similarity index 100% rename from ext/helloworld-2.0.jar rename to ext/helloworld/helloworld-2.0.jar diff --git a/ext/helloworld-mod.jar b/ext/helloworld/helloworld-mod.jar similarity index 100% rename from ext/helloworld-mod.jar rename to ext/helloworld/helloworld-mod.jar diff --git a/src/IKVM.JTReg.TestAdapter.Core/JTRegTestManager.cs b/src/IKVM.JTReg.TestAdapter.Core/JTRegTestManager.cs index 2e3e37b5d8..354e50c987 100644 --- a/src/IKVM.JTReg.TestAdapter.Core/JTRegTestManager.cs +++ b/src/IKVM.JTReg.TestAdapter.Core/JTRegTestManager.cs @@ -34,17 +34,6 @@ public class JTRegTestManager static readonly MD5 md5 = MD5.Create(); - /// - /// Computes the hash of the value. - /// - /// - /// - static byte[] ComputeHash(byte[] buffer) - { - lock (md5) - return md5.ComputeHash(buffer); - } - /// /// Initializes the static instance. /// @@ -82,10 +71,22 @@ static JTRegTestManager() /// /// /// - protected static string GetSourceHash(string source) + protected static string GetSourceId(string source) { + // allows a lock around the MD5 instance + byte[] ComputeHash(byte[] buffer) + { + lock (md5) + { + var b = new byte[8]; + var h = md5.ComputeHash(buffer); + Array.Copy(h, b, 8); + return b; + } + } + var b = ComputeHash(Encoding.UTF8.GetBytes(source)); - var s = new StringBuilder(32); + var s = new StringBuilder(16); for (int i = 0; i < b.Length; i++) s.Append(b[i].ToString("x2")); return s.ToString(); @@ -182,11 +183,11 @@ public void DiscoverTests(string source, IJTRegDiscoveryContext context, Cancell return; // output path for jtreg state - var id = GetSourceHash(source); + var id = GetSourceId(source); var baseDir = Path.Combine(Path.GetTempPath(), BASEDIR_PREFIX + id); // attempt to create a temporary directory as scratch space for this test - context.SendMessage(JTRegTestMessageLevel.Informational, $"JTReg: Using run directory: {baseDir}"); + context.SendMessage(JTRegTestMessageLevel.Informational, $"JTReg: Using discover directory: {baseDir}"); Directory.CreateDirectory(baseDir); // output to framework @@ -293,8 +294,11 @@ internal void RunTestForSource(string source, IJTRegExecutionContext context, Li return; // output path for jtreg state - var id = GetSourceHash(source); + var id = GetSourceId(source); var baseDir = Path.Combine(context.TestRunDirectory, BASEDIR_PREFIX + id); + + // attempt to create a temporary directory as scratch space for this test + context.SendMessage(JTRegTestMessageLevel.Informational, $"JTReg: Using run directory: {baseDir}"); Directory.CreateDirectory(baseDir); // output to framework diff --git a/src/IKVM.Java-ref/java/lang/Class.cs b/src/IKVM.Java-ref/java/lang/Class.cs index 3075fc094a..f2453f4522 100644 --- a/src/IKVM.Java-ref/java/lang/Class.cs +++ b/src/IKVM.Java-ref/java/lang/Class.cs @@ -1,9 +1,16 @@ -namespace java.lang +using System; + +namespace java.lang { public class Class { + public Class(Type type) + { + + } + } } diff --git a/src/IKVM.Java.Extensions/java/util/IteratorExtensions.cs b/src/IKVM.Java.Extensions/java/util/IteratorExtensions.cs index fc7ce72143..e055609cff 100644 --- a/src/IKVM.Java.Extensions/java/util/IteratorExtensions.cs +++ b/src/IKVM.Java.Extensions/java/util/IteratorExtensions.cs @@ -36,6 +36,18 @@ public static List RemainingToList(this Iterator iterator) return l; } + /// + /// Returns an enumerable that iterators over the items in an iterator. + /// + /// + /// + /// + public static IEnumerable RemainingToEnumerable(this Iterator iterator) + { + while (iterator.hasNext()) + yield return (T)iterator.next(); + } + } } diff --git a/src/IKVM.Tests.Java/IKVM.Tests.Java.msbuildproj b/src/IKVM.Java.Tests/IKVM.Java.Tests.msbuildproj similarity index 94% rename from src/IKVM.Tests.Java/IKVM.Tests.Java.msbuildproj rename to src/IKVM.Java.Tests/IKVM.Java.Tests.msbuildproj index a08ebb273c..ebf39a0f2a 100644 --- a/src/IKVM.Tests.Java/IKVM.Tests.Java.msbuildproj +++ b/src/IKVM.Java.Tests/IKVM.Java.Tests.msbuildproj @@ -19,10 +19,6 @@ - - - - diff --git a/src/IKVM.Java.Tests/java/util/concurrent/ForkJoinPoolTests.java b/src/IKVM.Java.Tests/java/util/concurrent/ForkJoinPoolTests.java new file mode 100644 index 0000000000..4fd8b929a6 --- /dev/null +++ b/src/IKVM.Java.Tests/java/util/concurrent/ForkJoinPoolTests.java @@ -0,0 +1,48 @@ +package ikvm.tests.java.java.util.concurrent; + +import java.lang.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +@cli.Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute.Annotation() +public class ForkJoinPoolTests { + + class RecursiveActionTestTask extends RecursiveAction { + + private int range; + private AtomicInteger count; + + public RecursiveActionTestTask(int range, AtomicInteger count) { + this.range = range; + this.count = count; + } + + protected void compute() { + if (this.range > 1) { + RecursiveActionTestTask t1 = new RecursiveActionTestTask(this.range / 2, count); + t1.fork(); + RecursiveActionTestTask t2 = new RecursiveActionTestTask(this.range / 2, count); + t2.fork(); + + t1.join(); + t2.join(); + } else { + count.incrementAndGet(); + } + } + + } + + @cli.Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute.Annotation() + public void canExecuteRecursiveAction() throws Exception { + ForkJoinPool p = ForkJoinPool.commonPool(); + AtomicInteger c = new AtomicInteger(); + RecursiveActionTestTask t = new RecursiveActionTestTask(1024 * 1024, c); + p.invoke(t); + if (c.get() != 1024 * 1024) { + throw new Exception(); + } + } + +} diff --git a/src/IKVM.Java/local/ikvm/runtime/Launcher.java b/src/IKVM.Java/local/ikvm/runtime/Launcher.java deleted file mode 100644 index da31e65049..0000000000 --- a/src/IKVM.Java/local/ikvm/runtime/Launcher.java +++ /dev/null @@ -1,22 +0,0 @@ -package ikvm.runtime; - -import java.util.Properties; - -import cli.System.Type; - -public final class Launcher -{ - - private Launcher() - { - - } - - /** - * Launches the given .NET type as a Java application class. This method should be invoked before any other JVM work is - * conducted, ideally by generated executables. - */ - @cli.IKVM.Attributes.HideFromJavaAttribute.Annotation - public static native int run(Type main, String[] args, String jvmArgPrefix, Properties properties); - -} diff --git a/src/IKVM.Java/local/java/util/concurrent/locks/AbstractQueuedSynchronizer.java b/src/IKVM.Java/local/java/util/concurrent/locks/AbstractQueuedSynchronizer.java index f2e5a5c8a2..dce35765df 100644 --- a/src/IKVM.Java/local/java/util/concurrent/locks/AbstractQueuedSynchronizer.java +++ b/src/IKVM.Java/local/java/util/concurrent/locks/AbstractQueuedSynchronizer.java @@ -38,7 +38,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Date; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import sun.misc.Unsafe; /** * Provides a framework for implementing blocking locks and related @@ -378,7 +378,6 @@ protected AbstractQueuedSynchronizer() { } * on the design of this class. */ static final class Node { - static final AtomicReferenceFieldUpdater nextUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next"); /** Marker to indicate a node is waiting in shared mode */ static final Node SHARED = new Node(); /** Marker to indicate a node is waiting in exclusive mode */ @@ -544,7 +543,7 @@ protected final int getState() { /** * Sets the value of synchronization state. - * This operation has memory semantics of a {@code volatile} read. + * This operation has memory semantics of a {@code volatile} write. * @param newState the new state value */ protected final void setState(int newState) { @@ -562,7 +561,10 @@ protected final void setState(int newState) { * @return {@code true} if successful. False return indicates that the actual * value was not equal to the expected value. */ - protected final native boolean compareAndSetState(int expect, int update); // implemented in map.xml + protected final boolean compareAndSetState(int expect, int update) { + // See below for intrinsics setup to support this + return unsafe.compareAndSwapInt(this, stateOffset, expect, update); + } // Queuing utilities @@ -661,7 +663,7 @@ private void unparkSuccessor(Node node) { } /** - * Release action for shared mode -- signals successor and ensure + * Release action for shared mode -- signals successor and ensures * propagation. (Note: For exclusive mode, release just amounts * to calling unparkSuccessor of head if it needs signal.) */ @@ -2247,39 +2249,67 @@ protected final Collection getWaitingThreads() { } /** - * IKVM specific. We use AtomicReferenceFieldUpdater instead of Unsafe primitives. + * Setup to support compareAndSet. We need to natively implement + * this here: For the sake of permitting future enhancements, we + * cannot explicitly subclass AtomicInteger, which would be + * efficient and useful otherwise. So, as the lesser of evils, we + * natively implement using hotspot intrinsics API. And while we + * are at it, we do the same for other CASable fields (which could + * otherwise be done with atomic field updaters). */ - private static final AtomicReferenceFieldUpdater headUpdater = - AtomicReferenceFieldUpdater.newUpdater(AbstractQueuedSynchronizer.class, Node.class, "head"); - private static final AtomicReferenceFieldUpdater tailUpdater = - AtomicReferenceFieldUpdater.newUpdater(AbstractQueuedSynchronizer.class, Node.class, "tail"); + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long stateOffset; + private static final long headOffset; + private static final long tailOffset; + private static final long waitStatusOffset; + private static final long nextOffset; + + static { + try { + stateOffset = unsafe.objectFieldOffset + (AbstractQueuedSynchronizer.class.getDeclaredField("state")); + headOffset = unsafe.objectFieldOffset + (AbstractQueuedSynchronizer.class.getDeclaredField("head")); + tailOffset = unsafe.objectFieldOffset + (AbstractQueuedSynchronizer.class.getDeclaredField("tail")); + waitStatusOffset = unsafe.objectFieldOffset + (Node.class.getDeclaredField("waitStatus")); + nextOffset = unsafe.objectFieldOffset + (Node.class.getDeclaredField("next")); + + } catch (Exception ex) { throw new Error(ex); } + } /** * CAS head field. Used only by enq. */ private final boolean compareAndSetHead(Node update) { - return headUpdater.compareAndSet(this, null, update); + return unsafe.compareAndSwapObject(this, headOffset, null, update); } /** * CAS tail field. Used only by enq. */ private final boolean compareAndSetTail(Node expect, Node update) { - return tailUpdater.compareAndSet(this, expect, update); + return unsafe.compareAndSwapObject(this, tailOffset, expect, update); } /** * CAS waitStatus field of a node. */ - private static final native boolean compareAndSetWaitStatus(Node node, + private static final boolean compareAndSetWaitStatus(Node node, int expect, - int update); // implemented in map.xml + int update) { + return unsafe.compareAndSwapInt(node, waitStatusOffset, + expect, update); + } + /** * CAS next field of a node. */ private static final boolean compareAndSetNext(Node node, Node expect, Node update) { - return Node.nextUpdater.compareAndSet(node, expect, update); + return unsafe.compareAndSwapObject(node, nextOffset, expect, update); } } diff --git a/src/IKVM.Java/local/java/util/concurrent/locks/LockSupport.java b/src/IKVM.Java/local/java/util/concurrent/locks/LockSupport.java index 45a3811296..46a0ab597c 100644 --- a/src/IKVM.Java/local/java/util/concurrent/locks/LockSupport.java +++ b/src/IKVM.Java/local/java/util/concurrent/locks/LockSupport.java @@ -34,6 +34,7 @@ */ package java.util.concurrent.locks; +import sun.misc.Unsafe; /** * Basic thread blocking primitives for creating locks and other @@ -120,18 +121,10 @@ public class LockSupport { private LockSupport() {} // Cannot be instantiated. private static void setBlocker(Thread t, Object arg) { - t.parkBlocker = arg; + // Even though volatile, hotspot doesn't need a write barrier here. + UNSAFE.putObject(t, parkBlockerOffset, arg); } - private static final int PARK_STATE_RUNNING = 0; - private static final int PARK_STATE_PERMIT = 1; - private static final int PARK_STATE_PARKED = 2; - - // these native methods are all implemented in map.xml - private static native int cmpxchgParkState(Thread t, int newValue, int comparand); - private static native Object getParkLock(Thread t); - private static native void setParkLock(Thread t, Object obj); - /** * Makes available the permit for the given thread, if it * was not already available. If the thread was blocked on @@ -145,63 +138,7 @@ private static void setBlocker(Thread t, Object arg) { */ public static void unpark(Thread thread) { if (thread != null) - { - if (cmpxchgParkState(thread, PARK_STATE_PERMIT, PARK_STATE_RUNNING) == PARK_STATE_PARKED) - { - if (cmpxchgParkState(thread, PARK_STATE_RUNNING, PARK_STATE_PARKED) == PARK_STATE_PARKED) - { - // thread is currently blocking, so we have to release it - Object lock = getParkLock(thread); - synchronized (lock) - { - lock.notify(); - } - } - } - } - } - - private static void parkImpl(Thread currentThread, boolean deadline, long nanos) - { - if (cmpxchgParkState(currentThread, PARK_STATE_RUNNING, PARK_STATE_PERMIT) == PARK_STATE_PERMIT) - { - // we consumed a permit - return; - } - - Object lock = getParkLock(currentThread); - if (lock == null) - { - // we lazily allocate the lock object - lock = new Object(); - setParkLock(currentThread, lock); - } - synchronized (lock) - { - if (cmpxchgParkState(currentThread, PARK_STATE_PARKED, PARK_STATE_RUNNING) == PARK_STATE_PERMIT) - { - // entering the parked state failed because we got a permit after the previous permit test - // release the permit and return - cmpxchgParkState(currentThread, PARK_STATE_RUNNING, PARK_STATE_PERMIT); - return; - } - if (deadline) - { - nanos -= System.currentTimeMillis() * 1000000; - } - if (nanos >= 0) - { - try - { - lock.wait(nanos / 1000000, (int)(nanos % 1000000)); - } - catch (InterruptedException _) - { - currentThread.interrupt(); - } - } - cmpxchgParkState(currentThread, PARK_STATE_RUNNING, PARK_STATE_PARKED); - } + UNSAFE.unpark(thread); } /** @@ -235,7 +172,7 @@ private static void parkImpl(Thread currentThread, boolean deadline, long nanos) public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); - parkImpl(t, false, 0L); + UNSAFE.park(false, 0L); setBlocker(t, null); } @@ -275,7 +212,7 @@ public static void parkNanos(Object blocker, long nanos) { if (nanos > 0) { Thread t = Thread.currentThread(); setBlocker(t, blocker); - parkImpl(t, false, nanos); + UNSAFE.park(false, nanos); setBlocker(t, null); } } @@ -316,7 +253,7 @@ public static void parkNanos(Object blocker, long nanos) { public static void parkUntil(Object blocker, long deadline) { Thread t = Thread.currentThread(); setBlocker(t, blocker); - parkImpl(t, true, deadline * 1000000); + UNSAFE.park(true, deadline); setBlocker(t, null); } @@ -335,7 +272,7 @@ public static void parkUntil(Object blocker, long deadline) { public static Object getBlocker(Thread t) { if (t == null) throw new NullPointerException(); - return t.parkBlocker; + return UNSAFE.getObjectVolatile(t, parkBlockerOffset); } /** @@ -364,7 +301,7 @@ public static Object getBlocker(Thread t) { * for example, the interrupt status of the thread upon return. */ public static void park() { - parkImpl(Thread.currentThread(), false, 0L); + UNSAFE.park(false, 0L); } /** @@ -398,7 +335,7 @@ public static void park() { */ public static void parkNanos(long nanos) { if (nanos > 0) - parkImpl(Thread.currentThread(), false, nanos); + UNSAFE.park(false, nanos); } /** @@ -432,7 +369,7 @@ public static void parkNanos(long nanos) { * to wait until */ public static void parkUntil(long deadline) { - parkImpl(Thread.currentThread(), true, deadline * 1000000); + UNSAFE.park(true, deadline); } /** @@ -442,15 +379,36 @@ public static void parkUntil(long deadline) { static final int nextSecondarySeed() { int r; Thread t = Thread.currentThread(); - if ((r = t.threadLocalRandomSecondarySeed) != 0) { + if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) { r ^= r << 13; // xorshift r ^= r >>> 17; r ^= r << 5; } else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0) r = 1; // avoid zero - t.threadLocalRandomSecondarySeed = r; + UNSAFE.putInt(t, SECONDARY, r); return r; } + // Hotspot implementation via intrinsics API + private static final sun.misc.Unsafe UNSAFE; + private static final long parkBlockerOffset; + private static final long SEED; + private static final long PROBE; + private static final long SECONDARY; + static { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class tk = Thread.class; + parkBlockerOffset = UNSAFE.objectFieldOffset + (tk.getDeclaredField("parkBlocker")); + SEED = UNSAFE.objectFieldOffset + (tk.getDeclaredField("threadLocalRandomSeed")); + PROBE = UNSAFE.objectFieldOffset + (tk.getDeclaredField("threadLocalRandomProbe")); + SECONDARY = UNSAFE.objectFieldOffset + (tk.getDeclaredField("threadLocalRandomSecondarySeed")); + } catch (Exception ex) { throw new Error(ex); } + } + } diff --git a/src/IKVM.Java/map.xml b/src/IKVM.Java/map.xml index 39eab4a394..0a1f628502 100644 --- a/src/IKVM.Java/map.xml +++ b/src/IKVM.Java/map.xml @@ -33,6 +33,12 @@ Never + + + + + + @@ -1618,62 +1624,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/IKVM.MSBuild.Tasks.Tests/IKVM.MSBuild.Tasks.Tests.csproj b/src/IKVM.MSBuild.Tasks.Tests/IKVM.MSBuild.Tasks.Tests.csproj index d0ecb25885..0691ae9950 100644 --- a/src/IKVM.MSBuild.Tasks.Tests/IKVM.MSBuild.Tasks.Tests.csproj +++ b/src/IKVM.MSBuild.Tasks.Tests/IKVM.MSBuild.Tasks.Tests.csproj @@ -7,10 +7,10 @@ - + PreserveNewest - + PreserveNewest diff --git a/src/IKVM.MSBuild.Tasks.Tests/IkvmReferenceItemPrepareTests.cs b/src/IKVM.MSBuild.Tasks.Tests/IkvmReferenceItemPrepareTests.cs index 66a358ab55..b828d76b74 100644 --- a/src/IKVM.MSBuild.Tasks.Tests/IkvmReferenceItemPrepareTests.cs +++ b/src/IKVM.MSBuild.Tasks.Tests/IkvmReferenceItemPrepareTests.cs @@ -16,7 +16,7 @@ namespace IKVM.MSBuild.Tasks.Tests public class IkvmReferenceItemPrepareTests { - readonly static string HELLOWORLD1_JAR = @".\ext\helloworld-2.0-1\helloworld-2.0.jar"; + readonly static string HELLOWORLD1_JAR = @".\helloworld\helloworld-2.0-1\helloworld-2.0.jar"; /// /// Builds a new task instance with various information. @@ -38,17 +38,17 @@ IkvmReferenceItemPrepare BuildTestTask(string toolFramework, string toolVersion) [TestMethod] public void Should_normalize_jar_itemspec() { - var i1 = new TaskItem(Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + var i1 = new TaskItem(Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); IkvmReferenceItemPrepare.AssignMetadata(IkvmReferenceItem.Import(new[] { i1 })); - i1.ItemSpec.Should().Be(Path.Combine("ext", "helloworld-2.0-1", "helloworld-2.0.jar")); + i1.ItemSpec.Should().Be(Path.Combine("helloworld", "helloworld-2.0-1", "helloworld-2.0.jar")); } [TestMethod] public void Should_normalize_dir_itemspec() { - var i1 = new TaskItem(@".\ext"); + var i1 = new TaskItem(@".\helloworld"); IkvmReferenceItemPrepare.AssignMetadata(IkvmReferenceItem.Import(new[] { i1 })); - i1.ItemSpec.Should().Be(@"ext" + Path.DirectorySeparatorChar); + i1.ItemSpec.Should().Be(@"helloworld" + Path.DirectorySeparatorChar); } [TestMethod] @@ -62,30 +62,30 @@ public void Should_not_normalize_unknown_itemspec() [TestMethod] public void Should_add_jar_identity_to_compile() { - var i1 = new TaskItem(Path.Combine(".", "ext", "helloworld-2.0-1", "helloworld-2.0.jar")); + var i1 = new TaskItem(Path.Combine(".", "helloworld", "helloworld-2.0-1", "helloworld-2.0.jar")); IkvmReferenceItemPrepare.AssignMetadata(IkvmReferenceItem.Import(new[] { i1 })); var c = i1.GetMetadata(IkvmReferenceItemMetadata.Compile); - c.Split(IkvmReferenceItemMetadata.PropertySeperatorChar).Should().Contain(Path.Combine("ext", "helloworld-2.0-1", "helloworld-2.0.jar")); + c.Split(IkvmReferenceItemMetadata.PropertySeperatorChar).Should().Contain(Path.Combine("helloworld", "helloworld-2.0-1", "helloworld-2.0.jar")); } [TestMethod] public void Should_not_add_dir_identity_to_compile() { - var i1 = new TaskItem(Path.Combine(".", "ext")); + var i1 = new TaskItem(Path.Combine(".", "helloworld")); IkvmReferenceItemPrepare.AssignMetadata(IkvmReferenceItem.Import(new[] { i1 })); var c = i1.GetMetadata(IkvmReferenceItemMetadata.Compile); - c.Split(IkvmReferenceItemMetadata.PropertySeperatorChar).Should().NotContain(@"ext"); + c.Split(IkvmReferenceItemMetadata.PropertySeperatorChar).Should().NotContain(@"helloworld"); } [TestMethod] public void Should_resolve_reference() { - var i1 = new TaskItem(Path.Combine(".", "ext", "helloworld-2.0-1", "helloworld-2.0.jar")); - var i2 = new TaskItem(Path.Combine(".", "ext", "helloworld-2.0-2", "helloworld-2.0.jar")); - i2.SetMetadata(IkvmReferenceItemMetadata.References, Path.Combine(".", "ext", "helloworld-2.0-1", "helloworld-2.0.jar")); + var i1 = new TaskItem(Path.Combine(".", "helloworld", "helloworld-2.0-1", "helloworld-2.0.jar")); + var i2 = new TaskItem(Path.Combine(".", "helloworld", "helloworld-2.0-2", "helloworld-2.0.jar")); + i2.SetMetadata(IkvmReferenceItemMetadata.References, Path.Combine(".", "helloworld", "helloworld-2.0-1", "helloworld-2.0.jar")); IkvmReferenceItemPrepare.AssignMetadata(IkvmReferenceItem.Import(new[] { i1, i2 })); var c = i2.GetMetadata(IkvmReferenceItemMetadata.References); - c.Split(IkvmReferenceItemMetadata.PropertySeperatorChar).Should().Contain(Path.Combine("ext", "helloworld-2.0-1", "helloworld-2.0.jar")); + c.Split(IkvmReferenceItemMetadata.PropertySeperatorChar).Should().Contain(Path.Combine("helloworld", "helloworld-2.0-1", "helloworld-2.0.jar")); } [TestMethod] @@ -115,9 +115,9 @@ public void Should_fail_when_missing_name() var t = new IkvmReferenceItemPrepare(); t.BuildEngine = engine.Object; - var i1 = new TaskItem(Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + var i1 = new TaskItem(Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); i1.SetMetadata(IkvmReferenceItemMetadata.AssemblyVersion, "2.0"); - i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); t.Items = new[] { i1 }; t.Validate(IkvmReferenceItem.Import(t.Items)).Should().BeFalse(); errors.Should().Contain(x => x.Code == "IKVMSDK0011"); @@ -132,9 +132,9 @@ public void Should_fail_when_missing_version() var t = new IkvmReferenceItemPrepare(); t.BuildEngine = engine.Object; - var i1 = new TaskItem(Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + var i1 = new TaskItem(Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); i1.SetMetadata(IkvmReferenceItemMetadata.AssemblyName, "helloworld"); - i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); t.Items = new[] { i1 }; t.Validate(IkvmReferenceItem.Import(t.Items)).Should().BeFalse(); errors.Should().Contain(x => x.Code == "IKVMSDK0012"); @@ -149,9 +149,9 @@ public void Should_fail_when_missing_file_version() var t = new IkvmReferenceItemPrepare(); t.BuildEngine = engine.Object; - var i1 = new TaskItem(Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + var i1 = new TaskItem(Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); i1.SetMetadata(IkvmReferenceItemMetadata.AssemblyName, "helloworld"); - i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); t.Items = new[] { i1 }; t.Validate(IkvmReferenceItem.Import(t.Items)).Should().BeFalse(); errors.Should().Contain(x => x.Code == "IKVMSDK0013"); @@ -166,10 +166,10 @@ public void Should_fail_when_invalid_version() var t = new IkvmReferenceItemPrepare(); t.BuildEngine = engine.Object; - var i1 = new TaskItem(Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + var i1 = new TaskItem(Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); i1.SetMetadata(IkvmReferenceItemMetadata.AssemblyName, "helloworld"); i1.SetMetadata(IkvmReferenceItemMetadata.AssemblyVersion, "invalid"); - i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); t.Items = new[] { i1 }; t.Validate(IkvmReferenceItem.Import(t.Items)).Should().BeFalse(); errors.Should().Contain(x => x.Code == "IKVMSDK0003"); @@ -202,10 +202,10 @@ public void Should_fail_when_invalid_sources() var t = new IkvmReferenceItemPrepare(); t.BuildEngine = engine.Object; - var i1 = new TaskItem(Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + var i1 = new TaskItem(Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); i1.SetMetadata(IkvmReferenceItemMetadata.AssemblyName, "helloworld"); i1.SetMetadata(IkvmReferenceItemMetadata.AssemblyVersion, "2.0"); - i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); i1.SetMetadata(IkvmReferenceItemMetadata.Sources, "invalid"); t.Items = new[] { i1 }; t.Validate(IkvmReferenceItem.Import(t.Items)).Should().BeFalse(); @@ -221,10 +221,10 @@ public void Should_fail_when_missing_sources() var t = new IkvmReferenceItemPrepare(); t.BuildEngine = engine.Object; - var i1 = new TaskItem(Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + var i1 = new TaskItem(Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); i1.SetMetadata(IkvmReferenceItemMetadata.AssemblyName, "helloworld"); i1.SetMetadata(IkvmReferenceItemMetadata.AssemblyVersion, "2.0"); - i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "ext", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); + i1.SetMetadata(IkvmReferenceItemMetadata.Compile, Path.Combine(".", "helloworld", "helloworld-2.0-1", ".", "helloworld-2.0.jar")); i1.SetMetadata(IkvmReferenceItemMetadata.Sources, "missing.java"); t.Items = new[] { i1 }; t.Validate(IkvmReferenceItem.Import(t.Items)).Should().BeFalse(); diff --git a/src/IKVM.MSBuild.Tests/IKVM.MSBuild.Tests.csproj b/src/IKVM.MSBuild.Tests/IKVM.MSBuild.Tests.csproj index 2bf9111ad9..fb867cc26d 100644 --- a/src/IKVM.MSBuild.Tests/IKVM.MSBuild.Tests.csproj +++ b/src/IKVM.MSBuild.Tests/IKVM.MSBuild.Tests.csproj @@ -11,10 +11,10 @@ - + PreserveNewest - + PreserveNewest diff --git a/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets b/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets index 7d323d8e47..fd9b16e6df 100644 --- a/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets +++ b/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets @@ -1,4 +1,4 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) diff --git a/src/IKVM.Runtime/Accessors/Accessor.cs b/src/IKVM.Runtime/Accessors/Accessor.cs index 44fd8373d4..df3f483339 100644 --- a/src/IKVM.Runtime/Accessors/Accessor.cs +++ b/src/IKVM.Runtime/Accessors/Accessor.cs @@ -48,6 +48,25 @@ public Accessor(AccessorTypeResolver resolver, string typeName) /// protected Type Resolve(ref Type type, string typeName) => AccessorUtil.LazyGet(ref type, () => Resolve(typeName)); + /// + /// Creates a new array of the accessed type. + /// + /// + /// + public object[] InitArray(int length) => (object[])Array.CreateInstance(Type, length); + + /// + /// Creates a new array of the accessed type from the elements in the given array. + /// + /// + /// + public object[] InitArray(params object[] source) + { + var a = InitArray(source.Length); + Array.Copy(source, a, source.Length); + return a; + } + } /// diff --git a/src/IKVM.Runtime/Accessors/AccessorRef.cs b/src/IKVM.Runtime/Accessors/AccessorRef.cs new file mode 100644 index 0000000000..4a01158adf --- /dev/null +++ b/src/IKVM.Runtime/Accessors/AccessorRef.cs @@ -0,0 +1,24 @@ +namespace IKVM.Runtime.Accessors +{ + +#if FIRST_PASS == false && EXPORTER == false && IMPORTER == false + + /// + /// Represents a reference to an accessor. + /// + /// + internal struct AccessorRef where T : Accessor + { + + T accessor; + + /// + /// Gets the accessor value. + /// + public T Value => JVM.BaseAccessors.Get(ref accessor); + + } + +#endif + +} diff --git a/src/IKVM.Runtime/Accessors/Java/Lang/ClassAccessor.cs b/src/IKVM.Runtime/Accessors/Java/Lang/ClassAccessor.cs new file mode 100644 index 0000000000..1b88c73b43 --- /dev/null +++ b/src/IKVM.Runtime/Accessors/Java/Lang/ClassAccessor.cs @@ -0,0 +1,67 @@ +using System; + +namespace IKVM.Runtime.Accessors.Java.Lang +{ + +#if FIRST_PASS == false && EXPORTER == false && IMPORTER == false + + /// + /// Provides runtime access to the 'java.lang.Class' type. + /// + internal sealed class ClassAccessor : Accessor + { + + Type ikvmInternalCallerID; + Type javaLangClass; + Type javaLangReflectMethod; + + MethodAccessor> init; + MethodAccessor> forName; + MethodAccessor> getMethod; + + /// + /// Initializes a new instance. + /// + /// + public ClassAccessor(AccessorTypeResolver resolver) : + base(resolver, "java.lang.Class") + { + + } + + Type IkvmInternalCallerID => Resolve(ref ikvmInternalCallerID, "ikvm.internal.CallerID"); + + Type JavaLangClass => Resolve(ref javaLangClass, "java.lang.Class"); + + Type JavaLangReflectMethod => Resolve(ref javaLangReflectMethod, "java.lang.reflect.Method"); + + /// + /// Creates a new instance. + /// + /// + /// + public object Init(Type type) => GetConstructor(ref init, typeof(Type)).Invoker(type); + + /// + /// Invokes the 'forName' method. + /// + /// + /// + /// + public object InvokeForName(string name, object callerID) => GetMethod(ref forName, nameof(forName), JavaLangClass, typeof(string), IkvmInternalCallerID).Invoker(name, callerID); + + /// + /// Invokes the 'getMethod' method. + /// + /// + /// + /// + /// + /// + public object InvokeGetMethod(object self, string name, object[] parameterTypes, object callerID) => GetMethod(ref getMethod, nameof(getMethod), JavaLangReflectMethod, typeof(string), JavaLangClass.MakeArrayType(), IkvmInternalCallerID).Invoker(self, name, parameterTypes, callerID); + + } + +#endif + +} diff --git a/src/IKVM.Runtime/Accessors/Java/Lang/Reflect/MethodAccessor.cs b/src/IKVM.Runtime/Accessors/Java/Lang/Reflect/MethodAccessor.cs new file mode 100644 index 0000000000..27e6d7cf01 --- /dev/null +++ b/src/IKVM.Runtime/Accessors/Java/Lang/Reflect/MethodAccessor.cs @@ -0,0 +1,41 @@ +using System; + +namespace IKVM.Runtime.Accessors.Java.Lang.Reflect +{ + +#if FIRST_PASS == false && EXPORTER == false && IMPORTER == false + + /// + /// Provides runtime access to the 'java.lang.reflect.Method' type. + /// + internal sealed class MethodAccessor : Accessor + { + + MethodAccessor> setAccessible; + MethodAccessor> invoke; + + /// + /// Initializes a new instance. + /// + /// + public MethodAccessor(AccessorTypeResolver resolver) : + base(resolver, "java.lang.reflect.Method") + { + + } + + /// + /// Invokes the 'setAccessible' method. + /// + public void InvokeSetAccessible(object self, bool flag) => GetMethod(ref setAccessible, nameof(setAccessible), typeof(void), typeof(bool)).Invoker(self, flag); + + /// + /// Invokes the 'invoke' method. + /// + public void InvokeInvoke(object self, object obj, object[] args) => GetMethod(ref invoke, nameof(invoke), typeof(object), typeof(object), typeof(object[])).Invoker(self, obj, args); + + } + +#endif + +} diff --git a/src/IKVM.Runtime/Accessors/Java/Lang/ThreadGroupAccessor.cs b/src/IKVM.Runtime/Accessors/Java/Lang/ThreadGroupAccessor.cs index 1c2590011f..ef15837437 100644 --- a/src/IKVM.Runtime/Accessors/Java/Lang/ThreadGroupAccessor.cs +++ b/src/IKVM.Runtime/Accessors/Java/Lang/ThreadGroupAccessor.cs @@ -14,7 +14,6 @@ internal sealed class ThreadGroupAccessor : Accessor Type javaLangVoid; Type javaLangThreadGroup; Type javaLangThread; - Type javaLangThrowable; MethodAccessor> init1; MethodAccessor> init2; diff --git a/src/IKVM.Runtime/Accessors/Sun/Launcher/LauncherHelperAccessor.cs b/src/IKVM.Runtime/Accessors/Sun/Launcher/LauncherHelperAccessor.cs new file mode 100644 index 0000000000..8b392a286c --- /dev/null +++ b/src/IKVM.Runtime/Accessors/Sun/Launcher/LauncherHelperAccessor.cs @@ -0,0 +1,39 @@ +using System; + +namespace IKVM.Runtime.Accessors.Sun.Launcher +{ + +#if FIRST_PASS == false && EXPORTER == false && IMPORTER == false + + /// + /// Provides runtime access to the 'sun.launcher.LauncherHelper' type. + /// + internal sealed class LauncherHelperAccessor : Accessor + { + + MethodAccessor> checkAndLoadMain; + + /// + /// Initializes a new instance. + /// + /// + public LauncherHelperAccessor(AccessorTypeResolver resolver) : + base(resolver, "sun.launcher.LauncherHelper") + { + + } + + /// + /// Invokes the 'checkAndLoadMain' method. + /// + /// + /// + /// + /// + public object InvokeCheckAndLoadMain(bool printToStderr, int mode, string what) => GetMethod(ref checkAndLoadMain, nameof(checkAndLoadMain), Resolve("java.lang.Class"), typeof(bool), typeof(int), typeof(string)).Invoker(printToStderr, mode, what); + + } + +#endif + +} diff --git a/src/IKVM.Runtime/Accessors/Sun/Nio/Fs/DotNetDirectoryStreamAccessor.cs b/src/IKVM.Runtime/Accessors/Sun/Nio/Fs/DotNetDirectoryStreamAccessor.cs index eb0b62720f..f0dc7771ab 100644 --- a/src/IKVM.Runtime/Accessors/Sun/Nio/Fs/DotNetDirectoryStreamAccessor.cs +++ b/src/IKVM.Runtime/Accessors/Sun/Nio/Fs/DotNetDirectoryStreamAccessor.cs @@ -29,7 +29,6 @@ public DotNetDirectoryStreamAccessor(AccessorTypeResolver resolver) : Type SunNioFsDotNetPath => Resolve(ref sunNioFsDotNetPath, "sun.nio.fs.DotNetPath"); - Type JavaNioFileDirectoryStreamFilter => Resolve(ref javaNioFileDirectoryStreamFilter, "java.nio.file.DirectoryStream+Filter"); /// diff --git a/src/IKVM.Runtime/ByteCodeHelper.cs b/src/IKVM.Runtime/ByteCodeHelper.cs index 28948cfe5e..95f99ff5a7 100644 --- a/src/IKVM.Runtime/ByteCodeHelper.cs +++ b/src/IKVM.Runtime/ByteCodeHelper.cs @@ -1066,11 +1066,11 @@ public static T GetDotNetEnumField(string name) #else try { - return (T)global::java.lang.Enum.valueOf(global::ikvm.@internal.ClassLiteral.Value, name); + return (T)global::java.lang.Enum.valueOf((global::java.lang.Class)ClassLiteral.Value, name); } catch (global::java.lang.IllegalArgumentException) { - throw new global::java.lang.NoSuchFieldError(global::ikvm.@internal.ClassLiteral.Value.getName() + "." + name); + throw new global::java.lang.NoSuchFieldError(((global::java.lang.Class)ClassLiteral.Value).getName() + "." + name); } #endif } @@ -1143,7 +1143,7 @@ public static T GetDelegateForInvoke(global::java.lang.invoke.MethodHandle h, // if realType is set, the delegate contains erased unloadable types if (realType != null) { - adapter = adapter.asType(realType.insertParameterTypes(0, global::ikvm.@internal.ClassLiteral.Value)).asFixedArity(); + adapter = adapter.asType(realType.insertParameterTypes(0, (global::java.lang.Class)ClassLiteral.Value)).asFixedArity(); } adapter = adapter.asType(MethodHandleUtil.GetDelegateMethodType(typeof(T))); del = GetDelegateForInvokeExact(adapter); diff --git a/src/IKVM.Runtime/ClassLiteral.cs b/src/IKVM.Runtime/ClassLiteral.cs new file mode 100644 index 0000000000..94f8a84d71 --- /dev/null +++ b/src/IKVM.Runtime/ClassLiteral.cs @@ -0,0 +1,47 @@ +using System.Threading; + +using IKVM.Runtime.Accessors.Java.Lang; + +namespace IKVM.Runtime +{ + + /// + /// Provides a static cache of java.lang.Class instances per .NET type. + /// + /// + public static class ClassLiteral + { + +#if FIRST_PASS == false && IMPORTER == false && EXPORTER == false + + static ClassAccessor classAccessor; + static object value; + + static ClassAccessor ClassAccessor => JVM.BaseAccessors.Get(ref classAccessor); + + /// + /// Gets the class literal value, caching the result. + /// + public static object Value => GetValue(); + + /// + /// Gets the class literal value, caching the result. + /// + /// + static object GetValue() + { + if (value == null) + Interlocked.CompareExchange(ref value, ClassAccessor.Init(typeof(T)), null); + + return value; + } + +#else + + public static object Value => null; + +#endif + + } + +} diff --git a/src/IKVM.Runtime/FieldWrapper.cs b/src/IKVM.Runtime/FieldWrapper.cs index dea3336a4a..34a460e2c0 100644 --- a/src/IKVM.Runtime/FieldWrapper.cs +++ b/src/IKVM.Runtime/FieldWrapper.cs @@ -711,6 +711,7 @@ Delegate GetUnsafeCompareAndSwapDelegate() /// Delegate CreateUnsafeCompareAndSwapDelegate() { + ResolveField(); var ft = FieldTypeWrapper.IsPrimitive ? FieldTypeWrapper.TypeAsSignatureType : typeof(object); var dm = DynamicMethodUtil.Create($"____{DeclaringType.Name.Replace(".", "_")}__{Name}", DeclaringType.TypeAsTBD, true, typeof(bool), new[] { typeof(object), ft, ft }); var il = CodeEmitter.Create(dm); diff --git a/src/IKVM.Runtime/JNI/Trampolines/ikvm.h b/src/IKVM.Runtime/JNI/Trampolines/ikvm.h index a4c2e0e699..41efe0a6ed 100644 --- a/src/IKVM.Runtime/JNI/Trampolines/ikvm.h +++ b/src/IKVM.Runtime/JNI/Trampolines/ikvm.h @@ -24,7 +24,7 @@ typedef int (*GetMethodArgs_t)(JNIEnv* pEnv, jmethodID method, char* sig); else if (sig[i] == 'I')\ argv[i].i = (jint)va_arg(args, int);\ else if (sig[i] == 'J')\ - argv[i].j = (jlong)va_arg(args, long);\ + argv[i].j = (jlong)va_arg(args, jlong);\ else if (sig[i] == 'F')\ argv[i].f = (jfloat)va_arg(args, double);\ else if (sig[i] == 'D')\ diff --git a/src/IKVM.Runtime/JVM.Properties.cs b/src/IKVM.Runtime/JVM.Properties.cs index 897c0a2513..9e2678f840 100644 --- a/src/IKVM.Runtime/JVM.Properties.cs +++ b/src/IKVM.Runtime/JVM.Properties.cs @@ -78,7 +78,7 @@ static Dictionary GetIkvmProperties() /// static string GetHomePath() { - var rootPath = Path.GetDirectoryName(BaseAssembly.Location); + var rootPath = Path.GetDirectoryName(typeof(JVM).Assembly.Location); // user value takes priority if (User.TryGetValue("ikvm.home", out var homePath1)) @@ -756,7 +756,7 @@ static IEnumerable GetIkvmHomeRids() static string GetWindowsConsoleEncoding() { var codepage = Console.InputEncoding.CodePage; - return codepage is >= 847 and <= 950 ? $"ms{codepage}" : $"cp{codepage}"; + return codepage is >= 874 and <= 950 ? $"ms{codepage}" : $"cp{codepage}"; } } diff --git a/src/IKVM.Runtime/JVM.cs b/src/IKVM.Runtime/JVM.cs index a0bf961fb7..4758ce3352 100644 --- a/src/IKVM.Runtime/JVM.cs +++ b/src/IKVM.Runtime/JVM.cs @@ -30,6 +30,8 @@ Jeroen Frijters using IKVM.Runtime.Accessors; using IKVM.Runtime.Accessors.Java.Lang; +using System.Runtime.CompilerServices; + #if IMPORTER || EXPORTER using IKVM.Reflection; using Type = IKVM.Reflection.Type; @@ -44,12 +46,12 @@ Jeroen Frijters namespace IKVM.Runtime { - static partial class JVM + internal static partial class JVM { internal const string JarClassList = "--ikvm-classes--/"; -#if !EXPORTER +#if EXPORTER == false static int emitSymbols; #endif @@ -58,13 +60,14 @@ static partial class JVM /// static Assembly baseAssembly; -#if !EXPORTER +#if EXPORTER == false internal static bool relaxedVerification = true; internal static bool AllowNonVirtualCalls; internal static readonly bool DisableEagerClassLoading = SafeGetEnvironmentVariable("IKVM_DISABLE_EAGER_CLASS_LOADING") != null; #endif -#if !IMPORTER && !EXPORTER && !FIRST_PASS + +#if FIRST_PASS == false && IMPORTER == false && EXPORTER == false readonly static object initializedLock = new object(); static bool initialized; @@ -92,11 +95,16 @@ static partial class JVM /// public static object MainThreadGroup => mainThreadGroup.Value; +#endif + /// /// Ensures the JVM is initialized. /// public static void EnsureInitialized() { +#if FIRST_PASS || IMPORTER || EXPORTER + throw new NotImplementedException(); +#else if (initialized == false) { lock (initializedLock) @@ -104,11 +112,52 @@ public static void EnsureInitialized() if (initialized == false) { initialized = true; + + // always required + RuntimeHelpers.RunClassConstructor(typeof(java.lang.String).TypeHandle); + + // initialize java_lang.System (needed before creating the thread) + RuntimeHelpers.RunClassConstructor(typeof(java.lang.System).TypeHandle); + + // initialize thread groups + RuntimeHelpers.RunClassConstructor(typeof(java.lang.ThreadGroup).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(java.lang.Thread).TypeHandle); + GC.KeepAlive(SystemThreadGroup); + GC.KeepAlive(MainThreadGroup); + + // the VM creates & returns objects of this class + RuntimeHelpers.RunClassConstructor(typeof(java.lang.Class).TypeHandle); + + // the VM preresolves methods to these classes + RuntimeHelpers.RunClassConstructor(typeof(java.lang.reflect.Method).TypeHandle); + + // ensure the System class is initialized SystemAccessor.InvokeInitializeSystemClass(); + + // should be done before System, but that fails for now + RuntimeHelpers.RunClassConstructor(typeof(java.lang.@ref.Finalizer).TypeHandle); + + // initialize certain classes after System properties are available + RuntimeHelpers.RunClassConstructor(typeof(java.lang.OutOfMemoryError).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(java.lang.NullPointerException).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(java.lang.ClassCastException).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(java.lang.ArrayStoreException).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(java.lang.ArithmeticException).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(java.lang.StackOverflowError).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(java.lang.IllegalMonitorStateException).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(java.lang.IllegalArgumentException).TypeHandle); + + // the static initializer of java.lang.Compiler tries to read property "java.compiler" and read & write property "java.vm.info" + RuntimeHelpers.RunClassConstructor(typeof(java.lang.Compiler).TypeHandle); + + // initialize some JSR292 core classes to avoid deadlock during class loading + RuntimeHelpers.RunClassConstructor(typeof(java.lang.invoke.MethodHandle).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(java.lang.invoke.MemberName).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(java.lang.invoke.MethodHandleNatives).TypeHandle); } } } - +#endif } /// @@ -117,7 +166,11 @@ public static void EnsureInitialized() /// static object MakeSystemThreadGroup() { +#if FIRST_PASS || IMPORTER || EXPORTER + throw new NotImplementedException(); +#else return ThreadGroupAccessor.Init(); +#endif } /// @@ -126,10 +179,12 @@ static object MakeSystemThreadGroup() /// static object MakeMainThreadGroup() { +#if FIRST_PASS || IMPORTER || EXPORTER + throw new NotImplementedException(); +#else return ThreadGroupAccessor.Init(null, SystemThreadGroup, "main"); - } - #endif + } /// /// Gets an environmental variable. diff --git a/src/IKVM.Runtime/Java/Externs/java/io/WinNTFileSystem.cs b/src/IKVM.Runtime/Java/Externs/java/io/WinNTFileSystem.cs index cea01b3998..c2c7773dbe 100644 --- a/src/IKVM.Runtime/Java/Externs/java/io/WinNTFileSystem.cs +++ b/src/IKVM.Runtime/Java/Externs/java/io/WinNTFileSystem.cs @@ -27,12 +27,12 @@ Jeroen Frijters using System.Linq; using System.Runtime.InteropServices; using System.Security; -using System.Text; using IKVM.Runtime.Vfs; namespace IKVM.Java.Externs.java.io { + static class WinNTFileSystem { @@ -49,13 +49,17 @@ public static string getDriveDirectory(object _this, int drive) } catch (ArgumentException) { + } catch (SecurityException) { + } catch (PathTooLongException) { + } + return "\\"; } @@ -125,29 +129,18 @@ static string CanonicalizePath(string path) return path; } - public static string canonicalize0(object _this, string path) + public static string canonicalize0(object self, string path) { #if FIRST_PASS - return null; + throw new NotImplementedException(); #else try { - // TODO there is still a known bug here. A dotted path component right after the root component - // are not removed as they should be. E.g. "c:\..." => "C:\..." or "\\server\..." => IOException - // Another know issue is that when running under Mono on Windows, the case names aren't converted - // to the correct (on file system) casing. - // - // FXBUG we're appending the directory separator to work around an apparent .NET bug. - // If we don't do this, "c:\j\." would be canonicalized to "C:\" - int colon = path.IndexOf(':', 2); - if (colon != -1) - return CanonicalizePath(path.Substring(0, colon) + Path.DirectorySeparatorChar) + path.Substring(colon); - - return CanonicalizePath(path + Path.DirectorySeparatorChar); + return CanonicalizePath(Path.IsPathRooted(path) == false ? Path.GetFullPath(path) : path); } - catch (ArgumentException x) + catch (Exception e) { - throw new global::java.io.IOException(x.Message); + throw new global::java.io.IOException(e); } #endif } @@ -160,7 +153,7 @@ public static string canonicalizeWithPrefix0(object _this, string canonicalPrefi private static string GetPathFromFile(global::java.io.File file) { #if FIRST_PASS - return null; + throw new NotImplementedException(); #else return file.getPath(); #endif @@ -196,19 +189,25 @@ public static int getBooleanAttributes(object _this, global::java.io.File f) } catch (ArgumentException) { + } catch (UnauthorizedAccessException) { + } catch (SecurityException) { + } catch (NotSupportedException) { + } catch (IOException) { + } + return 0; } @@ -288,20 +287,26 @@ public static bool checkAccess(object _this, global::java.io.File f, int access) } catch (SecurityException) { + } catch (ArgumentException) { + } catch (UnauthorizedAccessException) { + } catch (IOException) { + } catch (NotSupportedException) { + } } + return ok; } @@ -567,19 +572,25 @@ public static bool createDirectory(object _this, global::java.io.File f) } catch (SecurityException) { + } catch (ArgumentException) { + } catch (UnauthorizedAccessException) { + } catch (IOException) { + } catch (NotSupportedException) { + } + return false; } @@ -690,9 +701,8 @@ public static long getSpace0(object _this, global::java.io.File f, int t) long freeAvailable; long total; long totalFree; - StringBuilder volname = new StringBuilder(256); - if (GetVolumePathName(GetPathFromFile(f), volname, volname.Capacity) != 0 - && GetDiskFreeSpaceEx(volname.ToString(), out freeAvailable, out total, out totalFree) != 0) + var volname = new System.Text.StringBuilder(256); + if (GetVolumePathName(GetPathFromFile(f), volname, volname.Capacity) != 0 && GetDiskFreeSpaceEx(volname.ToString(), out freeAvailable, out total, out totalFree) != 0) { switch (t) { @@ -705,6 +715,7 @@ public static long getSpace0(object _this, global::java.io.File f, int t) } } #endif + return 0; } @@ -712,11 +723,13 @@ public static long getSpace0(object _this, global::java.io.File f, int t) private static extern int GetDiskFreeSpaceEx(string directory, out long freeAvailable, out long total, out long totalFree); [DllImport("kernel32")] - private static extern int GetVolumePathName(string lpszFileName, [In, Out] StringBuilder lpszVolumePathName, int cchBufferLength); + private static extern int GetVolumePathName(string lpszFileName, [In, Out] System.Text.StringBuilder lpszVolumePathName, int cchBufferLength); public static void initIDs() { + } + } } \ No newline at end of file diff --git a/src/IKVM.Runtime/Java/Externs/java/lang/System.cs b/src/IKVM.Runtime/Java/Externs/java/lang/System.cs index 33ea771b2f..8794705007 100644 --- a/src/IKVM.Runtime/Java/Externs/java/lang/System.cs +++ b/src/IKVM.Runtime/Java/Externs/java/lang/System.cs @@ -33,11 +33,7 @@ static class System /// public static void registerNatives() { -#if FIRST_PASS - throw new NotImplementedException(); -#else - JVM.EnsureInitialized(); -#endif + } /// diff --git a/src/IKVM.Runtime/Java/Externs/sun/misc/Unsafe.cs b/src/IKVM.Runtime/Java/Externs/sun/misc/Unsafe.cs index 417efe93f0..e0aa65c80f 100644 --- a/src/IKVM.Runtime/Java/Externs/sun/misc/Unsafe.cs +++ b/src/IKVM.Runtime/Java/Externs/sun/misc/Unsafe.cs @@ -196,7 +196,7 @@ public static object getObject(object self, object o, long offset) #else return o switch { - object[] array => array[offset], + object[] array => array[offset / IntPtr.Size], _ => GetField(o, offset) }; #endif @@ -217,7 +217,7 @@ public static void putObject(object self, object o, long offset, object x) switch (o) { case object[] array: - array[offset] = x; + array[offset / IntPtr.Size] = x; break; default: PutField(o, offset, x); @@ -1057,30 +1057,38 @@ public static int arrayBaseOffset(object self, global::java.lang.Class arrayClas } /// - /// Implementation of native method 'arrayIndexScale'. + /// Determines the index scale for the specified array type. /// - /// - /// + /// /// - public static int arrayIndexScale(object self, global::java.lang.Class arrayClass) + static int ArrayIndexScale(TypeWrapper tw) { - var tw = TypeWrapper.FromClass(arrayClass); - var ac = tw.TypeAsTBD; - - if (ac == typeof(byte[]) || ac == typeof(bool[])) + var et = tw.ElementTypeWrapper; + if (et == PrimitiveTypeWrapper.BYTE || et == PrimitiveTypeWrapper.BOOLEAN) return 1; - - if (ac == typeof(char[]) || ac == typeof(short[])) + else if (et == PrimitiveTypeWrapper.CHAR || et == PrimitiveTypeWrapper.SHORT) return 2; - - if (ac == typeof(int[]) || ac == typeof(float[]) || ac == typeof(object[])) + else if (et == PrimitiveTypeWrapper.INT || et == PrimitiveTypeWrapper.FLOAT) return 4; - - if (ac == typeof(long[]) || ac == typeof(double[])) + else if (et == PrimitiveTypeWrapper.LONG || et == PrimitiveTypeWrapper.DOUBLE) return 8; + else if (et.IsPrimitive == false && et.IsNonPrimitiveValueType) + return Marshal.SizeOf(et.TypeAsTBD); + else if (et.IsPrimitive == false && et.IsNonPrimitiveValueType == false) + return IntPtr.Size; + else + return 1; + } - // don't change this, the Unsafe intrinsics depend on this value - return 1; + /// + /// Implementation of native method 'arrayIndexScale'. + /// + /// + /// + /// + public static int arrayIndexScale(object self, global::java.lang.Class arrayClass) + { + return ArrayIndexScale(TypeWrapper.FromClass(arrayClass)); } /// @@ -1279,6 +1287,9 @@ static Delegate CreateGetArrayVolatileDelegate(TypeWrapper tw) il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Conv_Ovf_I); + il.Emit(OpCodes.Ldc_I4, ArrayIndexScale(tw.MakeArrayType(1))); + il.Emit(OpCodes.Div); + il.Emit(OpCodes.Conv_Ovf_I); il.Emit(OpCodes.Ldelema, tw.TypeAsLocalOrStackType); if (tw.IsWidePrimitive == false) @@ -1334,6 +1345,9 @@ static Delegate CreatePutArrayVolatileDelegate(TypeWrapper tw) il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Conv_Ovf_I); + il.Emit(OpCodes.Ldc_I4, ArrayIndexScale(tw.MakeArrayType(1))); + il.Emit(OpCodes.Div); + il.Emit(OpCodes.Conv_Ovf_I); il.Emit(OpCodes.Ldelema, tw.TypeAsLocalOrStackType); il.Emit(OpCodes.Ldarg_2); @@ -1392,7 +1406,7 @@ public static object getObjectVolatile(object self, object o, long offset) switch (o) { case object[] array when array.GetType() == typeof(object[]): - return Volatile.Read(ref array[offset]); + return Volatile.Read(ref array[offset / IntPtr.Size]); case object[] array: return GetArrayObjectVolatile(array, offset); default: @@ -1416,7 +1430,7 @@ public static void putObjectVolatile(object self, object o, long offset, object switch (o) { case object[] array when array.GetType() == typeof(object[]): - Volatile.Write(ref array[offset], x); + Volatile.Write(ref array[offset / IntPtr.Size], x); break; case object[] array: PutArrayObjectVolatile(array, offset, x); @@ -1833,7 +1847,7 @@ static Delegate CreateCompareExchangeArrayDelegate(TypeWrapper tw) static object CompareAndSwapArray(T[] o, long offset, T value, T comparand) where T : class { - return Interlocked.CompareExchange(ref o[offset], value, comparand); + return Interlocked.CompareExchange(ref o[offset / ArrayIndexScale(ClassLoaderWrapper.GetWrapperFromType(o.GetType()))], value, comparand); } /// @@ -1879,7 +1893,7 @@ public static bool compareAndSwapObject(object self, object o, long offset, obje #else return o switch { - object[] array when array.GetType() == typeof(object[]) => Interlocked.CompareExchange(ref array[offset], x, expected) == expected, + object[] array when array.GetType() == typeof(object[]) => Interlocked.CompareExchange(ref array[offset / IntPtr.Size], x, expected) == expected, object[] array => CompareAndSwapObjectArray(array, offset, x, expected) == expected, _ => CompareAndSwapField(o, offset, expected, x) }; @@ -1954,30 +1968,91 @@ public static bool compareAndSwapLong(object self, object o, long offset, long e #endif } + const int PARK_STATE_RUNNING = 0; + const int PARK_STATE_PERMIT = 1; + const int PARK_STATE_PARKED = 2; + + /// + /// Implements the native method 'unpark'. + /// + /// + /// public static void unpark(object self, object thread) { #if FIRST_PASS throw new NotImplementedException(); #else - global::java.util.concurrent.locks.LockSupport.unpark((global::java.lang.Thread)thread); + var currentThread = (global::java.lang.Thread)thread; + if (currentThread == null) + throw new global::java.lang.IllegalStateException(); + + if (currentThread != null) + { + if (Interlocked.CompareExchange(ref currentThread.parkState, PARK_STATE_PERMIT, PARK_STATE_RUNNING) == PARK_STATE_PARKED) + { + if (Interlocked.CompareExchange(ref currentThread.parkState, PARK_STATE_RUNNING, PARK_STATE_PARKED) == PARK_STATE_PARKED) + { + // initialize lock + Interlocked.CompareExchange(ref currentThread.parkLock, new global::java.lang.Object(), null); + + // thread is currently blocking, so we have to release it + lock (currentThread.parkLock) + Monitor.Pulse(currentThread.parkLock); + } + } + } #endif } + /// + /// Implements the native method 'park'. + /// + /// + /// + /// public static void park(object self, bool isAbsolute, long time) { #if FIRST_PASS throw new NotImplementedException(); #else - if (isAbsolute) - { - global::java.util.concurrent.locks.LockSupport.parkUntil(time); - } - else + var currentThread = global::java.lang.Thread.currentThread(); + if (currentThread == null) + throw new global::java.lang.IllegalStateException(); + + if (Interlocked.CompareExchange(ref currentThread.parkState, PARK_STATE_RUNNING, PARK_STATE_PERMIT) == PARK_STATE_PERMIT) + return; + + // initialize lock + Interlocked.CompareExchange(ref currentThread.parkLock, new global::java.lang.Object(), null); + + // lock thread + lock (currentThread.parkLock) { - if (time == 0) - time = global::java.lang.Long.MAX_VALUE; + if (Interlocked.CompareExchange(ref currentThread.parkState, PARK_STATE_PARKED, PARK_STATE_RUNNING) == PARK_STATE_PERMIT) + { + Interlocked.CompareExchange(ref currentThread.parkState, PARK_STATE_RUNNING, PARK_STATE_PERMIT); + return; + } + + if (isAbsolute) + { + time *= 1000000; + time -= global::java.lang.System.currentTimeMillis() * 1000000; + } + + if (time >= 0) + { + try + { + ((global::java.lang.Object)currentThread.parkLock).wait(time / 1000000, (int)(time % 1000000)); + } + catch (global::java.lang.InterruptedException _) + { + currentThread.interrupt(); + } + } - global::java.util.concurrent.locks.LockSupport.parkNanos(time); + Interlocked.CompareExchange(ref currentThread.parkState, PARK_STATE_RUNNING, PARK_STATE_PARKED); } #endif } @@ -2389,7 +2464,8 @@ static unsafe long CompareExchangeInt64(Array obj, long offset, long value, long } finally { - h.Free(); + if (h.IsAllocated) + h.Free(); } } diff --git a/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetDirectoryStream.cs b/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetDirectoryStream.cs index 1f6fc0aad1..3b50a6d60c 100644 --- a/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetDirectoryStream.cs +++ b/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetDirectoryStream.cs @@ -7,7 +7,6 @@ using IKVM.Runtime.Accessors.Java.Nio.File; using IKVM.Runtime.Accessors.Sun.Nio.Fs; - namespace IKVM.Java.Externs.sun.nio.fs { @@ -78,19 +77,22 @@ bool ApplyFilter(object i) } } - return EnumeratorIteratorAccessor.Init(files.Select(i => DotNetPathAccessor.Init(fileSystem, Path.Combine(directoryPath, i))).Where(ApplyFilter).GetEnumerator()); + return EnumeratorIteratorAccessor.Init(files.Select(i => DotNetPathAccessor.Init(fileSystem, i)).Where(ApplyFilter).GetEnumerator()); } catch (global::java.lang.Throwable) { throw; } + catch (Exception e) when (File.Exists(directoryPath)) + { + throw new global::java.nio.file.NotDirectoryException(directoryPath); + } + catch (Exception e) when (Directory.Exists(directoryPath) == false) + { + throw new global::java.nio.file.NotDirectoryException(directoryPath); + } catch (Exception e) { - if (File.Exists(directoryPath)) - throw new global::java.nio.file.NotDirectoryException(directoryPath); - if (Directory.Exists(directoryPath) == false) - throw new global::java.nio.file.NotDirectoryException(directoryPath); - throw new global::java.io.IOException(e.Message); } #endif diff --git a/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetFileSystemProvider.cs b/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetFileSystemProvider.cs index 19fb4482a4..bc332f1fba 100644 --- a/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetFileSystemProvider.cs +++ b/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetFileSystemProvider.cs @@ -1,12 +1,10 @@ using System; using System.IO; -using System.Linq.Expressions; +using System.Linq; using System.Reflection; -using System.Reflection.Emit; using System.Security; using System.Security.AccessControl; -using IKVM.Internal; using IKVM.Runtime; using IKVM.Runtime.Accessors.Java.Io; using IKVM.Runtime.Accessors.Java.Lang; @@ -231,7 +229,7 @@ public static object newDirectoryStream(object self, object dir, object filter) if (VfsTable.Default.GetEntry(path) is not VfsDirectory vfsDirectory) throw new global::java.nio.file.NotDirectoryException(path); - return DotNetDirectoryStreamAccessor.Init(dir, vfsDirectory.List(), filter); + return DotNetDirectoryStreamAccessor.Init(dir, vfsDirectory.List().Select(i => Path.Combine(path, i)), filter); } if (File.Exists(path)) diff --git a/src/IKVM.Runtime/Java/Externs/sun/reflect/Reflection.cs b/src/IKVM.Runtime/Java/Externs/sun/reflect/Reflection.cs index b355a3246b..a91f57a5f0 100644 --- a/src/IKVM.Runtime/Java/Externs/sun/reflect/Reflection.cs +++ b/src/IKVM.Runtime/Java/Externs/sun/reflect/Reflection.cs @@ -29,6 +29,7 @@ Jeroen Frijters using IKVM.Attributes; using IKVM.Internal; +using IKVM.Runtime; namespace IKVM.Java.Externs.sun.reflect { @@ -97,7 +98,7 @@ internal static bool IsHideFromStackWalk(MethodBase mb) throw new NotImplementedException(); #else if (realFramesToSkip <= 0) - return global::ikvm.@internal.ClassLiteral.Value; + return (global::java.lang.Class)ClassLiteral.Value; for (int i = 2; ;) { diff --git a/src/IKVM.Runtime/Launcher.cs b/src/IKVM.Runtime/Launcher.cs index 6ada07509e..3eed52ddc1 100644 --- a/src/IKVM.Runtime/Launcher.cs +++ b/src/IKVM.Runtime/Launcher.cs @@ -6,6 +6,7 @@ using System.Net; using System.Net.Sockets; using System.Reflection; +using System.Runtime.ExceptionServices; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -13,7 +14,10 @@ using IKVM.Attributes; using IKVM.Internal; +using IKVM.Runtime.Accessors.Ikvm.Internal; using IKVM.Runtime.Accessors.Java.Lang; +using IKVM.Runtime.Accessors.Java.Lang.Reflect; +using IKVM.Runtime.Accessors.Sun.Launcher; namespace IKVM.Runtime { @@ -22,14 +26,24 @@ namespace IKVM.Runtime /// Utility for launching a Java class from a main entry point. Parses JVM command line options, then passes the /// remainder through to the underlying Java main method. /// - static class Launcher + public static class Launcher { #if FIRST_PASS == false && IMPORTER == false && EXPORTER == false + static CallerIDAccessor callerIDAccessor; + static ClassAccessor classAccessor; + static MethodAccessor methodAccessor; static SystemAccessor systemAccessor; static ThreadAccessor threadAccessor; static ThreadGroupAccessor threadGroupAccessor; + static LauncherHelperAccessor launcherHelperAccessor; + + static CallerIDAccessor CallerIDAccessor => JVM.BaseAccessors.Get(ref callerIDAccessor); + + static ClassAccessor ClassAccessor => JVM.BaseAccessors.Get(ref classAccessor); + + static MethodAccessor MethodAccessor => JVM.BaseAccessors.Get(ref methodAccessor); static SystemAccessor SystemAccessor => JVM.BaseAccessors.Get(ref systemAccessor); @@ -37,6 +51,8 @@ static class Launcher static ThreadGroupAccessor ThreadGroupAccessor => JVM.BaseAccessors.Get(ref threadGroupAccessor); + static LauncherHelperAccessor LauncherHelperAccessor => JVM.BaseAccessors.Get(ref launcherHelperAccessor); + #endif /// @@ -115,7 +131,7 @@ static string[] Glob(string[] paths) /// Sets the startup properties. /// /// - static void SetProperties(IDictionary properties) + static void SetUserProperties(IDictionary properties) { #if FIRST_PASS || IMPORTER throw new NotImplementedException(); @@ -140,9 +156,6 @@ static void EnterMainThread() { try { - if (false) - throw new InvalidOperationException(); - Thread.CurrentThread.Name = "main"; } catch (InvalidOperationException) @@ -151,7 +164,7 @@ static void EnterMainThread() } } - JVM.EnsureInitialized(); + // first invocation of a type in the base assembly ThreadAccessor.InvokeCurrentThread(); try @@ -161,7 +174,7 @@ static void EnterMainThread() } catch (java.lang.IllegalArgumentException) { - // ignore it; + // ignore } #endif } @@ -485,7 +498,12 @@ public static int Run(string main, bool jar, string[] args, string rarg, IDictio initialize["sun.java.launcher"] = "SUN_STANDARD"; // apply the loaded VM properties - SetProperties(initialize); + SetUserProperties(initialize); + + // VM initialization, configures system properties, done before any static initializers + JVM.EnsureInitialized(); + + // first entry into base assembly EnterMainThread(); // we were instructed to show the version @@ -500,22 +518,24 @@ public static int Run(string main, bool jar, string[] args, string rarg, IDictio } // process the main argument, returning the true value, and resetting the command property to match - var clazz = sun.launcher.LauncherHelper.checkAndLoadMain(true, jar ? 2 : 1, main); + var clazz = LauncherHelperAccessor.InvokeCheckAndLoadMain(true, jar ? 2 : 1, main); SystemAccessor.InvokeSetProperty("sun.java.command", initialize["sun.java.command"]); // find the main method and ensure it is accessible - var method = clazz.getMethod("main", typeof(string[])); - method.setAccessible(true); + var method = ClassAccessor.InvokeGetMethod(clazz, "main", ClassAccessor.InitArray(ClassLoaderWrapper.GetWrapperFromType(typeof(string[])).ClassObject), CallerIDAccessor.InvokeCreate(typeof(Launcher).TypeHandle)); + MethodAccessor.InvokeSetAccessible(method, true); try { // invoke main method, which is responsible for exit - method.invoke(null, new object[] { appArgs.ToArray() }); + MethodAccessor.InvokeInvoke(method, null, new[] { appArgs.ToArray() }); return 0; } catch (java.lang.reflect.InvocationTargetException e) { - throw e.getCause(); + // we want to unwrap the cause to report to the user + ExceptionDispatchInfo.Capture(e.getCause()).Throw(); + throw null; } } catch (Exception e) @@ -593,7 +613,7 @@ static void HandleDebugTrace() /// /// Prints out the standard launcher help information. /// - public static void PrintHelp() + static void PrintHelp() { var exe = Process.GetCurrentProcess().ProcessName; Console.Error.WriteLine("Usage: {0} [-options] class [args...]", exe); @@ -638,7 +658,7 @@ public static void PrintHelp() /// /// Prints out the extended launcher help information. /// - public static void PrintXHelp() + static void PrintXHelp() { Console.Error.WriteLine(" -Xnoclassgc disable class garbage collection"); Console.Error.WriteLine(" -Xtime time the execution"); diff --git a/src/IKVM.Runtime/RuntimeHelperTypes.cs b/src/IKVM.Runtime/RuntimeHelperTypes.cs index c9cc00d97b..e69de29bb2 100644 --- a/src/IKVM.Runtime/RuntimeHelperTypes.cs +++ b/src/IKVM.Runtime/RuntimeHelperTypes.cs @@ -1,105 +0,0 @@ -/* - Copyright (C) 2009 Jeroen Frijters - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - Jeroen Frijters - jeroen@frijters.net - -*/ -using System; - -using IKVM.Runtime; - -#if IMPORTER -using IKVM.Reflection; -using IKVM.Reflection.Emit; -using IKVM.Tools.Importer; - -using Type = IKVM.Reflection.Type; - -#else -using System.Reflection; -using System.Reflection.Emit; -#endif - -using System.Diagnostics; - -namespace IKVM.Internal -{ - - static class RuntimeHelperTypes - { - - private static Type classLiteralType; - private static FieldInfo classLiteralField; - - internal static FieldInfo GetClassLiteralField(Type type) - { - Debug.Assert(type != Types.Void); - if (classLiteralType == null) - { -#if IMPORTER - classLiteralType = JVM.BaseAssembly.GetType("ikvm.internal.ClassLiteral`1"); -#elif !FIRST_PASS - classLiteralType = typeof(ikvm.@internal.ClassLiteral<>); -#endif - } -#if !IMPORTER - if (!IsTypeBuilder(type)) - { - return classLiteralType.MakeGenericType(type).GetField("Value", BindingFlags.Public | BindingFlags.Static); - } -#endif - if (classLiteralField == null) - { - classLiteralField = classLiteralType.GetField("Value", BindingFlags.Public | BindingFlags.Static); - } - return TypeBuilder.GetField(classLiteralType.MakeGenericType(type), classLiteralField); - } - - private static bool IsTypeBuilder(Type type) - { - return type is TypeBuilder || (type.HasElementType && IsTypeBuilder(type.GetElementType())); - } - -#if IMPORTER - internal static void Create(CompilerClassLoader ccl) - { - EmitClassLiteral(ccl); - } - - private static void EmitClassLiteral(CompilerClassLoader ccl) - { - TypeBuilder tb = ccl.GetTypeWrapperFactory().ModuleBuilder.DefineType("ikvm.internal.ClassLiteral`1", TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract | TypeAttributes.Class | TypeAttributes.BeforeFieldInit); - GenericTypeParameterBuilder typeParam = tb.DefineGenericParameters("T")[0]; - Type classType = CoreClasses.java.lang.Class.Wrapper.TypeAsSignatureType; - classLiteralField = tb.DefineField("Value", classType, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly); - CodeEmitter ilgen = CodeEmitter.Create(ReflectUtil.DefineTypeInitializer(tb, ccl)); - ilgen.Emit(OpCodes.Ldtoken, typeParam); - ilgen.Emit(OpCodes.Call, Types.Type.GetMethod("GetTypeFromHandle", new Type[] { Types.RuntimeTypeHandle })); - MethodWrapper mw = CoreClasses.java.lang.Class.Wrapper.GetMethodWrapper("", "(Lcli.System.Type;)V", false); - mw.Link(); - mw.EmitNewobj(ilgen); - ilgen.Emit(OpCodes.Stsfld, classLiteralField); - ilgen.Emit(OpCodes.Ret); - ilgen.DoEmit(); - classLiteralType = tb.CreateType(); - } -#endif - } -} diff --git a/src/IKVM.Runtime/TypeWrapper.cs b/src/IKVM.Runtime/TypeWrapper.cs index 08225a5ccd..8b5c8343c0 100644 --- a/src/IKVM.Runtime/TypeWrapper.cs +++ b/src/IKVM.Runtime/TypeWrapper.cs @@ -113,7 +113,13 @@ internal void EmitClassLiteral(CodeEmitter ilgen) } else { - ilgen.Emit(OpCodes.Ldsfld, RuntimeHelperTypes.GetClassLiteralField(type)); +#if IMPORTER + var classLiteralType = StaticCompiler.GetRuntimeType("IKVM.Runtime.ClassLiteral`1").MakeGenericType(type); +#else + var classLiteralType = typeof(ClassLiteral<>).MakeGenericType(type); +#endif + + ilgen.Emit(OpCodes.Call, classLiteralType.GetProperty("Value").GetMethod); } } @@ -168,6 +174,7 @@ internal java.lang.Class ClassObject get { Debug.Assert(!IsUnloadable && !IsVerifierType); + if (classObject == null) LazyInitClass(); @@ -253,7 +260,7 @@ private void LazyInitClass() } else { - clazz = (java.lang.Class)typeof(ikvm.@internal.ClassLiteral<>).MakeGenericType(type).GetField("Value").GetValue(null); + clazz = (java.lang.Class)typeof(ClassLiteral<>).MakeGenericType(type).GetProperty("Value").GetGetMethod().Invoke(null, Array.Empty()); } } #if __MonoCS__ diff --git a/src/IKVM.Runtime/intrinsics.cs b/src/IKVM.Runtime/intrinsics.cs index 356da57f22..f012292cd8 100644 --- a/src/IKVM.Runtime/intrinsics.cs +++ b/src/IKVM.Runtime/intrinsics.cs @@ -218,6 +218,31 @@ static Dictionary Register() return intrinsics; } + /// + /// Emits IL that pushes the index scale for the specified array type onto the stack. + /// + /// + /// + /// + static void EmitArrayIndexScale(EmitIntrinsicContext eic, TypeWrapper tw) + { + var et = tw.ElementTypeWrapper; + if (et == PrimitiveTypeWrapper.BYTE || et == PrimitiveTypeWrapper.BOOLEAN) + eic.Emitter.EmitLdc_I4(1); + else if (et == PrimitiveTypeWrapper.CHAR || et == PrimitiveTypeWrapper.SHORT) + eic.Emitter.EmitLdc_I4(2); + else if (et == PrimitiveTypeWrapper.INT || et == PrimitiveTypeWrapper.FLOAT) + eic.Emitter.EmitLdc_I4(4); + else if (et == PrimitiveTypeWrapper.LONG || et == PrimitiveTypeWrapper.DOUBLE) + eic.Emitter.EmitLdc_I4(8); + else if (et.IsPrimitive == false && et.IsNonPrimitiveValueType) + eic.Emitter.Emit(OpCodes.Sizeof, et.TypeAsArrayType); + else if (et.IsPrimitive == false && et.IsNonPrimitiveValueType == false) + eic.Emitter.Emit(OpCodes.Sizeof, Types.IntPtr); + else + eic.Emitter.EmitLdc_I4(1); + } + internal static bool IsIntrinsic(MethodWrapper mw) { return intrinsics.ContainsKey(new IntrinsicKey(mw)) && mw.DeclaringType.GetClassLoader() == CoreClasses.java.lang.Object.Wrapper.GetClassLoader(); @@ -543,21 +568,42 @@ internal static bool IsSupportedArrayTypeForUnsafeOperation(TypeWrapper tw) return tw.IsArray && !tw.IsGhostArray && !tw.ElementTypeWrapper.IsPrimitive && !tw.ElementTypeWrapper.IsNonPrimitiveValueType; } + /// + /// Attempts to replace an invocation of Unsafe.putObject with an intrinsic implementation. + /// + /// + /// static bool Unsafe_putObject(EmitIntrinsicContext eic) { return Unsafe_putObjectImpl(eic, false); } + /// + /// Attempts to replace an invocation of Unsafe.putOrderedObject with an intrinsic implementation. + /// + /// + /// static bool Unsafe_putOrderedObject(EmitIntrinsicContext eic) { return Unsafe_putObjectImpl(eic, false); } + /// + /// Attempts to replace an invocation of Unsafe.putObjectVolatile with an intrinsic implementation. + /// + /// + /// static bool Unsafe_putObjectVolatile(EmitIntrinsicContext eic) { return Unsafe_putObjectImpl(eic, true); } + /// + /// Attempts to replace an invocation of an Unsafe put operation with an intrinsic implementation. + /// + /// + /// + /// static bool Unsafe_putObjectImpl(EmitIntrinsicContext eic, bool isVolatile) { var tw = eic.GetStackTypeWrapper(0, 2); @@ -576,8 +622,11 @@ static bool Unsafe_putObjectImpl(EmitIntrinsicContext eic, bool isVolatile) eic.Emitter.Emit(OpCodes.Stloc, array); EmitConsumeUnsafe(eic); + // emit new call that sets the element by index eic.Emitter.Emit(OpCodes.Ldloc, array); eic.Emitter.Emit(OpCodes.Ldloc, index); + EmitArrayIndexScale(eic, tw); + eic.Emitter.Emit(OpCodes.Div); eic.Emitter.Emit(OpCodes.Ldloc, value); eic.Emitter.Emit(OpCodes.Stelem_Ref); @@ -659,8 +708,11 @@ static bool Unsafe_getObjectVolatile(EmitIntrinsicContext eic) eic.Emitter.Emit(OpCodes.Stloc, target); EmitConsumeUnsafe(eic); + // emit new call that gets the element by index eic.Emitter.Emit(OpCodes.Ldloc, target); eic.Emitter.Emit(OpCodes.Ldloc, offset); + EmitArrayIndexScale(eic, tw); + eic.Emitter.Emit(OpCodes.Div); eic.Emitter.Emit(OpCodes.Ldelema, tw.TypeAsLocalOrStackType.GetElementType()); eic.Emitter.Emit(OpCodes.Volatile); eic.Emitter.Emit(OpCodes.Ldind_Ref); @@ -711,6 +763,8 @@ static bool Unsafe_compareAndSwapObject(EmitIntrinsicContext eic) // emit new call site eic.Emitter.Emit(OpCodes.Ldloc, target); eic.Emitter.Emit(OpCodes.Ldloc, offset); + EmitArrayIndexScale(eic, tw); + eic.Emitter.Emit(OpCodes.Div); eic.Emitter.Emit(OpCodes.Ldelema, type); eic.Emitter.Emit(OpCodes.Ldloc, update); eic.Emitter.Emit(OpCodes.Ldloc, expect); @@ -769,6 +823,7 @@ static bool Unsafe_compareAndSwapObject(EmitIntrinsicContext eic) eic.Emitter.Emit(OpCodes.Call, AtomicReferenceFieldUpdaterEmitter.MakeCompareExchange(type)); eic.Emitter.Emit(OpCodes.Ldloc, expect); eic.Emitter.Emit(OpCodes.Ceq); + eic.Emitter.ReleaseTempLocal(expect); eic.Emitter.ReleaseTempLocal(update); eic.NonLeaf = false; @@ -820,6 +875,7 @@ static bool Unsafe_compareAndSwapObject(EmitIntrinsicContext eic) eic.Emitter.Emit(OpCodes.Ldloc, update); fw.EmitUnsafeCompareAndSwap(eic.Emitter); + eic.Emitter.ReleaseTempLocal(target); eic.Emitter.ReleaseTempLocal(expect); eic.Emitter.ReleaseTempLocal(update); eic.NonLeaf = false; @@ -857,6 +913,8 @@ static bool Unsafe_getAndSetObject(EmitIntrinsicContext eic) // emit new call eic.Emitter.Emit(OpCodes.Ldloc, target); eic.Emitter.Emit(OpCodes.Ldloc, offset); + EmitArrayIndexScale(eic, tw); + eic.Emitter.Emit(OpCodes.Div); eic.Emitter.Emit(OpCodes.Ldelema, type); eic.Emitter.Emit(OpCodes.Ldloc, newValue); eic.Emitter.Emit(OpCodes.Call, MakeExchange(type)); @@ -920,6 +978,7 @@ static bool Unsafe_compareAndSwapInt(EmitIntrinsicContext eic) eic.Emitter.Emit(OpCodes.Ldloc, update); fw.EmitUnsafeCompareAndSwap(eic.Emitter); + eic.Emitter.ReleaseTempLocal(target); eic.Emitter.ReleaseTempLocal(expect); eic.Emitter.ReleaseTempLocal(update); eic.NonLeaf = false; diff --git a/src/IKVM.Tests.Java/java/lang/ClassLoaderTests.java b/src/IKVM.Tests.Java/java/lang/ClassLoaderTests.java deleted file mode 100644 index 0722252e0a..0000000000 --- a/src/IKVM.Tests.Java/java/lang/ClassLoaderTests.java +++ /dev/null @@ -1,59 +0,0 @@ -package ikvm.tests.java.java.lang; - -import java.net.*; - -@cli.Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute.Annotation() -public class ClassLoaderTests { - - static class Foo extends ClassLoader { - @Override - protected void finalize() { - System.out.println(""); - } - } - - static class Bar extends Foo { - @Override - protected void finalize() { - super.finalize(); - System.out.println(""); - } - } - - static class ShouldPreventUninitializedParentClass { - static ClassLoader loader; - - public static void run() throws Exception { - try { - new ClassLoader(null) { - @Override - protected void finalize() { - loader = this; - } - }; - } catch (SecurityException exc) { - - } - - System.gc(); - System.runFinalization(); - - if (loader != null) { - try { - URLClassLoader child = URLClassLoader.newInstance(new URL[0], loader); - throw new RuntimeException("child class loader created"); - } catch (SecurityException se) { - // test passed - } - } else { - // test passed - } - } - } - - @cli.Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute.Annotation() - public void shouldPreventUninitializedParent() throws Exception { - ShouldPreventUninitializedParentClass.run(); - } - -} diff --git a/src/IKVM.Tests.Java/java/nio/channels/AsynchronousServerSocketChannelTests.java b/src/IKVM.Tests.Java/java/nio/channels/AsynchronousServerSocketChannelTests.java deleted file mode 100644 index 460fe18d80..0000000000 --- a/src/IKVM.Tests.Java/java/nio/channels/AsynchronousServerSocketChannelTests.java +++ /dev/null @@ -1,162 +0,0 @@ -package ikvm.tests.java.java.nio.channels; - -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Random; - -@cli.Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute.Annotation() -public class AsynchronousServerSocketChannelTests { - - static final Random rand = new Random(); - - @cli.Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute.Annotation() - public void canReadAndWriteStreams() throws Exception { - // establish loopback connection - AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(0)); - int port = ((InetSocketAddress)(listener.getLocalAddress())).getPort(); - InetSocketAddress isa = new InetSocketAddress(InetAddress.getLocalHost(), port); - AsynchronousSocketChannel ch1 = AsynchronousSocketChannel.open(); - ch1.connect(isa).get(); - AsynchronousSocketChannel ch2 = listener.accept().get(); - - // start thread to write to stream - Writer writer = new Writer(Channels.newOutputStream(ch1)); - Thread writerThread = new Thread(writer); - writerThread.start(); - - // start thread to read from stream - Reader reader = new Reader(Channels.newInputStream(ch2)); - Thread readerThread = new Thread(reader); - readerThread.start(); - - // wait for threads to complete - writerThread.join(); - readerThread.join(); - - // shutdown listener - listener.close(); - - // check that reader received what we expected - if (reader.total() != writer.total()) - throw new RuntimeException("Unexpected number of bytes read"); - if (reader.hash() != writer.hash()) - throw new RuntimeException("Hash incorrect for bytes read"); - - // channels should be closed - if (ch1.isOpen() || ch2.isOpen()) - throw new RuntimeException("Channels should be closed"); - } - - static class Reader implements Runnable { - - private final InputStream in; - private volatile int total; - private volatile int hash; - - Reader(InputStream in) { - this.in = in; - } - - public void run() { - try { - int n; - do { - // random offset/len - byte[] buf = new byte[128 + rand.nextInt(128)]; - int len, off; - if (rand.nextBoolean()) { - len = buf.length; - off = 0; - n = in.read(buf); - } else { - len = 1 + rand.nextInt(64); - off = rand.nextInt(64); - n = in.read(buf, off, len); - } - if (n > len) - throw new RuntimeException("Too many bytes read"); - if (n > 0) { - total += n; - for (int i=0; i 0); - in.close(); - } catch (IOException x) { - x.printStackTrace(); - } - } - - int total() { - return total; - } - - int hash() { - return hash; - } - - } - - static class Writer implements Runnable { - - private final OutputStream out; - private final int total; - private volatile int hash; - - Writer(OutputStream out) { - this.out = out; - this.total = 50*1000 + rand.nextInt(50*1000); - } - - public void run() { - hash = 0; - int rem = total; - try { - do { - byte[] buf = new byte[1 + rand.nextInt(rem)]; - int off, len; - - // write random bytes - if (rand.nextBoolean()) { - off = 0; - len = buf.length; - } else { - off = rand.nextInt(buf.length); - int r = buf.length - off; - len = (r <= 1) ? 1 : (1 + rand.nextInt(r)); - } - for (int i=0; i 0); - - // close stream when done - out.close(); - - } catch (IOException x) { - x.printStackTrace(); - } - } - - int total() { - return total; - } - - int hash() { - return hash; - } - - } - -} diff --git a/src/IKVM.Tests/IKVM.Tests.csproj b/src/IKVM.Tests/IKVM.Tests.csproj index 9765a62d2f..251d8365a9 100644 --- a/src/IKVM.Tests/IKVM.Tests.csproj +++ b/src/IKVM.Tests/IKVM.Tests.csproj @@ -21,7 +21,7 @@ - + PreserveNewest diff --git a/src/IKVM.Tests/Java/ikvm/runtime/AssemblyClassLoaderTests.cs b/src/IKVM.Tests/Java/ikvm/runtime/AssemblyClassLoaderTests.cs index a5093a7f16..4b2856bcda 100644 --- a/src/IKVM.Tests/Java/ikvm/runtime/AssemblyClassLoaderTests.cs +++ b/src/IKVM.Tests/Java/ikvm/runtime/AssemblyClassLoaderTests.cs @@ -52,7 +52,7 @@ public async Task Setup() { Runtime = Path.Combine("lib", tfm, "IKVM.Runtime.dll"), ResponseFile = $"{n}_ikvmc.rsp", - Input = { Path.Combine("ext", "helloworld-2.0.jar") }, + Input = { Path.Combine("helloworld", "helloworld-2.0.jar") }, Assembly = $"helloworld_{n}", Version = "1.0.0.0", NoStdLib = true, diff --git a/src/IKVM.Tests/Java/java/io/FileTests.cs b/src/IKVM.Tests/Java/java/io/FileTests.cs index c5fc5f3dbc..cbd4c6fb25 100644 --- a/src/IKVM.Tests/Java/java/io/FileTests.cs +++ b/src/IKVM.Tests/Java/java/io/FileTests.cs @@ -12,6 +12,12 @@ namespace IKVM.Tests.Java.java.io public class FileTests { + [TestMethod] + public void CanConvertRelativePathToRealPath() + { + (new global::java.io.File(".")).toPath().toRealPath(); + } + [TestMethod] public void CanCreateFile() { diff --git a/src/IKVM.Tests/Java/java/nio/charset/CharsetTests.cs b/src/IKVM.Tests/Java/java/nio/charset/CharsetTests.cs index 0b3b43329d..a5a739d907 100644 --- a/src/IKVM.Tests/Java/java/nio/charset/CharsetTests.cs +++ b/src/IKVM.Tests/Java/java/nio/charset/CharsetTests.cs @@ -1,4 +1,6 @@ -using java.nio.charset; +using FluentAssertions; + +using java.nio.charset; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -9,12 +11,38 @@ namespace IKVM.Tests.Java.java.nio.charset public class CharsetTests { + [TestMethod] + public void CanGetDefaultCharsets() + { + Charset.defaultCharset(); + } + [TestMethod] public void CanGetAvailableCharsets() { Charset.availableCharsets(); } + [DataTestMethod] + [DataRow("cp437")] + [DataRow("cp850")] + [DataRow("cp852")] + [DataRow("cp855")] + [DataRow("cp857")] + [DataRow("cp860")] + [DataRow("cp861")] + [DataRow("cp863")] + [DataRow("cp865")] + [DataRow("cp866")] + [DataRow("cp869")] + [DataRow("cp936")] + public void CanEncodeAndDecode(string charset) + { + var o = Charset.forName(charset).encode("test"); + var i = Charset.forName(charset).decode(o); + i.toString().Should().Be("test"); + } + } } diff --git a/src/IKVM.Tests/Java/java/nio/file/DirectoryStreamTests.cs b/src/IKVM.Tests/Java/java/nio/file/DirectoryStreamTests.cs new file mode 100644 index 0000000000..4cd2120ced --- /dev/null +++ b/src/IKVM.Tests/Java/java/nio/file/DirectoryStreamTests.cs @@ -0,0 +1,63 @@ +using System; + +using FluentAssertions; + +using java.io; +using java.nio.file; +using java.util; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.Tests.Java.java.nio.file +{ + + [TestClass] + public class DirectoryStreamTests + { + + [TestMethod] + public void ShouldListFilesAtAbsolutePath() + { + var d = Files.createTempDirectory("test"); + d.toFile().deleteOnExit(); + + var f1 = new File(d.toFile(), "test1.txt"); + f1.createNewFile(); + var f2 = new File(d.toFile(), "test2.txt"); + f2.createNewFile(); + var f3 = new File(d.toFile(), "test3.txt"); + f3.createNewFile(); + + using var stream = Files.newDirectoryStream(d); + var l = stream.iterator().RemainingToList(); + l.Should().HaveCount(3); + l.Should().Contain(i => i.ToString() == f1.ToString()); + l.Should().Contain(i => i.ToString() == f2.ToString()); + l.Should().Contain(i => i.ToString() == f3.ToString()); + } + + [TestMethod] + public void ShouldListFilesAtRelativePath() + { + var n = Guid.NewGuid().ToString("n"); + var d = Files.createDirectory(Paths.get(n)); + d.toFile().deleteOnExit(); + + var f1 = new File(d.toFile(), "test1.txt"); + f1.createNewFile(); + var f2 = new File(d.toFile(), "test2.txt"); + f2.createNewFile(); + var f3 = new File(d.toFile(), "test3.txt"); + f3.createNewFile(); + + using var stream = Files.newDirectoryStream(d); + var l = stream.iterator().RemainingToList(); + l.Should().HaveCount(3); + l.Should().Contain(i => i.ToString() == f1.ToString()); + l.Should().Contain(i => i.ToString() == f2.ToString()); + l.Should().Contain(i => i.ToString() == f3.ToString()); + } + + } + +} diff --git a/src/IKVM.Tests/Java/java/util/concurrent/ForkJoinPoolTests.cs b/src/IKVM.Tests/Java/java/util/concurrent/ForkJoinPoolTests.cs new file mode 100644 index 0000000000..b7c70d76f4 --- /dev/null +++ b/src/IKVM.Tests/Java/java/util/concurrent/ForkJoinPoolTests.cs @@ -0,0 +1,14 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.Tests.Java.java.util.concurrent +{ + + [TestClass] + public class ForkJoinPoolTests + { + + + + } + +} diff --git a/src/IKVM.Tests/Java/java/util/concurrent/atomic/AtomicReferenceArrayTests.cs b/src/IKVM.Tests/Java/java/util/concurrent/atomic/AtomicReferenceArrayTests.cs new file mode 100644 index 0000000000..2d42575176 --- /dev/null +++ b/src/IKVM.Tests/Java/java/util/concurrent/atomic/AtomicReferenceArrayTests.cs @@ -0,0 +1,44 @@ +using FluentAssertions; + +using java.util.concurrent.atomic; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.Tests.Java.java.util.concurrent.atomic +{ + + [TestClass] + public class AtomicReferenceArrayTests + { + + [TestMethod] + public void CanLazySetArray() + { + var ao = new AtomicReferenceArray(32); + for (int idx = 0; idx < ao.length(); ++idx) + ao.lazySet(idx, new object()); + for (int idx = 0; idx < ao.length(); ++idx) + ao.get(idx).Should().NotBeNull(); + for (int idx = 0; idx < ao.length(); ++idx) + ao.lazySet(idx, null); + for (int idx = 0; idx < ao.length(); ++idx) + ao.get(idx).Should().BeNull(); + } + + [TestMethod] + public void CanSetArray() + { + var ao = new AtomicReferenceArray(32); + for (int idx = 0; idx < ao.length(); ++idx) + ao.set(idx, new object()); + for (int idx = 0; idx < ao.length(); ++idx) + ao.get(idx).Should().NotBeNull(); + for (int idx = 0; idx < ao.length(); ++idx) + ao.set(idx, null); + for (int idx = 0; idx < ao.length(); ++idx) + ao.get(idx).Should().BeNull(); + } + + } + +} diff --git a/src/IKVM.Tests/Java/java/util/zip/ZipTests.cs b/src/IKVM.Tests/Java/java/util/zip/ZipTests.cs index 04d913e1a2..e3c98a4776 100644 --- a/src/IKVM.Tests/Java/java/util/zip/ZipTests.cs +++ b/src/IKVM.Tests/Java/java/util/zip/ZipTests.cs @@ -12,7 +12,6 @@ public class ZipTests [TestMethod] public void TotalInOut() { - const int BUF_SIZE = 1 * 1024 * 1024; long dataSize = 128L * 1024L * 1024L; // 128MB diff --git a/src/IKVM.Tests/Java/sun/misc/UnsafeTests.cs b/src/IKVM.Tests/Java/sun/misc/UnsafeTests.cs index 76a0fea3cf..07c52c9004 100644 --- a/src/IKVM.Tests/Java/sun/misc/UnsafeTests.cs +++ b/src/IKVM.Tests/Java/sun/misc/UnsafeTests.cs @@ -947,17 +947,18 @@ public void CanSetObjectInArray() var o1 = new object(); var o2 = new object(); var a = new object[16]; - u.putObject(a, 0L, o1); - u.getObject(a, 0L).Should().BeSameAs(o1); + var s = u.arrayIndexScale(typeof(object[])); + u.putObject(a, s * 0L, o1); + u.getObject(a, s * 0L).Should().BeSameAs(o1); a[0].Should().BeSameAs(o1); - u.putObject(a, 0L, null); - u.getObject(a, 0L).Should().BeSameAs(null); + u.putObject(a, s * 0L, null); + u.getObject(a, s * 0L).Should().BeSameAs(null); a[0].Should().BeNull(); - u.putObject(a, 1L, o2); - u.getObject(a, 1L).Should().BeSameAs(o2); + u.putObject(a, s * 1L, o2); + u.getObject(a, s * 1L).Should().BeSameAs(o2); a[1].Should().BeSameAs(o2); - u.putObject(a, 1L, null); - u.getObject(a, 1L).Should().BeSameAs(null); + u.putObject(a, s * 1L, null); + u.getObject(a, s * 1L).Should().BeSameAs(null); a[1].Should().BeNull(); } @@ -967,17 +968,18 @@ public void CanSetObjectInArrayVolatile() var o1 = new object(); var o2 = new object(); var a = new object[16]; - u.putObjectVolatile(a, 0L, o1); - u.getObjectVolatile(a, 0L).Should().BeSameAs(o1); + var s = u.arrayIndexScale(typeof(object[])); + u.putObjectVolatile(a, s * 0L, o1); + u.getObjectVolatile(a, s * 0L).Should().BeSameAs(o1); a[0].Should().BeSameAs(o1); - u.putObjectVolatile(a, 0L, null); - u.getObjectVolatile(a, 0L).Should().BeSameAs(null); + u.putObjectVolatile(a, s * 0L, null); + u.getObjectVolatile(a, s * 0L).Should().BeSameAs(null); a[0].Should().BeNull(); - u.putObjectVolatile(a, 1L, o2); - u.getObjectVolatile(a, 1L).Should().BeSameAs(o2); + u.putObjectVolatile(a, s * 1L, o2); + u.getObjectVolatile(a, s * 1L).Should().BeSameAs(o2); a[1].Should().BeSameAs(o2); - u.putObjectVolatile(a, 1L, null); - u.getObjectVolatile(a, 1L).Should().BeSameAs(null); + u.putObjectVolatile(a, s * 1L, null); + u.getObjectVolatile(a, s * 1L).Should().BeSameAs(null); a[1].Should().BeNull(); } @@ -994,17 +996,18 @@ public void CanSetObjectInTypedArray() var o1 = new AnonymousTestObject(); var o2 = new AnonymousTestObject(); var a = new AnonymousTestObject[16]; - u.putObject(a, 0L, o1); - u.getObject(a, 0L).Should().BeSameAs(o1); + var s = u.arrayIndexScale(typeof(AnonymousTestObject[])); + u.putObject(a, s * 0L, o1); + u.getObject(a, s * 0L).Should().BeSameAs(o1); a[0].Should().BeSameAs(o1); - u.putObject(a, 0L, null); - u.getObject(a, 0L).Should().BeSameAs(null); + u.putObject(a, s * 0L, null); + u.getObject(a, s * 0L).Should().BeSameAs(null); a[0].Should().BeNull(); - u.putObject(a, 1L, o2); - u.getObject(a, 1L).Should().BeSameAs(o2); + u.putObject(a, s * 1L, o2); + u.getObject(a, s * 1L).Should().BeSameAs(o2); a[1].Should().BeSameAs(o2); - u.putObject(a, 1L, null); - u.getObject(a, 1L).Should().BeSameAs(null); + u.putObject(a, s * 1L, null); + u.getObject(a, s * 1L).Should().BeSameAs(null); a[1].Should().BeNull(); } @@ -1014,17 +1017,18 @@ public void CanSetObjectInTypedArrayVolatile() var o1 = new AnonymousTestObject(); var o2 = new AnonymousTestObject(); var a = new AnonymousTestObject[16]; - u.putObjectVolatile(a, 0L, o1); - u.getObjectVolatile(a, 0L).Should().BeSameAs(o1); + var s = u.arrayIndexScale(typeof(AnonymousTestObject[])); + u.putObjectVolatile(a, s * 0L, o1); + u.getObjectVolatile(a, s * 0L).Should().BeSameAs(o1); a[0].Should().BeSameAs(o1); - u.putObjectVolatile(a, 0L, null); - u.getObjectVolatile(a, 0L).Should().BeSameAs(null); + u.putObjectVolatile(a, s * 0L, null); + u.getObjectVolatile(a, s * 0L).Should().BeSameAs(null); a[0].Should().BeNull(); - u.putObjectVolatile(a, 1L, o2); - u.getObjectVolatile(a, 1L).Should().BeSameAs(o2); + u.putObjectVolatile(a, s * 1L, o2); + u.getObjectVolatile(a, s * 1L).Should().BeSameAs(o2); a[1].Should().BeSameAs(o2); - u.putObjectVolatile(a, 1L, null); - u.getObjectVolatile(a, 1L).Should().BeSameAs(null); + u.putObjectVolatile(a, s * 1L, null); + u.getObjectVolatile(a, s * 1L).Should().BeSameAs(null); a[1].Should().BeNull(); } @@ -1391,14 +1395,16 @@ public void CanCompareAndSwapObjectInArray() var a = new object[4]; for (int i = 0; i < 4; i++) { + var of = i * u.arrayIndexScale(typeof(object[])); + var o1 = new object(); var o2 = new object(); - u.compareAndSwapObject(a, i, null, o1).Should().BeTrue(); + u.compareAndSwapObject(a, of, null, o1).Should().BeTrue(); a[i].Should().BeSameAs(o1); - u.compareAndSwapObject(a, i, o1, o2).Should().BeTrue(); + u.compareAndSwapObject(a, of, o1, o2).Should().BeTrue(); a[i].Should().BeSameAs(o2); - u.compareAndSwapObject(a, i, o1, o2).Should().BeFalse(); + u.compareAndSwapObject(a, of, o1, o2).Should().BeFalse(); a[i].Should().BeSameAs(o2); } } @@ -1409,14 +1415,16 @@ public void CanCompareAndSwapStringInArray() var a = new string[4]; for (int i = 0; i < 4; i++) { + var of = i * u.arrayIndexScale(typeof(string[])); + var o1 = "TEST1"; var o2 = "TEST2"; - u.compareAndSwapObject(a, i, null, o1).Should().BeTrue(); + u.compareAndSwapObject(a, of, null, o1).Should().BeTrue(); a[i].Should().BeSameAs(o1); - u.compareAndSwapObject(a, i, o1, o2).Should().BeTrue(); + u.compareAndSwapObject(a, of, o1, o2).Should().BeTrue(); a[i].Should().BeSameAs(o2); - u.compareAndSwapObject(a, i, o1, o2).Should().BeFalse(); + u.compareAndSwapObject(a, of, o1, o2).Should().BeFalse(); a[i].Should().BeSameAs(o2); } } diff --git a/src/IKVM.Tests/Runtime/Accessors/Java/Lang/ClassAccessorTests.cs b/src/IKVM.Tests/Runtime/Accessors/Java/Lang/ClassAccessorTests.cs new file mode 100644 index 0000000000..adb36f7d27 --- /dev/null +++ b/src/IKVM.Tests/Runtime/Accessors/Java/Lang/ClassAccessorTests.cs @@ -0,0 +1,33 @@ +using FluentAssertions; + +using IKVM.Runtime.Accessors; +using IKVM.Runtime.Accessors.Ikvm.Internal; +using IKVM.Runtime.Accessors.Java.Lang; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.Tests.Runtime.Accessors.Java.Lang +{ + + [TestClass] + public class ClassAccessorTests + { + + [TestMethod] + public void CanInvokeGetMethod() + { + AccessorRef callerIDAccessorRef = new(); + AccessorRef classAccessorRef = new(); + + var callerID = callerIDAccessorRef.Value.InvokeCreate(typeof(ClassAccessorTests).TypeHandle); + + var c = classAccessorRef.Value.InvokeForName("java.lang.Object", callerID); + c.Should().NotBeNull(); + + var m = classAccessorRef.Value.InvokeGetMethod(c, "toString", classAccessorRef.Value.InitArray(0), callerID); + m.Should().NotBeNull(); + } + + } + +} diff --git a/src/IKVM.Tests/Util/Jar/JarFileTests.cs b/src/IKVM.Tests/Util/Jar/JarFileTests.cs index e987022446..da664281f2 100644 --- a/src/IKVM.Tests/Util/Jar/JarFileTests.cs +++ b/src/IKVM.Tests/Util/Jar/JarFileTests.cs @@ -16,14 +16,14 @@ public class JarFileTests [TestMethod] public void CanReadManifestVersion() { - var z = new JarFile(Path.Combine("ext","helloworld-2.0.jar")); + var z = new JarFile(Path.Combine("helloworld", "helloworld-2.0.jar")); z.Manifest.MainAttributes.Should().Contain("Manifest-Version", "1.0"); } [TestMethod] public void CanReadModuleName() { - var z = new JarFile(Path.Combine("ext", "helloworld-mod.jar")); + var z = new JarFile(Path.Combine("helloworld", "helloworld-mod.jar")); z.GetModuleInfo().Name.Should().Be("helloworld"); } diff --git a/src/IKVM.Tools.Importer/CompilerClassLoader.cs b/src/IKVM.Tools.Importer/CompilerClassLoader.cs index 6122369687..5ba606b77d 100644 --- a/src/IKVM.Tools.Importer/CompilerClassLoader.cs +++ b/src/IKVM.Tools.Importer/CompilerClassLoader.cs @@ -484,29 +484,35 @@ void SetMain(TypeWrapper type, PEFileKinds target, IDictionary p throw new ArgumentNullException(nameof(properties)); // global main method decorated with appropriate apartment type - var mainMethodProxy = GetTypeWrapperFactory().ModuleBuilder.DefineGlobalMethod("Main", MethodAttributes.Public | MethodAttributes.Static, Types.Int32, new Type[] { Types.String.MakeArrayType() }); + var mainMethodProxy = GetTypeWrapperFactory().ModuleBuilder.DefineGlobalMethod("Main", MethodAttributes.Public | MethodAttributes.Static, Types.Int32, new[] { Types.String.MakeArrayType() }); if (apartmentAttributeType != null) - mainMethodProxy.SetCustomAttribute(new CustomAttributeBuilder(apartmentAttributeType.GetConstructor(Type.EmptyTypes), new object[0])); + mainMethodProxy.SetCustomAttribute(new CustomAttributeBuilder(apartmentAttributeType.GetConstructor(Type.EmptyTypes), Array.Empty())); var ilgen = CodeEmitter.Create(mainMethodProxy); - var propertiesType = LoadClassByDottedName("java.util.Properties").TypeAsTBD; - var launcherType = LoadClassByDottedName("ikvm.runtime.Launcher").TypeAsTBD; - var launchMethod = launcherType.GetMethod("run", new Type[] { Types.Type, Types.String.MakeArrayType(), Types.String, propertiesType }); - // first argument to Launch (Class) - ilgen.Emit(OpCodes.Ldtoken, type.TypeAsTBD); - ilgen.Emit(OpCodes.Call, Types.Type.GetMethod("GetTypeFromHandle")); + // first argument to Launch (type name) + ilgen.Emit(OpCodes.Ldstr, type.Name); - // second argument: args + // second argument: is this a jar + ilgen.Emit(OpCodes.Ldc_I4_0); + + // third argument: args ilgen.Emit(OpCodes.Ldarg_0); - // third argument, runtime prefix + // fourth argument, runtime prefix ilgen.Emit(OpCodes.Ldstr, DEFAULT_RUNTIME_ARGS_PREFIX); - // fourth argument, property set to initialize JVM with - ilgen.Emit(OpCodes.Newobj, propertiesType.GetConstructor(Type.EmptyTypes)); + // fifth argument, property set to initialize JVM if (properties.Count > 0) { + var environmentType = JVM.Import(typeof(Environment)); + var environmentExpandMethod = environmentType.GetMethod(nameof(Environment.ExpandEnvironmentVariables), new[] { Types.String }); + var dictionaryType = JVM.Import(typeof(Dictionary)); + var dictionaryAddMethod = dictionaryType.GetMethod("Add", new[] { Types.String, Types.String }); + + ilgen.EmitLdc_I4(properties.Count); + ilgen.Emit(OpCodes.Newobj, dictionaryType.GetConstructor(new[] { Types.Int32 })); + foreach (var kvp in properties) { ilgen.Emit(OpCodes.Dup); @@ -515,13 +521,19 @@ void SetMain(TypeWrapper type, PEFileKinds target, IDictionary p // property value can reference an environmental variable (reassess the requirment for this) if (kvp.Value.IndexOf('%') < kvp.Value.LastIndexOf('%')) - ilgen.Emit(OpCodes.Call, JVM.Import(typeof(Environment)).GetMethod(nameof(Environment.ExpandEnvironmentVariables), new[] { Types.String })); + ilgen.Emit(OpCodes.Call, environmentExpandMethod); - ilgen.Emit(OpCodes.Callvirt, propertiesType.GetMethod("setProperty", new Type[] { Types.Object, Types.Object })); + // add to properties dictionary + ilgen.Emit(OpCodes.Callvirt, dictionaryAddMethod); } } + else + { + ilgen.Emit(OpCodes.Ldnull); + } // invoke the launcher main method + var launchMethod = StaticCompiler.GetRuntimeType("IKVM.Runtime.Launcher").GetMethod("Run"); ilgen.Emit(OpCodes.Call, launchMethod); ilgen.Emit(OpCodes.Ret); @@ -2544,18 +2556,16 @@ internal static int Compile(string runtimeAssembly, List option } compiler.CompilePass1(); } + foreach (CompilerClassLoader compiler in compilers) { compiler.CompilePass2(); } + if (compilingCoreAssembly) - { - RuntimeHelperTypes.Create(compilers[0]); foreach (CompilerClassLoader compiler in compilers) - { compiler.EmitRemappedTypes2ndPass(); - } - } + foreach (CompilerClassLoader compiler in compilers) { int rc = compiler.CompilePass3(); @@ -2564,6 +2574,7 @@ internal static int Compile(string runtimeAssembly, List option return rc; } } + Tracer.Info(Tracer.Compiler, "CompilerClassLoader.Save..."); foreach (CompilerClassLoader compiler in compilers) { @@ -3060,8 +3071,21 @@ private int CompilePass3() throw new FatalCompilerErrorException(Message.ClassLoaderConstructorMissing); // apply custom attribute specifying custom class loader - var ci = JVM.LoadType(typeof(CustomAssemblyClassLoaderAttribute)).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { Types.Type }, null); + var ci = JVM.LoadType(typeof(CustomAssemblyClassLoaderAttribute)).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new[] { Types.Type }, null); assemblyBuilder.SetCustomAttribute(new CustomAttributeBuilder(ci, new object[] { classLoaderType.TypeAsTBD })); + + // the class loader type defines a module initialize method, ensure we call it upon module load + var mwModuleInit = classLoaderType.GetMethodWrapper("InitializeModule", "(Lcli.System.Reflection.Module;)V", false); + if (mwModuleInit != null && mwModuleInit.IsStatic == false) + { + var moduleInit = GetTypeWrapperFactory().ModuleBuilder.DefineGlobalMethod(".cctor", MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, null, Type.EmptyTypes); + var moduleInitIL = moduleInit.GetILGenerator(); + moduleInitIL.Emit(OpCodes.Ldtoken, moduleInit); + moduleInitIL.Emit(OpCodes.Call, JVM.Import(typeof(System.Reflection.MethodBase)).GetMethod("GetMethodFromHandle", new[] { JVM.Import(typeof(RuntimeMethodHandle)) })); + moduleInitIL.Emit(OpCodes.Callvirt, JVM.Import(typeof(System.Reflection.MemberInfo)).GetProperty("Module").GetGetMethod()); + moduleInitIL.Emit(OpCodes.Call, StaticCompiler.GetRuntimeType("IKVM.Runtime.ByteCodeHelper").GetMethod("InitializeModule")); + moduleInitIL.Emit(OpCodes.Ret); + } } if (options.iconfile != null) @@ -3074,16 +3098,6 @@ private int CompilePass3() assemblyBuilder.__DefineManifestResource(IkvmImporterInternal.ReadAllBytes(options.manifestFile)); } - // define a module initialization method - // this method invokes on module load and calls into the runtime - var moduleInit = GetTypeWrapperFactory().ModuleBuilder.DefineGlobalMethod(".cctor", MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, null, Type.EmptyTypes); - var moduleInitIL = moduleInit.GetILGenerator(); - moduleInitIL.Emit(OpCodes.Ldtoken, moduleInit); - moduleInitIL.Emit(OpCodes.Call, JVM.Import(typeof(System.Reflection.MethodBase)).GetMethod("GetMethodFromHandle", new Type[] { JVM.Import(typeof(RuntimeMethodHandle)) })); - moduleInitIL.Emit(OpCodes.Callvirt, JVM.Import(typeof(System.Reflection.MemberInfo)).GetMethod("get_Module")); - moduleInitIL.Emit(OpCodes.Call, StaticCompiler.GetRuntimeType("IKVM.Runtime.ByteCodeHelper").GetMethod("InitializeModule")); - moduleInitIL.Emit(OpCodes.Ret); - assemblyBuilder.DefineVersionInfoResource(); return 0; diff --git a/src/IKVM.Tools.Tests/IKVM.Tools.Tests.csproj b/src/IKVM.Tools.Tests/IKVM.Tools.Tests.csproj index 6805c372ac..fc20058fc3 100644 --- a/src/IKVM.Tools.Tests/IKVM.Tools.Tests.csproj +++ b/src/IKVM.Tools.Tests/IKVM.Tools.Tests.csproj @@ -16,7 +16,7 @@ - + PreserveNewest diff --git a/src/IKVM.Tools.Tests/Runner/Compiler/IkvmCompilerLauncherTests.cs b/src/IKVM.Tools.Tests/Runner/Compiler/IkvmCompilerLauncherTests.cs index 69b15b19ab..9b4e03a04e 100644 --- a/src/IKVM.Tools.Tests/Runner/Compiler/IkvmCompilerLauncherTests.cs +++ b/src/IKVM.Tools.Tests/Runner/Compiler/IkvmCompilerLauncherTests.cs @@ -25,7 +25,7 @@ async Task CompileJar(string tfm) { var libs = Path.Combine(TESTBASE, "lib", tfm); - var p = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), "ext", tfm, "helloworld-2.0.dll"); + var p = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), "helloworld", tfm, "helloworld-2.0.dll"); Directory.CreateDirectory(Path.GetDirectoryName(p)); var rid = ""; @@ -42,7 +42,7 @@ async Task CompileJar(string tfm) { Runtime = Path.Combine(TESTBASE, "lib", tfm, "IKVM.Runtime.dll"), ResponseFile = $"CompileJar_{tfm}_ikvmc.rsp", - Input = { Path.Combine(TESTBASE, "ext", "helloworld-2.0.jar") }, + Input = { Path.Combine(TESTBASE, "helloworld", "helloworld-2.0.jar") }, Assembly = "helloworld-2.0", Version = "1.0.0.0", NoStdLib = true, diff --git a/src/IKVM.Tools.Tests/Runner/Exporter/IkvmExporterLauncherTests.cs b/src/IKVM.Tools.Tests/Runner/Exporter/IkvmExporterLauncherTests.cs index dc717ebb41..50c873377a 100644 --- a/src/IKVM.Tools.Tests/Runner/Exporter/IkvmExporterLauncherTests.cs +++ b/src/IKVM.Tools.Tests/Runner/Exporter/IkvmExporterLauncherTests.cs @@ -26,7 +26,7 @@ async Task Can_export_dll(string tfm) { var libs = Path.Combine(TESTBASE, "lib", tfm); - var a = Path.Combine(TESTBASE, "ext", tfm, "HelloWorldDotNet.dll"); + var a = Path.Combine(TESTBASE, "helloworld", tfm, "HelloWorldDotNet.dll"); var p = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), Path.ChangeExtension(a, ".jar")); Directory.CreateDirectory(Path.GetDirectoryName(p)); diff --git a/src/dist-tests/dist-tests.csproj b/src/dist-tests/dist-tests.csproj index 57cb08b650..bc59cbfd03 100644 --- a/src/dist-tests/dist-tests.csproj +++ b/src/dist-tests/dist-tests.csproj @@ -13,6 +13,8 @@ + + @@ -39,7 +41,8 @@ <_ProjectName>%(TestTarget.ProjectName) <_ProjectFile>%(TestTarget.ProjectFile) - <_ProjectFile Condition=" '$(_ProjectFile)' == '' ">..\$(_ProjectName)\$(_ProjectName).csproj + <_ProjectFile Condition=" '$(_ProjectFile)' == '' And Exists('..\$(_ProjectName)\$(_ProjectName).csproj') ">..\$(_ProjectName)\$(_ProjectName).csproj + <_ProjectFile Condition=" '$(_ProjectFile)' == '' And Exists('..\$(_ProjectName)\$(_ProjectName).msbuildproj') ">..\$(_ProjectName)\$(_ProjectName).msbuildproj <_TargetFramework>%(TestTarget.TargetFramework) diff --git a/src/java/Program.cs b/src/java/Program.cs index 2fa263b360..a7336611f4 100644 --- a/src/java/Program.cs +++ b/src/java/Program.cs @@ -1,8 +1,5 @@ -using ikvm.runtime; - -using IKVM.Attributes; - -using java.util; +using IKVM.Attributes; +using IKVM.Runtime; namespace IKVM.Tools.Java { @@ -11,7 +8,7 @@ public static class Program { [HideFromJava] - public static int Main(string[] args) => Launcher.run(null, args, "", new Properties()); + public static int Main(string[] args) => Launcher.Run(null, false, args, "", null); } diff --git a/src/java/Properties/launchSettings.json b/src/java/Properties/launchSettings.json deleted file mode 100644 index c57951f810..0000000000 --- a/src/java/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "java": { - "commandName": "Project", - "commandLineArgs": "-cp foo" - } - } -} \ No newline at end of file