changeset 57521:4d90b46f099c

Merge
author jwilhelm
date Fri, 20 Dec 2019 04:47:34 +0100
parents 5dc3440be041 7118b1a7d9fb
children 2fbc66ef1a1d
files .hgtags src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacDmgBundler.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/IOUtils.java
diffstat 42 files changed, 1188 insertions(+), 174 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Thu Nov 21 17:10:26 2019 -0300
+++ b/.hgtags	Fri Dec 20 04:47:34 2019 +0100
@@ -602,4 +602,5 @@
 2c724dba4c3cf9516b2152e151c9aea66b21b30b jdk-15+0
 91a3f092682fc715d991a87eb6ec6f28886d2035 jdk-14+27
 63e17cf29bed191ea21020b4648c9cdf893f80f5 jdk-15+1
+2069b4bfd23b56b6fc659fba8b75aaaa23debbe0 jdk-14+28
 f33197adda9ad82fdef46ac0f7dc0126204f35b2 jdk-15+2
--- a/make/autoconf/hotspot.m4	Thu Nov 21 17:10:26 2019 -0300
+++ b/make/autoconf/hotspot.m4	Fri Dec 20 04:47:34 2019 +0100
@@ -345,10 +345,27 @@
   fi
 
   # Only enable ZGC on supported platforms
+  if (test "x$OPENJDK_TARGET_OS" = "xwindows" && test "x$OPENJDK_TARGET_CPU" = "xx86_64"); then
+    AC_MSG_CHECKING([if zgc can be built on windows])
+    AC_COMPILE_IFELSE(
+      [AC_LANG_PROGRAM([[#include <windows.h>]],
+        [[struct MEM_EXTENDED_PARAMETER x;]])
+      ],
+      [
+        AC_MSG_RESULT([yes])
+        CAN_BUILD_ZGC_ON_WINDOWS="yes"
+      ],
+      [
+        AC_MSG_RESULT([no, missing required APIs])
+        CAN_BUILD_ZGC_ON_WINDOWS="no"
+      ]
+    )
+  fi
+
   AC_MSG_CHECKING([if zgc can be built])
   if (test "x$OPENJDK_TARGET_OS" = "xlinux" && test "x$OPENJDK_TARGET_CPU" = "xx86_64") || \
      (test "x$OPENJDK_TARGET_OS" = "xlinux" && test "x$OPENJDK_TARGET_CPU" = "xaarch64") || \
-     (test "x$OPENJDK_TARGET_OS" = "xwindows" && test "x$OPENJDK_TARGET_CPU" = "xx86_64") || \
+     (test "x$CAN_BUILD_ZGC_ON_WINDOWS" = "xyes") || \
      (test "x$OPENJDK_TARGET_OS" = "xmacosx" && test "x$OPENJDK_TARGET_CPU" = "xx86_64"); then
     AC_MSG_RESULT([yes])
   else
--- a/src/jdk.aot/share/classes/jdk.tools.jaotc.binformat/src/jdk/tools/jaotc/binformat/BinaryContainer.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.aot/share/classes/jdk.tools.jaotc.binformat/src/jdk/tools/jaotc/binformat/BinaryContainer.java	Fri Dec 20 04:47:34 2019 +0100
@@ -31,6 +31,7 @@
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -43,6 +44,7 @@
 import org.graalvm.compiler.hotspot.GraalHotSpotVMConfig;
 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
 import org.graalvm.compiler.options.OptionValues;
+import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
 
 /**
  * A format-agnostic container class that holds various components of a binary.
@@ -67,6 +69,8 @@
 
     private final int codeEntryAlignment;
 
+    private final boolean threadLocalHandshakes;
+
     /**
      * Container holding code bits and any other related information.
      */
@@ -292,6 +296,8 @@
 
         this.codeEntryAlignment = graalHotSpotVMConfig.codeEntryAlignment;
 
+        this.threadLocalHandshakes = graalHotSpotVMConfig.threadLocalHandshakes;
+
         // Section unique name is limited to 8 characters due to limitation on Windows.
         // Name could be longer but only first 8 characters are stored on Windows.
 
@@ -350,6 +356,12 @@
         // @formatter:on
         // @Checkstyle: resume
 
+        if (JavaVersionUtil.JAVA_SPEC < 14) {
+            // See JDK-8220049. Thread local handshakes are on by default since JDK14, the command line option has been removed.
+            booleanFlags = Arrays.copyOf(booleanFlags, booleanFlags.length + 1);
+            booleanFlags[booleanFlags.length - 1] = graalHotSpotVMConfig.threadLocalHandshakes;
+        }
+
         byte[] booleanFlagsAsBytes = flagsToByteArray(booleanFlags);
         int size0 = configContainer.getByteStreamSize();
 
@@ -449,6 +461,10 @@
         return codeEntryAlignment;
     }
 
+    public boolean getThreadLocalHandshakes() {
+        return threadLocalHandshakes;
+    }
+
     /**
      * Gets the global AOT symbol associated with the function name.
      *
--- a/src/jdk.aot/share/classes/jdk.tools.jaotc/src/jdk/tools/jaotc/MarkProcessor.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.aot/share/classes/jdk.tools.jaotc/src/jdk/tools/jaotc/MarkProcessor.java	Fri Dec 20 04:47:34 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -56,8 +56,11 @@
                 break;
             case POLL_FAR:
             case POLL_RETURN_FAR:
-                // skip relocation
-                break;
+                if (binaryContainer.getThreadLocalHandshakes()) {
+                    // skip relocation
+                    break;
+                }
+                // fallthrough
             case CARD_TABLE_ADDRESS:
             case HEAP_TOP_ADDRESS:
             case HEAP_END_ADDRESS:
--- a/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacDmgBundler.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacDmgBundler.java	Fri Dec 20 04:47:34 2019 +0100
@@ -290,7 +290,7 @@
                 protoDMG.getAbsolutePath(),
                 hdiUtilVerbosityFlag,
                 "-mountroot", imagesRoot.getAbsolutePath());
-        IOUtils.exec(pb);
+        IOUtils.exec(pb, false, null, true);
 
         File mountedRoot = new File(imagesRoot.getAbsolutePath(),
                     APP_NAME.fetchFrom(params));
--- a/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Executor.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Executor.java	Fri Dec 20 04:47:34 2019 +0100
@@ -48,6 +48,11 @@
         return this;
     }
 
+    Executor setWaitBeforeOutput(boolean v) {
+        waitBeforeOutput = v;
+        return this;
+    }
+
     Executor setProcessBuilder(ProcessBuilder v) {
         pb = v;
         return this;
@@ -88,6 +93,16 @@
         Log.verbose(String.format("Running %s", createLogMessage(pb)));
         Process p = pb.start();
 
+        int code = 0;
+        if (waitBeforeOutput) {
+            try {
+                code = p.waitFor();
+            } catch (InterruptedException ex) {
+                Log.verbose(ex);
+                throw new RuntimeException(ex);
+            }
+        }
+
         if (needProcessOutput) {
             try (var br = new BufferedReader(new InputStreamReader(
                     p.getInputStream()))) {
@@ -131,7 +146,10 @@
         }
 
         try {
-            return p.waitFor();
+            if (!waitBeforeOutput) {
+                code = p.waitFor();
+            }
+            return code;
         } catch (InterruptedException ex) {
             Log.verbose(ex);
             throw new RuntimeException(ex);
@@ -157,6 +175,7 @@
 
     private ProcessBuilder pb;
     private boolean saveOutput;
+    private boolean waitBeforeOutput;
     private List<String> output;
     private Consumer<Stream<String>> outputConsumer;
 }
--- a/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/IOUtils.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/IOUtils.java	Fri Dec 20 04:47:34 2019 +0100
@@ -133,20 +133,33 @@
 
     public static void exec(ProcessBuilder pb)
             throws IOException {
-        exec(pb, false, null);
+        exec(pb, false, null, false);
     }
 
-    static void exec(ProcessBuilder pb, boolean testForPresenseOnly,
+    // Reading output from some processes (currently known "hdiutil attach" might hang even if process already
+    // exited. Only possible workaround found in "hdiutil attach" case is to wait for process to exit before
+    // reading output.
+    public static void exec(ProcessBuilder pb, boolean waitBeforeOutput)
+            throws IOException {
+        exec(pb, false, null, waitBeforeOutput);
+    }
+
+    static void exec(ProcessBuilder pb, boolean testForPresenceOnly,
             PrintStream consumer) throws IOException {
+        exec(pb, testForPresenceOnly, consumer, false);
+    }
+
+    static void exec(ProcessBuilder pb, boolean testForPresenceOnly,
+            PrintStream consumer, boolean waitBeforeOutput) throws IOException {
         List<String> output = new ArrayList<>();
-        Executor exec = Executor.of(pb).setOutputConsumer(lines -> {
+        Executor exec = Executor.of(pb).setWaitBeforeOutput(waitBeforeOutput).setOutputConsumer(lines -> {
             lines.forEach(output::add);
             if (consumer != null) {
                 output.forEach(consumer::println);
             }
         });
 
-        if (testForPresenseOnly) {
+        if (testForPresenceOnly) {
             exec.execute();
         } else {
             exec.executeExpectSuccess();
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.api.directives.test/src/org/graalvm/compiler/api/directives/test/BlackholeDirectiveTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.api.directives.test/src/org/graalvm/compiler/api/directives/test/BlackholeDirectiveTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -29,16 +29,13 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-import org.junit.Assert;
-import org.junit.Test;
-
 import org.graalvm.compiler.api.directives.GraalDirectives;
 import org.graalvm.compiler.core.test.GraalCompilerTest;
 import org.graalvm.compiler.nodes.ParameterNode;
 import org.graalvm.compiler.nodes.StructuredGraph;
 import org.graalvm.compiler.phases.OptimisticOptimizations;
-import org.graalvm.compiler.phases.OptimisticOptimizations.Optimization;
-import org.graalvm.compiler.phases.tiers.HighTierContext;
+import org.junit.Assert;
+import org.junit.Test;
 
 /**
  * Tests for {@link GraalDirectives#blackhole}.
@@ -134,8 +131,8 @@
     }
 
     @Override
-    protected HighTierContext getDefaultHighTierContext() {
-        return new HighTierContext(getProviders(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL.remove(Optimization.RemoveNeverExecutedCode));
+    protected OptimisticOptimizations getOptimisticOptimizations() {
+        return OptimisticOptimizations.ALL.remove(OptimisticOptimizations.Optimization.RemoveNeverExecutedCode);
     }
 
     @Override
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.api.directives.test/src/org/graalvm/compiler/api/directives/test/ControlFlowAnchorDirectiveTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.api.directives.test/src/org/graalvm/compiler/api/directives/test/ControlFlowAnchorDirectiveTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -33,9 +33,6 @@
 import java.util.Collections;
 import java.util.List;
 
-import org.junit.Assert;
-import org.junit.Test;
-
 import org.graalvm.compiler.api.directives.GraalDirectives;
 import org.graalvm.compiler.core.test.GraalCompilerTest;
 import org.graalvm.compiler.graph.Node;
@@ -46,11 +43,11 @@
 import org.graalvm.compiler.nodes.StructuredGraph;
 import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
 import org.graalvm.compiler.nodes.debug.ControlFlowAnchorNode;
+import org.graalvm.compiler.phases.OptimisticOptimizations;
+import org.junit.Assert;
+import org.junit.Test;
 
 import jdk.vm.ci.meta.ResolvedJavaMethod;
-import org.graalvm.compiler.phases.OptimisticOptimizations;
-import org.graalvm.compiler.phases.OptimisticOptimizations.Optimization;
-import org.graalvm.compiler.phases.tiers.HighTierContext;
 
 public class ControlFlowAnchorDirectiveTest extends GraalCompilerTest {
 
@@ -244,8 +241,8 @@
     }
 
     @Override
-    protected HighTierContext getDefaultHighTierContext() {
-        return new HighTierContext(getProviders(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL.remove(Optimization.RemoveNeverExecutedCode));
+    protected OptimisticOptimizations getOptimisticOptimizations() {
+        return OptimisticOptimizations.ALL.remove(OptimisticOptimizations.Optimization.RemoveNeverExecutedCode);
     }
 
     @Override
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.api.directives.test/src/org/graalvm/compiler/api/directives/test/OpaqueDirectiveTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.api.directives.test/src/org/graalvm/compiler/api/directives/test/OpaqueDirectiveTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -29,9 +29,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-import org.junit.Assert;
-import org.junit.Test;
-
 import org.graalvm.compiler.api.directives.GraalDirectives;
 import org.graalvm.compiler.core.test.GraalCompilerTest;
 import org.graalvm.compiler.nodes.ConstantNode;
@@ -40,8 +37,8 @@
 import org.graalvm.compiler.nodes.calc.AddNode;
 import org.graalvm.compiler.nodes.calc.ConditionalNode;
 import org.graalvm.compiler.phases.OptimisticOptimizations;
-import org.graalvm.compiler.phases.OptimisticOptimizations.Optimization;
-import org.graalvm.compiler.phases.tiers.HighTierContext;
+import org.junit.Assert;
+import org.junit.Test;
 
 /**
  * Tests for {@link GraalDirectives#opaque}.
@@ -133,8 +130,8 @@
     }
 
     @Override
-    protected HighTierContext getDefaultHighTierContext() {
-        return new HighTierContext(getProviders(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL.remove(Optimization.RemoveNeverExecutedCode));
+    protected OptimisticOptimizations getOptimisticOptimizations() {
+        return OptimisticOptimizations.ALL.remove(OptimisticOptimizations.Optimization.RemoveNeverExecutedCode);
     }
 
     @Override
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.asm.aarch64.test/src/org/graalvm/compiler/asm/aarch64/test/AArch64LoadStoreMergingAssemblerTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, Arm Limited. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package org.graalvm.compiler.asm.aarch64.test;
+
+import jdk.vm.ci.aarch64.AArch64;
+import jdk.vm.ci.code.Register;
+import jdk.vm.ci.code.TargetDescription;
+import jdk.vm.ci.runtime.JVMCI;
+import org.graalvm.compiler.asm.aarch64.AArch64Address;
+import org.graalvm.compiler.asm.aarch64.AArch64MacroAssembler;
+import org.graalvm.compiler.test.GraalTest;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assume.assumeTrue;
+
+public class AArch64LoadStoreMergingAssemblerTest extends GraalTest {
+    private Register base;
+    private Register rt1;
+    private Register rt2;
+
+    @Before
+    public void checkAArch64() {
+        TargetDescription target = JVMCI.getRuntime().getHostJVMCIBackend().getTarget();
+        assumeTrue("skipping non AArch64 specific test", target.arch instanceof AArch64);
+    }
+
+    @Before
+    public void setupEnvironment() {
+        base = AArch64.sp;
+        rt1 = AArch64.r1;
+        rt2 = AArch64.r2;
+    }
+
+    private abstract static class AArch64LoadStoreCodeGen {
+        protected AArch64MacroAssembler masm1;
+        protected AArch64MacroAssembler masm2;
+
+        AArch64LoadStoreCodeGen() {
+            TargetDescription target = JVMCI.getRuntime().getHostJVMCIBackend().getTarget();
+            masm1 = new AArch64MacroAssembler(target);
+            masm2 = new AArch64MacroAssembler(target);
+        }
+
+        void emitScaledImmLdr(int size, Register rt, Register base, int imm12) {
+            AArch64Address address = AArch64Address.createScaledImmediateAddress(base, imm12);
+            masm2.ldr(size, rt, address);
+        }
+
+        void emitUnscaledImmLdr(int size, Register rt, Register base, int imm9) {
+            AArch64Address address1 = AArch64Address.createUnscaledImmediateAddress(base, imm9);
+            masm2.ldr(size, rt, address1);
+        }
+
+        void emitScaledImmStr(int size, Register rt, Register base, int imm12) {
+            AArch64Address address = AArch64Address.createScaledImmediateAddress(base, imm12);
+            masm2.str(size, rt, address);
+        }
+
+        void emitUnscaledImmStr(int size, Register rt, Register base, int imm9) {
+            AArch64Address address1 = AArch64Address.createUnscaledImmediateAddress(base, imm9);
+            masm2.str(size, rt, address1);
+        }
+
+        void emitScaledLdp(int size, Register rt1, Register rt2, Register base, int imm7) {
+            AArch64Address mergeAddress = AArch64Address.createScaledImmediateAddress(base, imm7);
+            masm1.ldp(size, rt1, rt2, mergeAddress);
+        }
+
+        void emitScaledStp(int size, Register rt1, Register rt2, Register base, int imm7) {
+            AArch64Address mergeAddress = AArch64Address.createScaledImmediateAddress(base, imm7);
+            masm1.stp(size, rt1, rt2, mergeAddress);
+        }
+
+        void emitUnscaledLdp(int size, Register rt1, Register rt2, Register base, int imm) {
+            AArch64Address mergeAddress = AArch64Address.createUnscaledImmediateAddress(base, imm);
+            masm1.ldp(size, rt1, rt2, mergeAddress);
+        }
+
+        void emitUnscaledStp(int size, Register rt1, Register rt2, Register base, int imm) {
+            AArch64Address mergeAddress = AArch64Address.createUnscaledImmediateAddress(base, imm);
+            masm1.stp(size, rt1, rt2, mergeAddress);
+        }
+
+        abstract void checkAssembly();
+    }
+
+    private static class AArch64LoadStoreMergingCodeGen extends AArch64LoadStoreCodeGen {
+        AArch64LoadStoreMergingCodeGen() {
+            super();
+        }
+
+        @Override
+        void checkAssembly() {
+            byte[] expected = masm1.close(false);
+            byte[] actual = masm2.close(false);
+            assertArrayEquals(expected, actual);
+        }
+    }
+
+    @Test
+    public void testLoad64BitsScaledImmAddress() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitScaledImmLdr(64, rt1, base, 4);
+        codeGen.emitScaledImmLdr(64, rt2, base, 5);
+        codeGen.emitScaledLdp(64, rt1, rt2, base, 4);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testLoad32BitsScaledImmAddress() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitScaledImmLdr(32, rt1, base, 5);
+        codeGen.emitScaledImmLdr(32, rt2, base, 4);
+        codeGen.emitScaledLdp(32, rt2, rt1, base, 4);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testStore64BitsScaledImmAddress() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitScaledImmStr(64, rt1, base, 4);
+        codeGen.emitScaledImmStr(64, rt2, base, 5);
+        codeGen.emitScaledStp(64, rt1, rt2, base, 4);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testStore32BitsScaledImmAddress() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitScaledImmStr(32, rt1, base, 4);
+        codeGen.emitScaledImmStr(32, rt2, base, 5);
+        codeGen.emitScaledStp(32, rt1, rt2, base, 4);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testLoad64BitsUnscaledImmAddress() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitUnscaledImmLdr(64, rt1, base, -32);
+        codeGen.emitUnscaledImmLdr(64, rt2, base, -24);
+        codeGen.emitUnscaledLdp(64, rt1, rt2, base, -32);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testLoad32BitsUnscaledImmAddress() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitUnscaledImmLdr(32, rt1, base, 248);
+        codeGen.emitUnscaledImmLdr(32, rt2, base, 252);
+        codeGen.emitUnscaledLdp(32, rt1, rt2, base, 248);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testStore64BitsUnscaledImmAddress() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitUnscaledImmStr(64, rt1, base, 32);
+        codeGen.emitUnscaledImmStr(64, rt2, base, 40);
+        codeGen.emitUnscaledStp(64, rt1, rt2, base, 32);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testStore32BitsUnscaledImmAddress() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitUnscaledImmStr(32, rt1, base, 32);
+        codeGen.emitUnscaledImmStr(32, rt2, base, 36);
+        codeGen.emitUnscaledStp(32, rt1, rt2, base, 32);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testLoadUnscaledScaledImmAddress() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitUnscaledImmLdr(32, rt1, base, 48);
+        codeGen.emitScaledImmLdr(32, rt2, base, 13);
+        codeGen.emitScaledLdp(32, rt1, rt2, base, 12);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testLoadScaledUnscaledImmAddress() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitScaledImmLdr(32, rt1, base, 13);
+        codeGen.emitUnscaledImmLdr(32, rt2, base, 48);
+        codeGen.emitUnscaledLdp(32, rt2, rt1, base, 48);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testLoadMaxAlignedOffset() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitScaledImmLdr(64, rt1, base, 62);
+        codeGen.emitScaledImmLdr(64, rt2, base, 63);
+        codeGen.emitScaledLdp(64, rt1, rt2, base, 62);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testStoreMinAlignedOffest() {
+        AArch64LoadStoreMergingCodeGen codeGen = new AArch64LoadStoreMergingCodeGen();
+        codeGen.emitUnscaledImmStr(32, rt1, base, -256);
+        codeGen.emitUnscaledImmStr(32, rt2, base, -252);
+        codeGen.emitUnscaledStp(32, rt1, rt2, base, -256);
+        codeGen.checkAssembly();
+    }
+
+    // All the following tests are the negative ones that ldr/str will not be merged to ldp/stp.
+    private static class AArch64LoadStoreNotMergingCodeGen extends AArch64LoadStoreCodeGen {
+        AArch64LoadStoreNotMergingCodeGen() {
+            super();
+        }
+
+        @Override
+        void checkAssembly() {
+            boolean isMerged = masm2.isImmLoadStoreMerged();
+            masm2.close(false);
+            Assert.assertFalse(isMerged);
+        }
+    }
+
+    @Test
+    public void testDifferentBase() {
+        AArch64LoadStoreNotMergingCodeGen codeGen = new AArch64LoadStoreNotMergingCodeGen();
+        codeGen.emitScaledImmLdr(32, rt1, base, 4);
+        codeGen.emitScaledImmLdr(32, rt2, AArch64.r3, 5);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testDifferentSize() {
+        AArch64LoadStoreNotMergingCodeGen codeGen = new AArch64LoadStoreNotMergingCodeGen();
+        codeGen.emitScaledImmLdr(32, rt1, base, 4);
+        codeGen.emitScaledImmLdr(64, rt2, base, 5);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testSameRt() {
+        AArch64LoadStoreNotMergingCodeGen codeGen = new AArch64LoadStoreNotMergingCodeGen();
+        codeGen.emitScaledImmLdr(32, rt1, base, 4);
+        codeGen.emitScaledImmLdr(32, rt1, base, 5);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testDependencyLdrs() {
+        AArch64LoadStoreNotMergingCodeGen codeGen = new AArch64LoadStoreNotMergingCodeGen();
+        codeGen.emitScaledImmLdr(32, rt1, rt1, 4);
+        codeGen.emitScaledImmLdr(32, rt2, rt1, 5);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testUnalignedOffset() {
+        AArch64LoadStoreNotMergingCodeGen codeGen = new AArch64LoadStoreNotMergingCodeGen();
+        codeGen.emitUnscaledImmLdr(32, rt1, base, 34);
+        codeGen.emitUnscaledImmLdr(32, rt2, base, 38);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testUncontinuousOffset() {
+        AArch64LoadStoreNotMergingCodeGen codeGen = new AArch64LoadStoreNotMergingCodeGen();
+        codeGen.emitScaledImmLdr(32, rt1, base, 4);
+        codeGen.emitScaledImmLdr(32, rt2, base, 6);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testGreaterThanMaxOffset() {
+        AArch64LoadStoreNotMergingCodeGen codeGen = new AArch64LoadStoreNotMergingCodeGen();
+        codeGen.emitScaledImmLdr(32, rt1, base, 66);
+        codeGen.emitScaledImmLdr(32, rt2, base, 67);
+        codeGen.checkAssembly();
+    }
+
+    @Test
+    public void testLdrStr() {
+        AArch64LoadStoreNotMergingCodeGen codeGen = new AArch64LoadStoreNotMergingCodeGen();
+        codeGen.emitScaledImmLdr(32, rt1, base, 4);
+        codeGen.emitScaledImmStr(32, rt2, base, 5);
+        codeGen.checkAssembly();
+    }
+}
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.asm.aarch64/src/org/graalvm/compiler/asm/aarch64/AArch64Assembler.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.asm.aarch64/src/org/graalvm/compiler/asm/aarch64/AArch64Assembler.java	Fri Dec 20 04:47:34 2019 +0100
@@ -1302,13 +1302,23 @@
     }
 
     /**
+     * Insert ldp/stp at the specified position.
+     */
+    protected void insertLdpStp(int size, Instruction instr, Register rt, Register rt2, Register base, int offset, int position) {
+        InstructionType type = generalFromSize(size);
+        int scaledOffset = maskField(7, offset);
+        int memop = type.encoding | instr.encoding | scaledOffset << LoadStorePairImm7Offset | rt2(rt2) | rn(base) | rt(rt);
+        emitInt(memop | LoadStorePairOp | (0b010 << 23), position);
+    }
+
+    /**
      * Load Pair of Registers calculates an address from a base register value and an immediate
      * offset, and stores two 32-bit words or two 64-bit doublewords to the calculated address, from
      * two registers.
      */
     public void ldp(int size, Register rt, Register rt2, AArch64Address address) {
         assert size == 32 || size == 64;
-        loadStorePairInstruction(LDP, rt, rt2, address, generalFromSize(size));
+        loadStorePairInstruction(size, LDP, rt, rt2, address);
     }
 
     /**
@@ -1318,15 +1328,24 @@
      */
     public void stp(int size, Register rt, Register rt2, AArch64Address address) {
         assert size == 32 || size == 64;
-        loadStorePairInstruction(STP, rt, rt2, address, generalFromSize(size));
+        loadStorePairInstruction(size, STP, rt, rt2, address);
     }
 
-    private void loadStorePairInstruction(Instruction instr, Register rt, Register rt2, AArch64Address address, InstructionType type) {
-        int scaledOffset = maskField(7, address.getImmediateRaw());  // LDP/STP use a 7-bit scaled
-                                                                     // offset
+    private void loadStorePairInstruction(int size, Instruction instr, Register rt, Register rt2, AArch64Address address) {
+        InstructionType type = generalFromSize(size);
+        // LDP/STP uses a 7-bit scaled offset
+        int offset = address.getImmediateRaw();
+        if (address.getAddressingMode() == AddressingMode.IMMEDIATE_UNSCALED) {
+            int sizeInBytes = size / Byte.SIZE;
+            long mask = sizeInBytes - 1;
+            assert (offset & mask) == 0 : "LDP/STP only supports aligned offset.";
+            offset = offset / sizeInBytes;
+        }
+        int scaledOffset = maskField(7, offset);
         int memop = type.encoding | instr.encoding | scaledOffset << LoadStorePairImm7Offset | rt2(rt2) | rn(address.getBase()) | rt(rt);
         switch (address.getAddressingMode()) {
             case IMMEDIATE_SCALED:
+            case IMMEDIATE_UNSCALED:
                 emitInt(memop | LoadStorePairOp | (0b010 << 23));
                 break;
             case IMMEDIATE_POST_INDEXED:
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.asm.aarch64/src/org/graalvm/compiler/asm/aarch64/AArch64MacroAssembler.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.asm.aarch64/src/org/graalvm/compiler/asm/aarch64/AArch64MacroAssembler.java	Fri Dec 20 04:47:34 2019 +0100
@@ -35,6 +35,8 @@
 import static org.graalvm.compiler.asm.aarch64.AArch64Address.AddressingMode.IMMEDIATE_SCALED;
 import static org.graalvm.compiler.asm.aarch64.AArch64Address.AddressingMode.IMMEDIATE_UNSCALED;
 import static org.graalvm.compiler.asm.aarch64.AArch64Address.AddressingMode.REGISTER_OFFSET;
+import static org.graalvm.compiler.asm.aarch64.AArch64Assembler.Instruction.LDP;
+import static org.graalvm.compiler.asm.aarch64.AArch64Assembler.Instruction.STP;
 import static org.graalvm.compiler.asm.aarch64.AArch64MacroAssembler.AddressGenerationPlan.WorkPlan.ADD_TO_BASE;
 import static org.graalvm.compiler.asm.aarch64.AArch64MacroAssembler.AddressGenerationPlan.WorkPlan.ADD_TO_INDEX;
 import static org.graalvm.compiler.asm.aarch64.AArch64MacroAssembler.AddressGenerationPlan.WorkPlan.NO_WORK;
@@ -55,6 +57,10 @@
     // Points to the next free scratch register
     private int nextFreeScratchRegister = 0;
 
+    // Last immediate ldr/str instruction, which is a candidate to be merged.
+    private AArch64MemoryEncoding lastImmLoadStoreEncoding;
+    private boolean isImmLoadStoreMerged = false;
+
     public AArch64MacroAssembler(TargetDescription target) {
         super(target);
     }
@@ -81,6 +87,43 @@
         return scratchRegister[nextFreeScratchRegister++];
     }
 
+    @Override
+    public void bind(Label l) {
+        super.bind(l);
+        // Clear last ldr/str instruction to prevent the labeled ldr/str being merged.
+        lastImmLoadStoreEncoding = null;
+    }
+
+    private static class AArch64MemoryEncoding {
+        private AArch64Address address;
+        private Register result;
+        private int sizeInBytes;
+        private int position;
+        private boolean isStore;
+
+        AArch64MemoryEncoding(int sizeInBytes, Register result, AArch64Address address, boolean isStore, int position) {
+            this.sizeInBytes = sizeInBytes;
+            this.result = result;
+            this.address = address;
+            this.isStore = isStore;
+            this.position = position;
+            AArch64Address.AddressingMode addressingMode = address.getAddressingMode();
+            assert addressingMode == IMMEDIATE_SCALED || addressingMode == IMMEDIATE_UNSCALED : "Invalid address mode" +
+                            "to merge: " + addressingMode;
+        }
+
+        Register getBase() {
+            return address.getBase();
+        }
+
+        int getOffset() {
+            if (address.getAddressingMode() == IMMEDIATE_UNSCALED) {
+                return address.getImmediateRaw();
+            }
+            return address.getImmediate() * sizeInBytes;
+        }
+    }
+
     /**
      * Specifies what actions have to be taken to turn an arbitrary address of the form
      * {@code base + displacement [+ index [<< scale]]} into a valid AArch64Address.
@@ -321,6 +364,132 @@
         }
     }
 
+    private boolean tryMerge(int sizeInBytes, Register rt, AArch64Address address, boolean isStore) {
+        isImmLoadStoreMerged = false;
+        if (lastImmLoadStoreEncoding == null) {
+            return false;
+        }
+
+        // Only immediate scaled/unscaled address can be merged.
+        // Pre-index and post-index mode can't be merged.
+        AArch64Address.AddressingMode addressMode = address.getAddressingMode();
+        if (addressMode != IMMEDIATE_SCALED && addressMode != IMMEDIATE_UNSCALED) {
+            return false;
+        }
+
+        // Only the two adjacent ldrs/strs can be merged.
+        int lastPosition = position() - 4;
+        if (lastPosition < 0 || lastPosition != lastImmLoadStoreEncoding.position) {
+            return false;
+        }
+
+        if (isStore != lastImmLoadStoreEncoding.isStore) {
+            return false;
+        }
+
+        // Only merge ldr/str with the same size of 32bits or 64bits.
+        if (sizeInBytes != lastImmLoadStoreEncoding.sizeInBytes || (sizeInBytes != 4 && sizeInBytes != 8)) {
+            return false;
+        }
+
+        // Base register must be the same one.
+        Register curBase = address.getBase();
+        Register preBase = lastImmLoadStoreEncoding.getBase();
+        if (!curBase.equals(preBase)) {
+            return false;
+        }
+
+        // If the two ldrs have the same rt register, they can't be merged.
+        // If the two ldrs have dependence, they can't be merged.
+        Register curRt = rt;
+        Register preRt = lastImmLoadStoreEncoding.result;
+        if (!isStore && (curRt.equals(preRt) || preRt.equals(curBase))) {
+            return false;
+        }
+
+        // Offset checking. Offsets of the two ldrs/strs must be continuous.
+        int curOffset = address.getImmediateRaw();
+        if (addressMode == IMMEDIATE_SCALED) {
+            curOffset = curOffset * sizeInBytes;
+        }
+        int preOffset = lastImmLoadStoreEncoding.getOffset();
+        if (Math.abs(curOffset - preOffset) != sizeInBytes) {
+            return false;
+        }
+
+        // Offset must be in ldp/stp instruction's range.
+        int offset = curOffset > preOffset ? preOffset : curOffset;
+        int minOffset = -64 * sizeInBytes;
+        int maxOffset = 63 * sizeInBytes;
+        if (offset < minOffset || offset > maxOffset) {
+            return false;
+        }
+
+        // Alignment checking.
+        if (isFlagSet(AArch64.Flag.AvoidUnalignedAccesses)) {
+            // AArch64 sp is 16-bytes aligned.
+            if (curBase.equals(sp)) {
+                long pairMask = sizeInBytes * 2 - 1;
+                if ((offset & pairMask) != 0) {
+                    return false;
+                }
+            } else {
+                // If base is not sp, we can't guarantee the access is aligned.
+                return false;
+            }
+        } else {
+            // ldp/stp only supports sizeInBytes aligned offset.
+            long mask = sizeInBytes - 1;
+            if ((curOffset & mask) != 0 || (preOffset & mask) != 0) {
+                return false;
+            }
+        }
+
+        // Merge two ldrs/strs to ldp/stp.
+        Register rt1 = preRt;
+        Register rt2 = curRt;
+        if (curOffset < preOffset) {
+            rt1 = curRt;
+            rt2 = preRt;
+        }
+        int immediate = offset / sizeInBytes;
+        Instruction instruction = isStore ? STP : LDP;
+        int size = sizeInBytes * Byte.SIZE;
+        insertLdpStp(size, instruction, rt1, rt2, curBase, immediate, lastPosition);
+        lastImmLoadStoreEncoding = null;
+        isImmLoadStoreMerged = true;
+        return true;
+    }
+
+    /**
+     * Try to merge two continuous ldr/str to one ldp/stp. If this current ldr/str is not merged,
+     * save it as the last ldr/str.
+     */
+    private boolean tryMergeLoadStore(int srcSize, Register rt, AArch64Address address, boolean isStore) {
+        int sizeInBytes = srcSize / Byte.SIZE;
+        if (tryMerge(sizeInBytes, rt, address, isStore)) {
+            return true;
+        }
+
+        // Save last ldr/str if it is not merged.
+        AArch64Address.AddressingMode addressMode = address.getAddressingMode();
+        if (addressMode == IMMEDIATE_SCALED || addressMode == IMMEDIATE_UNSCALED) {
+            if (addressMode == IMMEDIATE_UNSCALED) {
+                long mask = sizeInBytes - 1;
+                int offset = address.getImmediateRaw();
+                if ((offset & mask) != 0) {
+                    return false;
+                }
+            }
+            lastImmLoadStoreEncoding = new AArch64MemoryEncoding(sizeInBytes, rt, address, isStore, position());
+        }
+        return false;
+    }
+
+    public boolean isImmLoadStoreMerged() {
+        return isImmLoadStoreMerged;
+    }
+
     public void movx(Register dst, Register src) {
         mov(64, dst, src);
     }
@@ -505,7 +674,7 @@
         assert targetSize == 32 || targetSize == 64;
         assert srcSize <= targetSize;
         if (targetSize == srcSize) {
-            super.ldr(srcSize, rt, address);
+            ldr(srcSize, rt, address);
         } else {
             super.ldrs(targetSize, srcSize, rt, address);
         }
@@ -521,7 +690,25 @@
      */
     @Override
     public void ldr(int srcSize, Register rt, AArch64Address address) {
-        super.ldr(srcSize, rt, address);
+        // Try to merge two adjacent loads into one ldp.
+        if (!tryMergeLoadStore(srcSize, rt, address, false)) {
+            super.ldr(srcSize, rt, address);
+        }
+    }
+
+    /**
+     * Stores register rt into memory pointed by address.
+     *
+     * @param destSize number of bits written to memory. Must be 8, 16, 32 or 64.
+     * @param rt general purpose register. May not be null or stackpointer.
+     * @param address all addressing modes allowed. May not be null.
+     */
+    @Override
+    public void str(int destSize, Register rt, AArch64Address address) {
+        // Try to merge two adjacent stores into one stp.
+        if (!tryMergeLoadStore(destSize, rt, address, true)) {
+            super.str(destSize, rt, address);
+        }
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.aarch64.test/src/org/graalvm/compiler/core/aarch64/test/AArch64PairLoadStoreTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, Arm Limited and affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+
+package org.graalvm.compiler.core.aarch64.test;
+
+import jdk.vm.ci.aarch64.AArch64;
+import jdk.vm.ci.runtime.JVMCI;
+import org.graalvm.compiler.core.test.GraalCompilerTest;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assume.assumeTrue;
+
+public class AArch64PairLoadStoreTest extends GraalCompilerTest {
+
+    @Before
+    public void checkAArch64() {
+        assumeTrue("skipping AArch64 specific test", JVMCI.getRuntime().getHostJVMCIBackend().getTarget().arch instanceof AArch64);
+    }
+
+    public static long parameterSpill(long v1, long v2, long v3, long v4, long v5, long v6, long v7, long v8, long v9, long v10) {
+        long value0 = v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8;
+        long value1 = v9 + v10;
+        return value0 + value1;
+    }
+
+    @Test
+    public void testParameterSpill() {
+        test("parameterSpill", 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L);
+    }
+
+    static class A {
+        static String a = "adsaf";
+
+        static String b = "asfgsfd";
+
+        static int c;
+
+        static int d;
+    }
+
+    public static int pairLoadStaticFields() {
+        if (A.a == null || A.a != A.b) {
+            return A.b.length();
+        }
+        return A.a.length();
+    }
+
+    @Test
+    public void testPairLoadStaticFields() {
+        test("pairLoadStaticFields");
+    }
+
+    public static int pairStoreStaticFields(int m, int n) {
+        A.c = m;
+        A.d = n;
+        return A.c + A.d;
+    }
+
+    @Test
+    public void testPairStoreStaticFields() {
+        test("pairStoreStaticFields", 1, 2);
+    }
+}
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/util/UnsafeArrayTypeWriter.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/util/UnsafeArrayTypeWriter.java	Fri Dec 20 04:47:34 2019 +0100
@@ -116,6 +116,11 @@
         return buffer;
     }
 
+    public final byte[] toArray() {
+        byte[] result = new byte[TypeConversion.asS4(getBytesWritten())];
+        return toArray(result);
+    }
+
     @Override
     public final void putS1(long value) {
         long offset = writeOffset(Byte.BYTES);
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/CheckGraalInvariants.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/CheckGraalInvariants.java	Fri Dec 20 04:47:34 2019 +0100
@@ -190,6 +190,13 @@
         public boolean shouldVerifyFoldableMethods() {
             return true;
         }
+
+        /**
+         * Makes edits to the list of verifiers to be run.
+         */
+        @SuppressWarnings("unused")
+        protected void updateVerifiers(List<VerifyPhase<CoreProviders>> verifiers) {
+        }
     }
 
     @Test
@@ -304,6 +311,8 @@
             verifiers.add(foldableMethodsVerifier);
         }
 
+        tool.updateVerifiers(verifiers);
+
         for (Method m : BadUsageWithEquals.class.getDeclaredMethods()) {
             ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
             try (DebugContext debug = DebugContext.create(options, DebugHandlersFactory.LOADER)) {
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/ConditionalEliminationTestBase.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/ConditionalEliminationTestBase.java	Fri Dec 20 04:47:34 2019 +0100
@@ -32,13 +32,11 @@
 import org.graalvm.compiler.nodes.spi.CoreProviders;
 import org.graalvm.compiler.nodes.spi.LoweringTool;
 import org.graalvm.compiler.phases.OptimisticOptimizations;
-import org.graalvm.compiler.phases.OptimisticOptimizations.Optimization;
 import org.graalvm.compiler.phases.common.CanonicalizerPhase;
 import org.graalvm.compiler.phases.common.ConditionalEliminationPhase;
 import org.graalvm.compiler.phases.common.IterativeConditionalEliminationPhase;
 import org.graalvm.compiler.phases.common.LoweringPhase;
 import org.graalvm.compiler.phases.schedule.SchedulePhase;
-import org.graalvm.compiler.phases.tiers.HighTierContext;
 import org.junit.Assert;
 
 /**
@@ -55,8 +53,8 @@
      * code based on method profiles.
      */
     @Override
-    protected HighTierContext getDefaultHighTierContext() {
-        return new HighTierContext(getProviders(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL.remove(Optimization.RemoveNeverExecutedCode));
+    protected OptimisticOptimizations getOptimisticOptimizations() {
+        return OptimisticOptimizations.ALL.remove(OptimisticOptimizations.Optimization.RemoveNeverExecutedCode);
     }
 
     protected void testConditionalElimination(String snippet, String referenceSnippet) {
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/CountedLoopTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/CountedLoopTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -45,7 +45,6 @@
 import org.graalvm.compiler.nodes.spi.NodeLIRBuilderTool;
 import org.graalvm.compiler.nodes.util.GraphUtil;
 import org.graalvm.compiler.phases.OptimisticOptimizations;
-import org.graalvm.compiler.phases.tiers.HighTierContext;
 import org.junit.Test;
 
 import jdk.vm.ci.meta.JavaKind;
@@ -647,9 +646,9 @@
     }
 
     @Override
-    protected HighTierContext getDefaultHighTierContext() {
+    protected OptimisticOptimizations getOptimisticOptimizations() {
         // Don't convert unreached paths into Guard
-        return new HighTierContext(getProviders(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.NONE);
+        return OptimisticOptimizations.ALL.remove(OptimisticOptimizations.Optimization.RemoveNeverExecutedCode);
     }
 
     private Object[] argsToBind;
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/GraalCompilerTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/GraalCompilerTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -54,6 +54,7 @@
 import org.graalvm.compiler.api.directives.GraalDirectives;
 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
 import org.graalvm.compiler.api.test.Graal;
+import org.graalvm.compiler.api.test.ModuleSupport;
 import org.graalvm.compiler.code.CompilationResult;
 import org.graalvm.compiler.core.CompilationPrinter;
 import org.graalvm.compiler.core.GraalCompiler;
@@ -126,7 +127,6 @@
 import org.graalvm.compiler.runtime.RuntimeProvider;
 import org.graalvm.compiler.test.AddExports;
 import org.graalvm.compiler.test.GraalTest;
-import org.graalvm.compiler.api.test.ModuleSupport;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.BeforeClass;
@@ -606,11 +606,21 @@
         return providers;
     }
 
-    protected HighTierContext getDefaultHighTierContext() {
+    /**
+     * Override the {@link OptimisticOptimizations} settings used for the test. This is called for
+     * all the paths where the value is set so it is the proper place for a test override. Setting
+     * it in other places can result in inconsistent values being used in other parts of the
+     * compiler.
+     */
+    protected OptimisticOptimizations getOptimisticOptimizations() {
+        return OptimisticOptimizations.ALL;
+    }
+
+    protected final HighTierContext getDefaultHighTierContext() {
         return new HighTierContext(getProviders(), getDefaultGraphBuilderSuite(), getOptimisticOptimizations());
     }
 
-    protected MidTierContext getDefaultMidTierContext() {
+    protected final MidTierContext getDefaultMidTierContext() {
         return new MidTierContext(getProviders(), getTargetProvider(), getOptimisticOptimizations(), null);
     }
 
@@ -1081,10 +1091,6 @@
         return compile(installedCodeOwner, graph, new CompilationResult(compilationId), compilationId, options);
     }
 
-    protected OptimisticOptimizations getOptimisticOptimizations() {
-        return OptimisticOptimizations.ALL;
-    }
-
     /**
      * Compiles a given method.
      *
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/MergeCanonicalizerTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/MergeCanonicalizerTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -29,8 +29,6 @@
 import org.graalvm.compiler.nodes.StructuredGraph;
 import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
 import org.graalvm.compiler.phases.OptimisticOptimizations;
-import org.graalvm.compiler.phases.OptimisticOptimizations.Optimization;
-import org.graalvm.compiler.phases.tiers.HighTierContext;
 import org.junit.Test;
 
 public class MergeCanonicalizerTest extends GraalCompilerTest {
@@ -39,8 +37,8 @@
      * These tests assume all code paths are reachable so disable profile based dead code removal.
      */
     @Override
-    protected HighTierContext getDefaultHighTierContext() {
-        return new HighTierContext(getProviders(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL.remove(Optimization.RemoveNeverExecutedCode));
+    protected OptimisticOptimizations getOptimisticOptimizations() {
+        return OptimisticOptimizations.ALL.remove(OptimisticOptimizations.Optimization.RemoveNeverExecutedCode);
     }
 
     public static int staticField;
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/OptionsVerifierTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/OptionsVerifierTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -33,6 +33,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.TreeSet;
 
 import org.graalvm.compiler.options.OptionDescriptor;
 import org.graalvm.compiler.options.OptionDescriptors;
@@ -54,12 +55,19 @@
  */
 public class OptionsVerifierTest {
 
+    private static Set<String> WHITELIST = new TreeSet<>(Arrays.asList(//
+                    // Generated options delegating default values to PolyglotCompilerOptions
+                    "org.graalvm.compiler.truffle.compiler.SharedTruffleCompilerOptions"));
+
     @Test
     public void verifyOptions() throws IOException {
         HashSet<Class<?>> checked = new HashSet<>();
         for (OptionDescriptors opts : OptionsParser.getOptionsLoader()) {
             for (OptionDescriptor desc : opts) {
-                OptionsVerifier.checkClass(desc.getDeclaringClass(), desc, checked);
+                Class<?> descDeclaringClass = desc.getDeclaringClass();
+                if (!WHITELIST.contains(descDeclaringClass.getName())) {
+                    OptionsVerifier.checkClass(descDeclaringClass, desc, checked);
+                }
             }
         }
     }
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/SubprocessTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/SubprocessTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -48,7 +48,7 @@
             runnable.run();
         } else {
             List<String> vmArgs = withoutDebuggerArguments(getVMCommandLine());
-            vmArgs.addAll(SubprocessUtil.getPackageOpeningOptions());
+            vmArgs.add(SubprocessUtil.PACKAGE_OPENING_OPTIONS);
             vmArgs.add("-D" + recursionPropName + "=true");
             configSubprocess(vmArgs);
             SubprocessUtil.Subprocess proc = java(vmArgs, "com.oracle.mxtool.junit.MxJUnitWrapper", getClass().getName());
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/VerifyDebugUsage.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/VerifyDebugUsage.java	Fri Dec 20 04:47:34 2019 +0100
@@ -110,7 +110,7 @@
         }
     }
 
-    private void verifyParameters(MethodCallTargetNode callTarget, StructuredGraph callerGraph, NodeInputList<? extends ValueNode> args, ResolvedJavaType stringType, int startArgIdx) {
+    protected void verifyParameters(MethodCallTargetNode callTarget, StructuredGraph callerGraph, NodeInputList<? extends ValueNode> args, ResolvedJavaType stringType, int startArgIdx) {
         if (callTarget.targetMethod().isVarArgs() && args.get(args.count() - 1) instanceof NewArrayNode) {
             // unpack the arguments to the var args
             List<ValueNode> unpacked = new ArrayList<>(args.snapshot());
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/ea/PEAAssertionsTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/ea/PEAAssertionsTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,14 +24,11 @@
 
 package org.graalvm.compiler.core.test.ea;
 
-import org.junit.Test;
-
 import org.graalvm.compiler.api.directives.GraalDirectives;
 import org.graalvm.compiler.code.SourceStackTraceBailoutException;
 import org.graalvm.compiler.core.test.GraalCompilerTest;
 import org.graalvm.compiler.phases.OptimisticOptimizations;
-import org.graalvm.compiler.phases.OptimisticOptimizations.Optimization;
-import org.graalvm.compiler.phases.tiers.HighTierContext;
+import org.junit.Test;
 
 public class PEAAssertionsTest extends GraalCompilerTest {
 
@@ -39,8 +36,8 @@
      * These tests assume all code paths are reachable so disable profile based dead code removal.
      */
     @Override
-    protected HighTierContext getDefaultHighTierContext() {
-        return new HighTierContext(getProviders(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL.remove(Optimization.RemoveNeverExecutedCode));
+    protected OptimisticOptimizations getOptimisticOptimizations() {
+        return OptimisticOptimizations.ALL.remove(OptimisticOptimizations.Optimization.RemoveNeverExecutedCode);
     }
 
     public static Object field;
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.debug/src/org/graalvm/compiler/debug/DebugContext.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.debug/src/org/graalvm/compiler/debug/DebugContext.java	Fri Dec 20 04:47:34 2019 +0100
@@ -53,6 +53,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Formatter;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.SortedMap;
@@ -2161,6 +2162,18 @@
         out.println();
     }
 
+    public Map<MetricKey, Long> getMetricsSnapshot() {
+        Map<MetricKey, Long> res = new HashMap<>();
+        for (MetricKey key : KeyRegistry.getKeys()) {
+            int index = ((AbstractKey) key).getIndex();
+            if (index < metricValues.length && metricValues[index] != 0) {
+                long value = metricValues[index];
+                res.put(key, value);
+            }
+        }
+        return res;
+    }
+
     @SuppressWarnings({"unused", "unchecked"})
     private static <E extends Exception> E rethrowSilently(Class<E> type, Throwable ex) throws E {
         throw (E) ex;
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.aarch64/src/org/graalvm/compiler/hotspot/aarch64/AArch64HotSpotSafepointOp.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.aarch64/src/org/graalvm/compiler/hotspot/aarch64/AArch64HotSpotSafepointOp.java	Fri Dec 20 04:47:34 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -81,7 +81,11 @@
     }
 
     public static void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm, GraalHotSpotVMConfig config, boolean onReturn, Register thread, Register scratch, LIRFrameState state) {
-        emitThreadLocalPoll(crb, masm, config, onReturn, thread, scratch, state);
+        if (config.threadLocalHandshakes) {
+            emitThreadLocalPoll(crb, masm, config, onReturn, thread, scratch, state);
+        } else {
+            emitGlobalPoll(crb, masm, config, onReturn, scratch, state);
+        }
     }
 
     private static void emitGlobalPoll(CompilationResultBuilder crb, AArch64MacroAssembler masm, GraalHotSpotVMConfig config, boolean onReturn, Register scratch, LIRFrameState state) {
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.amd64/src/org/graalvm/compiler/hotspot/amd64/AMD64HotSpotSafepointOp.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.amd64/src/org/graalvm/compiler/hotspot/amd64/AMD64HotSpotSafepointOp.java	Fri Dec 20 04:47:34 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -67,7 +67,12 @@
         this.state = state;
         this.config = config;
         this.thread = thread;
-        temp = tool.getLIRGeneratorTool().newVariable(LIRKind.value(tool.getLIRGeneratorTool().target().arch.getWordKind()));
+        if (config.threadLocalHandshakes || isPollingPageFar(config) || ImmutableCode.getValue(tool.getOptions())) {
+            temp = tool.getLIRGeneratorTool().newVariable(LIRKind.value(tool.getLIRGeneratorTool().target().arch.getWordKind()));
+        } else {
+            // Don't waste a register if it's unneeded
+            temp = Value.ILLEGAL;
+        }
     }
 
     @Override
@@ -76,7 +81,11 @@
     }
 
     public static void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler asm, GraalHotSpotVMConfig config, boolean atReturn, LIRFrameState state, Register thread, Register scratch) {
-        emitThreadLocalPoll(crb, asm, config, atReturn, state, thread, scratch);
+        if (config.threadLocalHandshakes) {
+            emitThreadLocalPoll(crb, asm, config, atReturn, state, thread, scratch);
+        } else {
+            emitGlobalPoll(crb, asm, config, atReturn, state, scratch);
+        }
     }
 
     /**
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.lir.test/src/org/graalvm/compiler/hotspot/lir/test/BenchmarkCounterOverflowTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.lir.test/src/org/graalvm/compiler/hotspot/lir/test/BenchmarkCounterOverflowTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -106,7 +106,7 @@
         List<String> vmArgs = withoutDebuggerArguments(getVMCommandLine());
         vmArgs.add("-XX:JVMCICounterSize=1");
         vmArgs.add("-Dgraal." + BenchmarkCounters.Options.AbortOnBenchmarkCounterOverflow.getName() + "=true");
-        vmArgs.addAll(SubprocessUtil.getPackageOpeningOptions());
+        vmArgs.add(SubprocessUtil.PACKAGE_OPENING_OPTIONS);
         vmArgs.add("-D" + SUBPROCESS_PROPERTY + "=true");
 
         // Disable increment range checks (e.g. HotSpotCounterOp.checkIncrements())
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.sparc.test/src/org/graalvm/compiler/hotspot/sparc/test/SPARCAllocatorTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.sparc.test/src/org/graalvm/compiler/hotspot/sparc/test/SPARCAllocatorTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -47,7 +47,7 @@
 
     @Test
     public void test1() {
-        testAllocation("test1snippet", 1 , 0, 0);
+        testAllocation("test1snippet", config.threadLocalHandshakes ? 1 : 2, 0, 0);
     }
 
     public static long test1snippet(long x) {
@@ -56,7 +56,7 @@
 
     @Test
     public void test2() {
-        testAllocation("test2snippet", 1, 0, 0);
+        testAllocation("test2snippet", config.threadLocalHandshakes ? 1 : 2, 0, 0);
     }
 
     public static long test2snippet(long x) {
@@ -65,7 +65,7 @@
 
     @Test
     public void test3() {
-        testAllocation("test3snippet", 3, 0, 0);
+        testAllocation("test3snippet", config.threadLocalHandshakes ? 3 : 4, 0, 0);
     }
 
     public static long test3snippet(long x) {
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.sparc/src/org/graalvm/compiler/hotspot/sparc/SPARCHotSpotSafepointOp.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.sparc/src/org/graalvm/compiler/hotspot/sparc/SPARCHotSpotSafepointOp.java	Fri Dec 20 04:47:34 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -75,7 +75,11 @@
 
     public static void emitCode(CompilationResultBuilder crb, SPARCMacroAssembler masm, GraalHotSpotVMConfig config, boolean atReturn, LIRFrameState state, Register thread,
                     Value safepointPollAddress) {
-        emitThreadLocalPoll(crb, masm, config, atReturn, state, thread);
+        if (config.threadLocalHandshakes) {
+            emitThreadLocalPoll(crb, masm, config, atReturn, state, thread);
+        } else {
+            emitGlobalPoll(crb, masm, config, atReturn, state, asRegister(safepointPollAddress));
+        }
     }
 
     /**
@@ -113,10 +117,19 @@
     }
 
     static AllocatableValue getSafepointAddressValue(SPARCHotSpotLIRGenerator gen) {
-        return Value.ILLEGAL;
+        if (gen.config.threadLocalHandshakes) {
+            return Value.ILLEGAL;
+        } else {
+            return gen.newVariable(LIRKind.value(gen.target().arch.getWordKind()));
+        }
     }
 
     static void emitPrologue(SPARCHotSpotNodeLIRBuilder lir, SPARCHotSpotLIRGenerator gen) {
+        if (!gen.config.threadLocalHandshakes) {
+            AllocatableValue var = gen.getSafepointAddressValue();
+            lir.append(new SPARCHotSpotSafepointOp.SPARCLoadSafepointPollAddress(var, gen.config));
+            gen.append(((HotSpotDebugInfoBuilder) lir.getDebugInfoBuilder()).lockStack());
+        }
     }
 
     public static class SPARCLoadSafepointPollAddress extends SPARCLIRInstruction {
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.test/src/org/graalvm/compiler/hotspot/test/CompilationWrapperTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.test/src/org/graalvm/compiler/hotspot/test/CompilationWrapperTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -24,7 +24,6 @@
 
 package org.graalvm.compiler.hotspot.test;
 
-import static org.graalvm.compiler.test.SubprocessUtil.getPackageOpeningOptions;
 import static org.graalvm.compiler.test.SubprocessUtil.getVMCommandLine;
 import static org.graalvm.compiler.test.SubprocessUtil.withoutDebuggerArguments;
 
@@ -51,13 +50,6 @@
  */
 public class CompilationWrapperTest extends GraalCompilerTest {
 
-    private static List<String> join(List<String> l1, List<String> l2) {
-        ArrayList<String> result = new ArrayList<>(l1.size() + l2.size());
-        result.addAll(l1);
-        result.addAll(l2);
-        return result;
-    }
-
     /**
      * Tests compilation requested by the VM.
      */
@@ -167,11 +159,11 @@
     public void testTruffleCompilation1() throws IOException, InterruptedException {
         assumeManagementLibraryIsLoadable();
         testHelper(Collections.emptyList(),
-                        join(getPackageOpeningOptions(),
-                                        Arrays.asList(
-                                                        "-Dgraal.CompilationFailureAction=ExitVM",
-                                                        "-Dgraal.TrufflePerformanceWarningsAreFatal=true",
-                                                        "-Dgraal.CrashAt=root test1")),
+                        Arrays.asList(
+                                        SubprocessUtil.PACKAGE_OPENING_OPTIONS,
+                                        "-Dgraal.CompilationFailureAction=ExitVM",
+                                        "-Dgraal.TrufflePerformanceWarningsAreFatal=true",
+                                        "-Dgraal.CrashAt=root test1"),
                         "org.graalvm.compiler.truffle.test.SLTruffleGraalTestSuite", "test");
     }
 
@@ -184,11 +176,11 @@
                         new Probe("Exiting VM due to TruffleCompilationExceptionsAreFatal=true", 1),
         };
         testHelper(Arrays.asList(probes),
-                        join(getPackageOpeningOptions(),
-                                        Arrays.asList(
-                                                        "-Dgraal.CompilationFailureAction=Silent",
-                                                        "-Dgraal.TruffleCompilationExceptionsAreFatal=true",
-                                                        "-Dgraal.CrashAt=root test1")),
+                        Arrays.asList(
+                                        SubprocessUtil.PACKAGE_OPENING_OPTIONS,
+                                        "-Dgraal.CompilationFailureAction=Silent",
+                                        "-Dgraal.TruffleCompilationExceptionsAreFatal=true",
+                                        "-Dgraal.CrashAt=root test1"),
                         "org.graalvm.compiler.truffle.test.SLTruffleGraalTestSuite", "test");
     }
 
@@ -202,11 +194,11 @@
                         new Probe("Exiting VM due to TrufflePerformanceWarningsAreFatal=true", 1),
         };
         testHelper(Arrays.asList(probes),
-                        join(getPackageOpeningOptions(),
-                                        Arrays.asList(
-                                                        "-Dgraal.CompilationFailureAction=Silent",
-                                                        "-Dgraal.TrufflePerformanceWarningsAreFatal=true",
-                                                        "-Dgraal.CrashAt=root test1:PermanentBailout")),
+                        Arrays.asList(
+                                        SubprocessUtil.PACKAGE_OPENING_OPTIONS,
+                                        "-Dgraal.CompilationFailureAction=Silent",
+                                        "-Dgraal.TrufflePerformanceWarningsAreFatal=true",
+                                        "-Dgraal.CrashAt=root test1:PermanentBailout"),
                         "org.graalvm.compiler.truffle.test.SLTruffleGraalTestSuite", "test");
     }
 
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.test/src/org/graalvm/compiler/hotspot/test/ReservedStackAccessTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot.test/src/org/graalvm/compiler/hotspot/test/ReservedStackAccessTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -67,12 +67,13 @@
 
     @Test
     public void run() throws IOException, InterruptedException {
+        Assume.assumeFalse("GR-19833", runtime().getVMConfig().osName.equals("windows"));
         Assume.assumeTrue(runtime().getVMConfig().enableStackReservedZoneAddress != 0);
         List<String> vmArgs = SubprocessUtil.withoutDebuggerArguments(SubprocessUtil.getVMCommandLine());
         vmArgs.add("-XX:+UseJVMCICompiler");
         vmArgs.add("-Dgraal.Inline=false");
         vmArgs.add("-XX:CompileCommand=exclude,java/util/concurrent/locks/AbstractOwnableSynchronizer.setExclusiveOwnerThread");
-        vmArgs.addAll(SubprocessUtil.getPackageOpeningOptions());
+        vmArgs.add(SubprocessUtil.PACKAGE_OPENING_OPTIONS);
 
         // Avoid SOE in HotSpotJVMCIRuntime.adjustCompilationLevel
         vmArgs.add("-Dgraal.CompileGraalWithC1Only=false");
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot/src/org/graalvm/compiler/hotspot/GraalHotSpotVMConfig.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot/src/org/graalvm/compiler/hotspot/GraalHotSpotVMConfig.java	Fri Dec 20 04:47:34 2019 +0100
@@ -96,6 +96,7 @@
     public final boolean useAESCTRIntrinsics = getFlag("UseAESCTRIntrinsics", Boolean.class, false);
     public final boolean useCRC32Intrinsics = getFlag("UseCRC32Intrinsics", Boolean.class);
     public final boolean useCRC32CIntrinsics = versioned.useCRC32CIntrinsics;
+    public final boolean threadLocalHandshakes = versioned.threadLocalHandshakes;
 
     private final boolean useMultiplyToLenIntrinsic = getFlag("UseMultiplyToLenIntrinsic", Boolean.class);
     private final boolean useSHA1Intrinsics = getFlag("UseSHA1Intrinsics", Boolean.class);
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot/src/org/graalvm/compiler/hotspot/GraalHotSpotVMConfigVersioned.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot/src/org/graalvm/compiler/hotspot/GraalHotSpotVMConfigVersioned.java	Fri Dec 20 04:47:34 2019 +0100
@@ -89,4 +89,7 @@
     // JDK-8186777
     int classMirrorOffset = getFieldOffset("Klass::_java_mirror", Integer.class, "OopHandle");
     boolean classMirrorIsHandle = true;
+
+    // JDK-8220049
+    boolean threadLocalHandshakes = true;
 }
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.jtt/src/org/graalvm/compiler/jtt/optimize/ConditionalElimination02.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.jtt/src/org/graalvm/compiler/jtt/optimize/ConditionalElimination02.java	Fri Dec 20 04:47:34 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,14 +26,11 @@
 
 import java.util.EnumSet;
 
-import jdk.vm.ci.meta.DeoptimizationReason;
-
+import org.graalvm.compiler.jtt.JTTTest;
+import org.graalvm.compiler.phases.OptimisticOptimizations;
 import org.junit.Test;
 
-import org.graalvm.compiler.jtt.JTTTest;
-import org.graalvm.compiler.phases.OptimisticOptimizations;
-import org.graalvm.compiler.phases.OptimisticOptimizations.Optimization;
-import org.graalvm.compiler.phases.tiers.HighTierContext;
+import jdk.vm.ci.meta.DeoptimizationReason;
 
 public class ConditionalElimination02 extends JTTTest {
 
@@ -68,8 +65,8 @@
      * These tests assume all code paths are reachable so disable profile based dead code removal.
      */
     @Override
-    protected HighTierContext getDefaultHighTierContext() {
-        return new HighTierContext(getProviders(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL.remove(Optimization.RemoveNeverExecutedCode));
+    protected OptimisticOptimizations getOptimisticOptimizations() {
+        return OptimisticOptimizations.ALL.remove(OptimisticOptimizations.Optimization.RemoveNeverExecutedCode);
     }
 
     @Test
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.lir.aarch64/src/org/graalvm/compiler/lir/aarch64/AArch64Move.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.lir.aarch64/src/org/graalvm/compiler/lir/aarch64/AArch64Move.java	Fri Dec 20 04:47:34 2019 +0100
@@ -255,10 +255,19 @@
 
         @Override
         public void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) {
+            int prePosition = masm.position();
+            emitMemAccess(crb, masm);
             if (state != null) {
-                crb.recordImplicitException(masm.position(), state);
+                int implicitExceptionPosition = prePosition;
+                // Adjust implicit exception position if this ldr/str has been merged to ldp/stp.
+                if (kind.isInteger() && prePosition == masm.position() && masm.isImmLoadStoreMerged()) {
+                    implicitExceptionPosition = prePosition - 4;
+                    if (crb.isImplicitExceptionExist(implicitExceptionPosition)) {
+                        return;
+                    }
+                }
+                crb.recordImplicitException(implicitExceptionPosition, state);
             }
-            emitMemAccess(crb, masm);
         }
 
         @Override
@@ -346,8 +355,17 @@
 
         @Override
         public void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) {
-            crb.recordImplicitException(masm.position(), state);
+            int prePosition = masm.position();
             masm.ldr(64, zr, address.toAddress());
+            int implicitExceptionPosition = prePosition;
+            // Adjust implicit exception position if this ldr has been merged to ldp.
+            if (prePosition == masm.position() && masm.isImmLoadStoreMerged()) {
+                implicitExceptionPosition = prePosition - 4;
+                if (crb.isImplicitExceptionExist(implicitExceptionPosition)) {
+                    return;
+                }
+            }
+            crb.recordImplicitException(implicitExceptionPosition, state);
         }
 
         @Override
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.lir.aarch64/src/org/graalvm/compiler/lir/aarch64/AArch64Unary.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.lir.aarch64/src/org/graalvm/compiler/lir/aarch64/AArch64Unary.java	Fri Dec 20 04:47:34 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -72,9 +72,7 @@
 
         @Override
         public void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) {
-            if (state != null) {
-                crb.recordImplicitException(masm.position(), state);
-            }
+            int prePosition = masm.position();
             AArch64Address address = input.toAddress();
             Register dst = asRegister(result);
             if (isSigned) {
@@ -82,6 +80,18 @@
             } else {
                 masm.ldr(srcSize, dst, address);
             }
+
+            if (state != null) {
+                int implicitExceptionPosition = prePosition;
+                // Adjust implicit exception position if this ldr/str has been merged to ldp/stp.
+                if (prePosition == masm.position() && masm.isImmLoadStoreMerged()) {
+                    implicitExceptionPosition = prePosition - 4;
+                    if (crb.isImplicitExceptionExist(implicitExceptionPosition)) {
+                        return;
+                    }
+                }
+                crb.recordImplicitException(implicitExceptionPosition, state);
+            }
         }
 
         @Override
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.lir/src/org/graalvm/compiler/lir/asm/CompilationResultBuilder.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.lir/src/org/graalvm/compiler/lir/asm/CompilationResultBuilder.java	Fri Dec 20 04:47:34 2019 +0100
@@ -72,6 +72,7 @@
 import jdk.vm.ci.code.TargetDescription;
 import jdk.vm.ci.code.site.ConstantReference;
 import jdk.vm.ci.code.site.DataSectionReference;
+import jdk.vm.ci.code.site.Infopoint;
 import jdk.vm.ci.code.site.InfopointReason;
 import jdk.vm.ci.code.site.Mark;
 import jdk.vm.ci.meta.Constant;
@@ -295,6 +296,16 @@
         assert info.exceptionEdge == null;
     }
 
+    public boolean isImplicitExceptionExist(int pcOffset) {
+        List<Infopoint> infopoints = compilationResult.getInfopoints();
+        for (Infopoint infopoint : infopoints) {
+            if (infopoint.pcOffset == pcOffset && infopoint.reason == InfopointReason.IMPLICIT_EXCEPTION) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public void recordDirectCall(int posBefore, int posAfter, InvokeTarget callTarget, LIRFrameState info) {
         DebugInfo debugInfo = info != null ? info.debugInfo() : null;
         compilationResult.recordCall(posBefore, posAfter - posBefore, callTarget, debugInfo, true);
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/GraphDecoder.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/GraphDecoder.java	Fri Dec 20 04:47:34 2019 +0100
@@ -160,17 +160,70 @@
 
     }
 
+    /**
+     * Marker to distinguish the reasons for the creation of a loop scope during partial evaluation.
+     */
+    public enum LoopScopeTrigger {
+        /**
+         * Start loop scope: creation triggered manually at the beginning of partial evaluation.
+         */
+        START,
+
+        /**
+         * Loop scope created for the next iteration of a loop if unrolling is enabled in the loop
+         * explosion mode. See {@link LoopExplosionKind#unrollLoops()} for details. Loop unrolling
+         * will merge loop end nodes for each iteration of the original loop.
+         */
+        LOOP_BEGIN_UNROLLING,
+
+        /**
+         * Loop scope created for the next iteration of a loop along a particular loop end node if
+         * {@link LoopExplosionKind#duplicateLoopEnds()} is enabled and loops are exploded. This
+         * means for every loop end we duplicate the next loop iteration of the original loop.
+         */
+        LOOP_END_DUPLICATION,
+
+        /**
+         * Loop scope created for a loop exit node if {@link LoopExplosionKind#duplicateLoopExits()}
+         * is enabled, i.e., code after a loop exit is duplicated per loop exit node.
+         *
+         * Special case nested loops: For compilation units with nested loops where inner loops
+         * continue loops at a level n -1 the partial evaluation algorithm will merge outer loops to
+         * avoid loop explosion along loop end nodes (which would be the same as
+         * {@link #LOOP_END_DUPLICATION}.
+         */
+        LOOP_EXIT_DUPLICATION
+    }
+
     /** Decoding state maintained for each loop in the encoded graph. */
     protected static class LoopScope {
         public final MethodScope methodScope;
         public final LoopScope outer;
         public final int loopDepth;
         public final int loopIteration;
+
         /**
-         * Upcoming loop iterations during loop explosions that have not been processed yet. Only
-         * used when {@link MethodScope#loopExplosion} is not {@link LoopExplosionKind#NONE}.
+         * Creation trigger of this particular loop scope, i.e., the reason it was created.
          */
-        public Deque<LoopScope> nextIterations;
+        final LoopScopeTrigger trigger;
+        /**
+         * Upcoming, not yet processed, loop iterations created in the context of code duplication
+         * along loop exits. Only used when {@link MethodScope#loopExplosion} has
+         * {@link LoopExplosionKind#duplicateLoopExits()} enabled.
+         */
+        public Deque<LoopScope> nextIterationFromLoopExitDuplication;
+        /**
+         * Same as {@link #nextIterationFromLoopExitDuplication} except that upcoming iterations
+         * have been created because the duplication of loop ends
+         * {@link LoopExplosionKind#duplicateLoopEnds()} is enabled.
+         */
+        public Deque<LoopScope> nextIterationFromLoopEndDuplication;
+        /**
+         * Same as {@link #nextIterationFromLoopExitDuplication} except that upcoming iterations
+         * have been created because the unrolling of a loop with constant iteration count
+         * {@link LoopExplosionKind#unrollLoops()} is enabled.
+         */
+        public Deque<LoopScope> nextIterationsFromUnrolling;
         /**
          * Information about already processed loop iterations for state merging during loop
          * explosion. Only used when {@link MethodScope#loopExplosion} is
@@ -196,7 +249,9 @@
         protected LoopScope(MethodScope methodScope) {
             this.methodScope = methodScope;
             this.outer = null;
-            this.nextIterations = methodScope.loopExplosion.duplicateLoopExits() ? new ArrayDeque<>(2) : null;
+            this.nextIterationFromLoopExitDuplication = methodScope.loopExplosion.duplicateLoopExits() || methodScope.loopExplosion.mergeLoops() ? new ArrayDeque<>(2) : null;
+            this.nextIterationFromLoopEndDuplication = methodScope.loopExplosion.duplicateLoopEnds() ? new ArrayDeque<>(2) : null;
+            this.nextIterationsFromUnrolling = methodScope.loopExplosion.unrollLoops() ? new ArrayDeque<>(2) : null;
             this.loopDepth = 0;
             this.loopIteration = 0;
             this.iterationStates = null;
@@ -205,15 +260,21 @@
             this.nodesToProcess = new BitSet(methodScope.maxFixedNodeOrderId);
             this.createdNodes = new Node[nodeCount];
             this.initialCreatedNodes = null;
+            this.trigger = LoopScopeTrigger.START;
         }
 
-        protected LoopScope(MethodScope methodScope, LoopScope outer, int loopDepth, int loopIteration, int loopBeginOrderId, Node[] initialCreatedNodes, Node[] createdNodes,
-                        Deque<LoopScope> nextIterations, EconomicMap<LoopExplosionState, LoopExplosionState> iterationStates) {
+        protected LoopScope(MethodScope methodScope, LoopScope outer, int loopDepth, int loopIteration, int loopBeginOrderId, LoopScopeTrigger trigger, Node[] initialCreatedNodes, Node[] createdNodes,
+                        Deque<LoopScope> nextIterationFromLoopExitDuplication,
+                        Deque<LoopScope> nextIterationFromLoopEndDuplication,
+                        Deque<LoopScope> nextIterationsFromUnrolling, EconomicMap<LoopExplosionState, LoopExplosionState> iterationStates) {
             this.methodScope = methodScope;
             this.outer = outer;
             this.loopDepth = loopDepth;
             this.loopIteration = loopIteration;
-            this.nextIterations = nextIterations;
+            this.trigger = trigger;
+            this.nextIterationFromLoopExitDuplication = nextIterationFromLoopExitDuplication;
+            this.nextIterationFromLoopEndDuplication = nextIterationFromLoopEndDuplication;
+            this.nextIterationsFromUnrolling = nextIterationsFromUnrolling;
             this.iterationStates = iterationStates;
             this.loopBeginOrderId = loopBeginOrderId;
             this.nodesToProcess = new BitSet(methodScope.maxFixedNodeOrderId);
@@ -223,7 +284,41 @@
 
         @Override
         public String toString() {
-            return loopDepth + "," + loopIteration + (loopBeginOrderId == -1 ? "" : "#" + loopBeginOrderId);
+            return loopDepth + "," + loopIteration + (loopBeginOrderId == -1 ? "" : "#" + loopBeginOrderId) + " triggered by " + trigger;
+        }
+
+        /**
+         * Determines if iterations generated when decoding this loop have yet to be processed.
+         *
+         * @return {@code true} if there are iterations to be decoded, {@code false} else
+         */
+        public boolean hasIterationsToProcess() {
+            return nextIterationFromLoopEndDuplication != null && !nextIterationFromLoopEndDuplication.isEmpty() ||
+                            nextIterationFromLoopExitDuplication != null && !nextIterationFromLoopExitDuplication.isEmpty() ||
+                            nextIterationsFromUnrolling != null && !nextIterationsFromUnrolling.isEmpty();
+        }
+
+        /**
+         * Return the next iteration yet to be processed that has been created in the context of
+         * decoding this loop scope.
+         *
+         * @param remove determines if the query of the next iteration should remove it from the
+         *            list of iterations to be processed
+         * @return the next {@link LoopScope} to be processed that has been created in the context
+         *         of decoding this loop scope. Note that the order is not necessarily reflecting
+         *         the number of loop iterations.
+         */
+        public LoopScope getNextIterationToProcess(boolean remove) {
+            if (nextIterationFromLoopEndDuplication != null && !nextIterationFromLoopEndDuplication.isEmpty()) {
+                return remove ? nextIterationFromLoopEndDuplication.removeFirst() : nextIterationFromLoopEndDuplication.peekFirst();
+            }
+            if (nextIterationFromLoopExitDuplication != null && !nextIterationFromLoopExitDuplication.isEmpty()) {
+                return remove ? nextIterationFromLoopExitDuplication.removeFirst() : nextIterationFromLoopExitDuplication.peekFirst();
+            }
+            if (nextIterationsFromUnrolling != null && !nextIterationsFromUnrolling.isEmpty()) {
+                return remove ? nextIterationsFromUnrolling.removeFirst() : nextIterationsFromUnrolling.peekFirst();
+            }
+            return null;
         }
     }
 
@@ -408,7 +503,6 @@
 
             /* Process loops of method. */
             while (loopScope != null) {
-
                 /* Process nodes of loop. */
                 while (!loopScope.nodesToProcess.isEmpty()) {
                     loopScope = processNextNode(methodScope, loopScope);
@@ -419,10 +513,8 @@
                 }
 
                 /* Finished with a loop. */
-                if (loopScope.nextIterations != null && !loopScope.nextIterations.isEmpty()) {
-                    /* Loop explosion: process the loop iteration. */
-                    assert loopScope.nextIterations.peekFirst().loopIteration == loopScope.loopIteration + 1;
-                    loopScope = loopScope.nextIterations.removeFirst();
+                if (loopScope.hasIterationsToProcess()) {
+                    loopScope = loopScope.getNextIterationToProcess(true);
                 } else {
                     propagateCreatedNodes(loopScope);
                     loopScope = loopScope.outer;
@@ -461,18 +553,34 @@
         }
     }
 
+    public static final boolean DUMP_DURING_FIXED_NODE_PROCESSING = false;
+
     protected LoopScope processNextNode(MethodScope methodScope, LoopScope loopScope) {
         int nodeOrderId = loopScope.nodesToProcess.nextSetBit(0);
         loopScope.nodesToProcess.clear(nodeOrderId);
 
         FixedNode node = (FixedNode) lookupNode(loopScope, nodeOrderId);
+
         if (node.isDeleted()) {
             return loopScope;
         }
-
+        if (DUMP_DURING_FIXED_NODE_PROCESSING) {
+            if (node != null) {
+                try {
+                    debug.dump(DebugContext.DETAILED_LEVEL, graph, "Before processing node %s", node);
+                } catch (Throwable t) {
+                    // swallow here, dumping uninitialized nodes can cause problems
+                }
+            }
+        }
         if ((node instanceof MergeNode ||
-                        (node instanceof LoopBeginNode && (methodScope.loopExplosion.unrollLoops() && !methodScope.loopExplosion.mergeLoops()))) &&
+                        (node instanceof LoopBeginNode && (methodScope.loopExplosion.unrollLoops() &&
+                                        !methodScope.loopExplosion.mergeLoops()))) &&
                         ((AbstractMergeNode) node).forwardEndCount() == 1) {
+            /*
+             * In case node is a loop begin and we are unrolling loops we remove the loop begin
+             * since the loop will be gone after PE.
+             */
             AbstractMergeNode merge = (AbstractMergeNode) node;
             EndNode singleEnd = merge.forwardEndAt(0);
 
@@ -498,10 +606,15 @@
                  * of the inner loop.
                  */
                 LoopScope outerScope = loopScope.outer;
-                int nextIterationNumber = outerScope.nextIterations.isEmpty() ? outerScope.loopIteration + 1 : outerScope.nextIterations.getLast().loopIteration + 1;
-                successorAddScope = new LoopScope(methodScope, outerScope.outer, outerScope.loopDepth, nextIterationNumber, outerScope.loopBeginOrderId,
+                int nextIterationNumber = outerScope.nextIterationFromLoopExitDuplication.isEmpty() ? outerScope.loopIteration + 1
+                                : outerScope.nextIterationFromLoopExitDuplication.getLast().loopIteration + 1;
+                successorAddScope = new LoopScope(methodScope, outerScope.outer, outerScope.loopDepth, nextIterationNumber, outerScope.loopBeginOrderId, LoopScopeTrigger.LOOP_EXIT_DUPLICATION,
                                 outerScope.initialCreatedNodes == null ? null : Arrays.copyOf(outerScope.initialCreatedNodes, outerScope.initialCreatedNodes.length),
-                                Arrays.copyOf(loopScope.initialCreatedNodes, loopScope.initialCreatedNodes.length), outerScope.nextIterations, outerScope.iterationStates);
+                                Arrays.copyOf(loopScope.initialCreatedNodes, loopScope.initialCreatedNodes.length),
+                                outerScope.nextIterationFromLoopExitDuplication,
+                                outerScope.nextIterationFromLoopEndDuplication,
+                                outerScope.nextIterationsFromUnrolling,
+                                outerScope.iterationStates);
                 checkLoopExplosionIteration(methodScope, successorAddScope);
 
                 /*
@@ -513,7 +626,7 @@
                     successorAddScope.createdNodes[id] = null;
                 }
 
-                outerScope.nextIterations.addLast(successorAddScope);
+                outerScope.nextIterationFromLoopExitDuplication.addLast(successorAddScope);
             } else {
                 successorAddScope = loopScope.outer;
             }
@@ -542,27 +655,91 @@
 
         } else if (node instanceof MergeNode) {
             handleMergeNode(((MergeNode) node));
-
         } else if (node instanceof AbstractEndNode) {
             LoopScope phiInputScope = loopScope;
             LoopScope phiNodeScope = loopScope;
+            int mergeOrderId = readOrderId(methodScope);
 
-            if (methodScope.loopExplosion.useExplosion() && node instanceof LoopEndNode) {
-                node = handleLoopExplosionEnd(methodScope, loopScope, (LoopEndNode) node);
-                phiNodeScope = loopScope.nextIterations.getLast();
+            boolean requiresMergeOfOuterLoop = methodScope.loopExplosion.unrollLoops() &&
+                            methodScope.loopExplosion.duplicateLoopExits() &&
+                            (!methodScope.loopExplosion.duplicateLoopEnds()) &&
+                            (!methodScope.loopExplosion.mergeLoops()) &&
+                            node instanceof LoopEndNode &&
+                            loopScope.trigger == LoopScopeTrigger.LOOP_EXIT_DUPLICATION;
+
+            if (requiresMergeOfOuterLoop) {
+                EndNode replacementNode = graph.add(new EndNode());
+                node.replaceAtPredecessor(replacementNode);
+                node.safeDelete();
+                node = replacementNode;
+                /*
+                 * We are in a loop exit duplicated loop scope and see a loop end node, this can
+                 * only happen if we have a loop end to an outer loop. When duplicating over loop
+                 * exits we have to merge outer loops for nested inner loops.
+                 *
+                 * Therefore, we create a correct outer loop iteration and check if there is already
+                 * one, if not we create it else we re-use it.
+                 */
+                if (loopScope.nextIterationsFromUnrolling.isEmpty()) {
+                    // create it
+                    int nextIterationNumber = loopScope.nextIterationsFromUnrolling.isEmpty() ? loopScope.loopIteration + 1 : loopScope.nextIterationsFromUnrolling.getLast().loopIteration + 1;
+                    LoopScope outerLoopMergeScope = new LoopScope(methodScope, loopScope.outer, loopScope.loopDepth, nextIterationNumber, loopScope.loopBeginOrderId,
+                                    LoopScopeTrigger.LOOP_BEGIN_UNROLLING,
+                                    Arrays.copyOf(loopScope.initialCreatedNodes, loopScope.initialCreatedNodes.length),
+                                    Arrays.copyOf(loopScope.initialCreatedNodes, loopScope.initialCreatedNodes.length),
+                                    loopScope.nextIterationFromLoopExitDuplication,
+                                    loopScope.nextIterationFromLoopEndDuplication,
+                                    loopScope.nextIterationsFromUnrolling,
+                                    loopScope.iterationStates);
+                    checkLoopExplosionIteration(methodScope, outerLoopMergeScope);
+                    loopScope.nextIterationsFromUnrolling.addLast(outerLoopMergeScope);
+                    registerNode(outerLoopMergeScope, loopScope.loopBeginOrderId, null, true, true);
+                    makeStubNode(methodScope, outerLoopMergeScope, loopScope.loopBeginOrderId);
+                    phiNodeScope = outerLoopMergeScope;
+                } else {
+                    // re-use it
+                    phiNodeScope = loopScope.nextIterationsFromUnrolling.getLast();
+                }
+
+            } else if (methodScope.loopExplosion.useExplosion() && node instanceof LoopEndNode) {
+                EndNode replacementNode = graph.add(new EndNode());
+                node.replaceAtPredecessor(replacementNode);
+                node.safeDelete();
+                node = replacementNode;
+                LoopScopeTrigger trigger = handleLoopExplosionEnd(methodScope, loopScope);
+                Deque<LoopScope> phiScope = loopScope.nextIterationsFromUnrolling;
+                if (trigger == LoopScopeTrigger.LOOP_END_DUPLICATION) {
+                    phiScope = loopScope.nextIterationFromLoopEndDuplication;
+                }
+                phiNodeScope = phiScope.getLast();
             }
-
-            int mergeOrderId = readOrderId(methodScope);
             AbstractMergeNode merge = (AbstractMergeNode) lookupNode(phiNodeScope, mergeOrderId);
             if (merge == null) {
                 merge = (AbstractMergeNode) makeStubNode(methodScope, phiNodeScope, mergeOrderId);
-
                 if (merge instanceof LoopBeginNode) {
+                    /*
+                     * In contrast to the LoopScopeTrigger.START created at the beginning of every
+                     * PE, we see a real loop here and create the first real loop scope associated
+                     * with a loop.
+                     *
+                     * Creation of a loop scope if we reach a loop begin node. We process a loop
+                     * begin node (always before encountering a loop end associated with the loop
+                     * begin) and simply create a normal loop scope. This does not imply an advanced
+                     * unrolling strategy (however it can later if we see duplicate over loop end or
+                     * exits). Therefore, we still use the start marker here, we could also use the
+                     * unrolling marker.
+                     *
+                     * If we unroll loops we will later remove the loop begin node and replace it
+                     * with its forward end (since we do not need to create a loop begin node if we
+                     * unroll the entire loop and it has a constant trip count).
+                     */
                     assert phiNodeScope == phiInputScope && phiNodeScope == loopScope;
-                    resultScope = new LoopScope(methodScope, loopScope, loopScope.loopDepth + 1, 0, mergeOrderId,
+                    resultScope = new LoopScope(methodScope, loopScope, loopScope.loopDepth + 1, 0, mergeOrderId, LoopScopeTrigger.START,
                                     methodScope.loopExplosion.useExplosion() ? Arrays.copyOf(loopScope.createdNodes, loopScope.createdNodes.length) : null,
                                     methodScope.loopExplosion.useExplosion() ? Arrays.copyOf(loopScope.createdNodes, loopScope.createdNodes.length) : loopScope.createdNodes, //
-                                    methodScope.loopExplosion.useExplosion() ? new ArrayDeque<>(2) : null, //
+                                    methodScope.loopExplosion.duplicateLoopExits() || methodScope.loopExplosion.mergeLoops() ? new ArrayDeque<>(2) : null,
+                                    methodScope.loopExplosion.duplicateLoopEnds() ? new ArrayDeque<>(2) : null,
+                                    methodScope.loopExplosion.unrollLoops() ? new ArrayDeque<>(2) : null, //
                                     methodScope.loopExplosion.mergeLoops() ? EconomicMap.create(Equivalence.DEFAULT) : null);
                     phiInputScope = resultScope;
                     phiNodeScope = resultScope;
@@ -574,9 +751,7 @@
                     resultScope.nodesToProcess.set(mergeOrderId);
                 }
             }
-
             handlePhiFunctions(methodScope, phiInputScope, phiNodeScope, (AbstractEndNode) node, merge);
-
         } else if (node instanceof Invoke) {
             InvokeData invokeData = readInvokeData(methodScope, nodeOrderId, (Invoke) node);
             resultScope = handleInvoke(methodScope, loopScope, invokeData);
@@ -585,7 +760,15 @@
         } else {
             handleFixedNode(methodScope, loopScope, nodeOrderId, node);
         }
-
+        if (DUMP_DURING_FIXED_NODE_PROCESSING) {
+            if (node != null) {
+                try {
+                    debug.dump(DebugContext.DETAILED_LEVEL, graph, "After processing node %s", node);
+                } catch (Throwable t) {
+                    // swallow here, dumping uninitialized nodes can cause problems
+                }
+            }
+        }
         return resultScope;
     }
 
@@ -738,23 +921,44 @@
         throw shouldNotReachHere("when subclass uses loop explosion, it needs to implement this method");
     }
 
-    protected FixedNode handleLoopExplosionEnd(MethodScope methodScope, LoopScope loopScope, LoopEndNode loopEnd) {
-        EndNode replacementNode = graph.add(new EndNode());
-        loopEnd.replaceAtPredecessor(replacementNode);
-        loopEnd.safeDelete();
-
-        assert methodScope.loopExplosion.useExplosion();
-        if (methodScope.loopExplosion.duplicateLoopEnds() || loopScope.nextIterations.isEmpty()) {
-            int nextIterationNumber = loopScope.nextIterations.isEmpty() ? loopScope.loopIteration + 1 : loopScope.nextIterations.getLast().loopIteration + 1;
-            LoopScope nextIterationScope = new LoopScope(methodScope, loopScope.outer, loopScope.loopDepth, nextIterationNumber, loopScope.loopBeginOrderId,
+    protected LoopScopeTrigger handleLoopExplosionEnd(MethodScope methodScope, LoopScope loopScope) {
+        /*
+         * This method is only called if we reach a loop end and we use some kind of loop explosion,
+         * i.e., we unroll loops or explode along loop ends.
+         */
+        LoopScopeTrigger trigger = null;
+        Deque<LoopScope> nextIterations = null;
+        if (methodScope.loopExplosion.duplicateLoopEnds()) {
+            /*
+             * Loop explosion along loop ends: We see a loop end, however we do not merge all loop
+             * ends at a common merge node but rather duplicate the rest of the loop for every loop
+             * end.
+             */
+            trigger = LoopScopeTrigger.LOOP_END_DUPLICATION;
+            nextIterations = loopScope.nextIterationFromLoopEndDuplication;
+        } else if (loopScope.nextIterationsFromUnrolling.isEmpty()) {
+            /*
+             * Regular loop unrolling, i.e., we reach a loop end node of a loop that should be
+             * unrolled: We create a new successor scope.
+             */
+            trigger = LoopScopeTrigger.LOOP_BEGIN_UNROLLING;
+            nextIterations = loopScope.nextIterationsFromUnrolling;
+        }
+        if (trigger != null) {
+            int nextIterationNumber = nextIterations.isEmpty() ? loopScope.loopIteration + 1 : nextIterations.getLast().loopIteration + 1;
+            LoopScope nextIterationScope = new LoopScope(methodScope, loopScope.outer, loopScope.loopDepth, nextIterationNumber, loopScope.loopBeginOrderId, trigger,
                             Arrays.copyOf(loopScope.initialCreatedNodes, loopScope.initialCreatedNodes.length),
-                            Arrays.copyOf(loopScope.initialCreatedNodes, loopScope.initialCreatedNodes.length), loopScope.nextIterations, loopScope.iterationStates);
+                            Arrays.copyOf(loopScope.initialCreatedNodes, loopScope.initialCreatedNodes.length),
+                            loopScope.nextIterationFromLoopExitDuplication,
+                            loopScope.nextIterationFromLoopEndDuplication,
+                            loopScope.nextIterationsFromUnrolling,
+                            loopScope.iterationStates);
             checkLoopExplosionIteration(methodScope, nextIterationScope);
-            loopScope.nextIterations.addLast(nextIterationScope);
+            nextIterations.addLast(nextIterationScope);
             registerNode(nextIterationScope, loopScope.loopBeginOrderId, null, true, true);
             makeStubNode(methodScope, nextIterationScope, loopScope.loopBeginOrderId);
         }
-        return replacementNode;
+        return trigger;
     }
 
     /**
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/classfile/RedefineIntrinsicTest.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/classfile/RedefineIntrinsicTest.java	Fri Dec 20 04:47:34 2019 +0100
@@ -117,7 +117,7 @@
         } else {
             List<String> vmArgs = withoutDebuggerArguments(getVMCommandLine());
             vmArgs.add("-D" + recursionPropName + "=true");
-            vmArgs.addAll(SubprocessUtil.getPackageOpeningOptions());
+            vmArgs.add(SubprocessUtil.PACKAGE_OPENING_OPTIONS);
             vmArgs.add("-Djdk.attach.allowAttachSelf=true");
             Subprocess proc = java(vmArgs, "com.oracle.mxtool.junit.MxJUnitWrapper", getClass().getName());
             if (proc.exitCode != 0) {
--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.test/src/org/graalvm/compiler/test/SubprocessUtil.java	Thu Nov 21 17:10:26 2019 -0300
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.test/src/org/graalvm/compiler/test/SubprocessUtil.java	Fri Dec 20 04:47:34 2019 +0100
@@ -29,6 +29,8 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Formatter;
@@ -47,6 +49,14 @@
  */
 public final class SubprocessUtil {
 
+    /**
+     * The name of the boolean system property that can be set to preserve temporary files created
+     * as arguments files passed to the java launcher.
+     *
+     * @see "https://docs.oracle.com/javase/9/tools/java.htm#JSWOR-GUID-4856361B-8BFD-4964-AE84-121F5F6CF111"
+     */
+    public static final String KEEP_TEMPORARY_ARGUMENT_FILES_PROPERTY_NAME = "test." + SubprocessUtil.class.getSimpleName() + ".keepTempArgumentFiles";
+
     private SubprocessUtil() {
     }
 
@@ -184,8 +194,14 @@
          */
         public final List<String> output;
 
-        public Subprocess(List<String> command, int exitCode, List<String> output) {
+        /**
+         * Explicit environment variables.
+         */
+        private Map<String, String> env;
+
+        public Subprocess(List<String> command, Map<String, String> env, int exitCode, List<String> output) {
             this.command = command;
+            this.env = env;
             this.exitCode = exitCode;
             this.output = output;
         }
@@ -202,6 +218,13 @@
             if (delimiter != null) {
                 msg.format("%s%n", delimiter);
             }
+            if (env != null && !env.isEmpty()) {
+                msg.format("env");
+                for (Map.Entry<String, String> e : env.entrySet()) {
+                    msg.format(" %s=%s", e.getKey(), quoteShellArg(e.getValue()));
+                }
+                msg.format("\\%n");
+            }
             msg.format("%s%n", CollectionsUtil.mapAndJoin(command, e -> quoteShellArg(String.valueOf(e)), " "));
             for (String line : output) {
                 msg.format("%s%n", line);
@@ -223,6 +246,14 @@
     }
 
     /**
+     * A sentinel value which when present in the {@code vmArgs} parameter for any of the
+     * {@code java(...)} methods in this class is replaced with a temporary argument file containing
+     * the contents of {@link #getPackageOpeningOptions}. The argument file is preserved if the
+     * {@link #KEEP_TEMPORARY_ARGUMENT_FILES_PROPERTY_NAME} system property is true.
+     */
+    public static final String PACKAGE_OPENING_OPTIONS = ";:PACKAGE_OPENING_OPTIONS_IN_TEMPORARY_ARGUMENTS_FILE:;";
+
+    /**
      * Executes a Java subprocess.
      *
      * @param vmArgs the VM arguments
@@ -272,7 +303,22 @@
      * @param mainClassAndArgs the main class and its arguments
      */
     private static Subprocess javaHelper(List<String> vmArgs, Map<String, String> env, List<String> mainClassAndArgs) throws IOException, InterruptedException {
-        List<String> command = new ArrayList<>(vmArgs);
+        List<String> command = new ArrayList<>(vmArgs.size());
+        Path packageOpeningOptionsArgumentsFile = null;
+        for (String vmArg : vmArgs) {
+            if (vmArg == PACKAGE_OPENING_OPTIONS) {
+                if (packageOpeningOptionsArgumentsFile == null) {
+                    List<String> packageOpeningOptions = getPackageOpeningOptions();
+                    if (!packageOpeningOptions.isEmpty()) {
+                        packageOpeningOptionsArgumentsFile = Files.createTempFile(Paths.get("."), "package-opening-options-arguments-file", ".txt").toAbsolutePath();
+                        Files.write(packageOpeningOptionsArgumentsFile, packageOpeningOptions);
+                        command.add("@" + packageOpeningOptionsArgumentsFile);
+                    }
+                }
+            } else {
+                command.add(vmArg);
+            }
+        }
         command.addAll(mainClassAndArgs);
         ProcessBuilder processBuilder = new ProcessBuilder(command);
         if (env != null) {
@@ -280,14 +326,22 @@
             processBuilderEnv.putAll(env);
         }
         processBuilder.redirectErrorStream(true);
-        Process process = processBuilder.start();
-        BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
-        String line;
-        List<String> output = new ArrayList<>();
-        while ((line = stdout.readLine()) != null) {
-            output.add(line);
+        try {
+            Process process = processBuilder.start();
+            BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            List<String> output = new ArrayList<>();
+            while ((line = stdout.readLine()) != null) {
+                output.add(line);
+            }
+            return new Subprocess(command, env, process.waitFor(), output);
+        } finally {
+            if (packageOpeningOptionsArgumentsFile != null) {
+                if (!Boolean.getBoolean(KEEP_TEMPORARY_ARGUMENT_FILES_PROPERTY_NAME)) {
+                    Files.delete(packageOpeningOptionsArgumentsFile);
+                }
+            }
         }
-        return new Subprocess(command, process.waitFor(), output);
     }
 
     private static final boolean isJava8OrEarlier = JavaVersionUtil.JAVA_SPEC <= 8;
--- a/test/jdk/ProblemList-graal.txt	Thu Nov 21 17:10:26 2019 -0300
+++ b/test/jdk/ProblemList-graal.txt	Fri Dec 20 04:47:34 2019 +0100
@@ -34,7 +34,7 @@
 
 java/lang/ref/OOMEInReferenceHandler.java                               8196611   generic-all
 java/lang/ref/SoftReference/Pin.java                                    8196611   generic-all
-java/lang/Runtime/exec/LotsOfOutput.java                                8196611   generic-all
+java/lang/RuntimeTests/exec/LotsOfOutput.java                           8196611   generic-all
 java/util/concurrent/ScheduledThreadPoolExecutor/BasicCancelTest.java   8196611   generic-all
 com/sun/crypto/provider/KeyFactory/TestProviderLeak.java                8196611   generic-all