Skip to content

Commit 0e2816d

Browse files
committed
[GR-38639] Lazily walk continuation stacks during GC (if still reachable).
PullRequest: graal/11836
2 parents b85c8cd + b8755b8 commit 0e2816d

File tree

17 files changed

+443
-566
lines changed

17 files changed

+443
-566
lines changed

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@ private void walkStack(JavaStackWalk walk) {
902902
}
903903

904904
CodeInfoAccess.lookupCodeInfo(codeInfo, CodeInfoAccess.relativeIP(codeInfo, ip), queryResult);
905-
assert Deoptimizer.checkDeoptimized(sp) == null : "We are at a safepoint, so no deoptimization can have happened even though looking up the code info is not uninterruptible";
905+
assert Deoptimizer.checkDeoptimized(sp) == null : "We are at a safepoint, so no deoptimization can have happened";
906906

907907
NonmovableArray<Byte> referenceMapEncoding = CodeInfoAccess.getStackReferenceMapEncoding(codeInfo);
908908
long referenceMapIndex = queryResult.getReferenceMapIndex();

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FrameAccess.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ public static FrameAccess singleton() {
5151
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
5252
public abstract void writeReturnAddress(Pointer sourceSp, CodePointer newReturnAddress);
5353

54+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
55+
public abstract Pointer getReturnAddressLocation(Pointer sourceSp);
56+
5457
@Fold
5558
public static int returnAddressSize() {
5659
Architecture arch = ConfigurationValues.getTarget().arch;

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/aarch64/AArch64FrameAccess.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public class AArch64FrameAccess extends FrameAccess {
5050
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
5151
public CodePointer readReturnAddress(Pointer sourceSp) {
5252
/* Read the return address, which is stored immediately below the stack pointer. */
53-
return (CodePointer) sourceSp.readWord(-returnAddressSize());
53+
return sourceSp.readWord(-returnAddressSize());
5454
}
5555

5656
@Override
@@ -59,6 +59,11 @@ public void writeReturnAddress(Pointer sourceSp, CodePointer newReturnAddress) {
5959
sourceSp.writeWord(-returnAddressSize(), newReturnAddress);
6060
}
6161

62+
@Override
63+
public Pointer getReturnAddressLocation(Pointer sourceSp) {
64+
return sourceSp.subtract(returnAddressSize());
65+
}
66+
6267
@Fold
6368
@Override
6469
public int savedBasePointerSize() {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/amd64/AMD64FrameAccess.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public final class AMD64FrameAccess extends FrameAccess {
5353
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
5454
public CodePointer readReturnAddress(Pointer sourceSp) {
5555
/* Read the return address, which is stored just below the stack pointer. */
56-
return (CodePointer) sourceSp.readWord(-returnAddressSize());
56+
return sourceSp.readWord(-returnAddressSize());
5757
}
5858

5959
@Override
@@ -62,6 +62,12 @@ public void writeReturnAddress(Pointer sourceSp, CodePointer newReturnAddress) {
6262
sourceSp.writeWord(-returnAddressSize(), newReturnAddress);
6363
}
6464

65+
@Override
66+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
67+
public Pointer getReturnAddressLocation(Pointer sourceSp) {
68+
return sourceSp.subtract(returnAddressSize());
69+
}
70+
6571
@Fold
6672
@Override
6773
public int savedBasePointerSize() {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/InstanceReferenceMapDecoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static boolean walkOffsetsFromPointer(Pointer baseAddress, NonmovableArra
4444
assert referenceMapEncoding.isNonNull();
4545

4646
if (Continuation.isSupported() && referenceMapIndex == ReferenceMapIndex.STORED_CONTINUATION) {
47-
return StoredContinuationImpl.walkStoredContinuationFromPointer(baseAddress, null, visitor, holderObject);
47+
return StoredContinuationAccess.walkReferences(baseAddress, visitor, holderObject);
4848
}
4949

5050
Pointer position = NonmovableByteArrayReader.pointerTo(referenceMapEncoding, referenceMapIndex);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/StoredContinuation.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,10 @@
2626

2727
import com.oracle.svm.core.annotate.Hybrid;
2828

29-
/**
30-
* This class is used for variably-sized objects that store continuation stack frames.
31-
*
32-
* For object layout and other implementation details, see {@link StoredContinuationImpl}.
33-
*/
34-
@Hybrid(componentType = byte.class)
29+
/** Execution state of a continuation, use via {@link StoredContinuationAccess}. */
30+
@Hybrid(componentType = long.class)
3531
public final class StoredContinuation {
36-
/** Must be allocated via {@link StoredContinuationImpl}. */
32+
/** Must be allocated via {@link StoredContinuationAccess}. */
3733
private StoredContinuation() {
3834
}
3935
}
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
/*
2+
* Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.core.heap;
26+
27+
import org.graalvm.compiler.api.directives.GraalDirectives;
28+
import org.graalvm.compiler.graph.Node.NodeIntrinsic;
29+
import org.graalvm.compiler.nodes.java.ArrayLengthNode;
30+
import org.graalvm.compiler.word.Word;
31+
import org.graalvm.nativeimage.ImageSingletons;
32+
import org.graalvm.nativeimage.IsolateThread;
33+
import org.graalvm.nativeimage.StackValue;
34+
import org.graalvm.nativeimage.c.function.CodePointer;
35+
import org.graalvm.word.Pointer;
36+
import org.graalvm.word.UnsignedWord;
37+
import org.graalvm.word.WordFactory;
38+
39+
import com.oracle.svm.core.UnmanagedMemoryUtil;
40+
import com.oracle.svm.core.annotate.AlwaysInline;
41+
import com.oracle.svm.core.annotate.Uninterruptible;
42+
import com.oracle.svm.core.c.NonmovableArray;
43+
import com.oracle.svm.core.code.CodeInfo;
44+
import com.oracle.svm.core.code.CodeInfoAccess;
45+
import com.oracle.svm.core.code.CodeInfoTable;
46+
import com.oracle.svm.core.code.FrameInfoQueryResult;
47+
import com.oracle.svm.core.code.SimpleCodeInfoQueryResult;
48+
import com.oracle.svm.core.code.UntetheredCodeInfo;
49+
import com.oracle.svm.core.deopt.DeoptimizedFrame;
50+
import com.oracle.svm.core.deopt.Deoptimizer;
51+
import com.oracle.svm.core.graal.nodes.SubstrateNewHybridInstanceNode;
52+
import com.oracle.svm.core.hub.LayoutEncoding;
53+
import com.oracle.svm.core.snippets.KnownIntrinsics;
54+
import com.oracle.svm.core.stack.JavaStackWalk;
55+
import com.oracle.svm.core.stack.JavaStackWalker;
56+
import com.oracle.svm.core.stack.StackFrameVisitor;
57+
import com.oracle.svm.core.thread.Continuation;
58+
import com.oracle.svm.core.thread.ContinuationSupport;
59+
import com.oracle.svm.core.thread.Safepoint;
60+
import com.oracle.svm.core.util.UnsignedUtils;
61+
import com.oracle.svm.core.util.VMError;
62+
63+
/** Helper for allocating and accessing {@link StoredContinuation} instances. */
64+
public final class StoredContinuationAccess {
65+
private static final int IP_OFFSET = 0; // instruction pointer of top frame
66+
private static final int FRAMES_OFFSET = IP_OFFSET + Long.BYTES;
67+
68+
private StoredContinuationAccess() {
69+
}
70+
71+
private static StoredContinuation allocate(int framesSize) {
72+
// Using long[] to ensure that words are properly aligned.
73+
int nlongs = Integer.divideUnsigned(FRAMES_OFFSET + framesSize, Long.BYTES);
74+
StoredContinuation s = (StoredContinuation) SubstrateNewHybridInstanceNode.allocate(StoredContinuation.class, long.class, nlongs);
75+
assert getFramesSizeInBytes(s) == framesSize;
76+
return s;
77+
}
78+
79+
@NodeIntrinsic(ArrayLengthNode.class)
80+
private static native int arrayLength(StoredContinuation s);
81+
82+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
83+
private static int getSizeInBytes(StoredContinuation s) {
84+
return arrayLength(s) * Long.BYTES;
85+
}
86+
87+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
88+
public static int getFramesSizeInBytes(StoredContinuation s) {
89+
return getSizeInBytes(s) - FRAMES_OFFSET;
90+
}
91+
92+
@Uninterruptible(reason = "Prevent GC during accesses via object address.", callerMustBe = true)
93+
private static Pointer arrayAddress(StoredContinuation s) {
94+
int layout = KnownIntrinsics.readHub(s).getLayoutEncoding();
95+
UnsignedWord baseOffset = LayoutEncoding.getArrayBaseOffset(layout);
96+
return Word.objectToUntrackedPointer(s).add(baseOffset);
97+
}
98+
99+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
100+
public static CodePointer getIP(StoredContinuation s) {
101+
return arrayAddress(s).readWord(IP_OFFSET);
102+
}
103+
104+
@Uninterruptible(reason = "Prevent GC during accesses via object address.", callerMustBe = true)
105+
public static Pointer getFramesStart(StoredContinuation s) {
106+
return arrayAddress(s).add(FRAMES_OFFSET);
107+
}
108+
109+
public static int allocateToYield(Continuation c, Pointer baseSp, Pointer sp, CodePointer ip) {
110+
assert sp.isNonNull() && ip.isNonNull();
111+
return allocateFromStack(c, baseSp, sp, ip, WordFactory.nullPointer());
112+
}
113+
114+
public static int allocateToPreempt(Continuation c, Pointer baseSp, IsolateThread targetThread) {
115+
return allocateFromStack(c, baseSp, WordFactory.nullPointer(), WordFactory.nullPointer(), targetThread);
116+
}
117+
118+
private static int allocateFromStack(Continuation cont, Pointer baseSp, Pointer sp, CodePointer ip, IsolateThread targetThread) {
119+
boolean yield = sp.isNonNull();
120+
assert yield == ip.isNonNull() && yield == targetThread.isNull();
121+
assert baseSp.isNonNull();
122+
123+
Pointer startSp = sp;
124+
CodePointer startIp = ip;
125+
if (!yield) {
126+
PreemptVisitor visitor = new PreemptVisitor(baseSp);
127+
JavaStackWalker.walkThread(targetThread, visitor);
128+
if (visitor.preemptStatus != Continuation.YIELD_SUCCESS) {
129+
return visitor.preemptStatus;
130+
}
131+
startSp = visitor.leafSP;
132+
startIp = visitor.leafIP;
133+
}
134+
135+
VMError.guarantee(startSp.isNonNull());
136+
137+
int framesSize = UnsignedUtils.safeToInt(baseSp.subtract(startSp));
138+
StoredContinuation instance = allocate(framesSize);
139+
fillUninterruptibly(instance, startIp, startSp, framesSize);
140+
cont.stored = instance;
141+
return Continuation.YIELD_SUCCESS;
142+
}
143+
144+
@Uninterruptible(reason = "Prevent modifications to the stack while initializing instance and copying frames.")
145+
private static void fillUninterruptibly(StoredContinuation stored, CodePointer ip, Pointer sp, int size) {
146+
arrayAddress(stored).writeWord(IP_OFFSET, ip);
147+
UnmanagedMemoryUtil.copy(sp, getFramesStart(stored), WordFactory.unsigned(size));
148+
afterFill(stored);
149+
}
150+
151+
@Uninterruptible(reason = "Prevent modifications to the stack while initializing instance.")
152+
private static void afterFill(StoredContinuation stored) {
153+
/*
154+
* Since its allocation, our StoredContinuation could have already been promoted to the old
155+
* generation and some references we just copied might point to the young generation and
156+
* need to be added to the remembered set.
157+
*
158+
* To support precise marking and pre-write barriers, we need to check first if the object
159+
* needs barriers, then, on a slow path, individually copy references from stack frames.
160+
*/
161+
// Drop type info to not trigger compiler assertions about StoredContinuation in barriers
162+
Object opaque = GraalDirectives.opaque(stored);
163+
Heap.getHeap().dirtyAllReferencesOf(opaque);
164+
}
165+
166+
public static StoredContinuation clone(StoredContinuation cont) {
167+
StoredContinuation clone = allocate(getFramesSizeInBytes(cont));
168+
return fillCloneUninterruptibly(cont, clone);
169+
}
170+
171+
@Uninterruptible(reason = "Prevent garbage collection while initializing instance and copying frames.")
172+
private static StoredContinuation fillCloneUninterruptibly(StoredContinuation cont, StoredContinuation clone) {
173+
CodePointer ip = ImageSingletons.lookup(ContinuationSupport.class).copyFrames(cont, getFramesStart(clone));
174+
// copyFrames() above may do something interruptible before uninterruptibly copying frames,
175+
// so set IP only afterwards so that the object is considered uninitialized until then.
176+
arrayAddress(clone).writeWord(IP_OFFSET, ip);
177+
afterFill(clone);
178+
return clone;
179+
}
180+
181+
/** Derived from {@link InstanceReferenceMapDecoder#walkOffsetsFromPointer}. */
182+
@AlwaysInline("De-virtualize calls to ObjectReferenceVisitor")
183+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
184+
public static boolean walkReferences(Pointer baseAddress, ObjectReferenceVisitor visitor, Object holderObject) {
185+
assert !Heap.getHeap().isInImageHeap(baseAddress);
186+
187+
StoredContinuation s = (StoredContinuation) holderObject;
188+
assert baseAddress.equal(Word.objectToUntrackedPointer(holderObject));
189+
190+
CodePointer startIp = getIP(s);
191+
if (startIp.isNull()) {
192+
return true; // uninitialized, ignore
193+
}
194+
195+
Pointer startSp = getFramesStart(s);
196+
Pointer endSp = arrayAddress(s).add(getSizeInBytes(s));
197+
198+
JavaStackWalk walk = StackValue.get(JavaStackWalk.class);
199+
JavaStackWalker.initWalk(walk, startSp, endSp, startIp);
200+
201+
SimpleCodeInfoQueryResult queryResult = StackValue.get(SimpleCodeInfoQueryResult.class);
202+
do {
203+
Pointer sp = walk.getSP();
204+
CodePointer ip = walk.getPossiblyStaleIP();
205+
206+
UntetheredCodeInfo untetheredCodeInfo = walk.getIPCodeInfo();
207+
Object tether = CodeInfoAccess.acquireTether(untetheredCodeInfo);
208+
try {
209+
CodeInfo codeInfo = CodeInfoAccess.convert(untetheredCodeInfo);
210+
VMError.guarantee(codeInfo.equal(CodeInfoTable.getImageCodeInfo()));
211+
VMError.guarantee(Deoptimizer.checkDeoptimized(sp) == null);
212+
if (codeInfo.isNull()) {
213+
throw JavaStackWalker.reportUnknownFrameEncountered(sp, ip, null);
214+
}
215+
216+
CodeInfoAccess.lookupCodeInfo(codeInfo, CodeInfoAccess.relativeIP(codeInfo, ip), queryResult);
217+
218+
NonmovableArray<Byte> referenceMapEncoding = CodeInfoAccess.getStackReferenceMapEncoding(codeInfo);
219+
long referenceMapIndex = queryResult.getReferenceMapIndex();
220+
if (referenceMapIndex != ReferenceMapIndex.NO_REFERENCE_MAP) {
221+
CodeReferenceMapDecoder.walkOffsetsFromPointer(sp, referenceMapEncoding, referenceMapIndex, visitor, holderObject);
222+
}
223+
} finally {
224+
CodeInfoAccess.releaseTether(untetheredCodeInfo, tether);
225+
}
226+
} while (JavaStackWalker.continueWalk(walk, queryResult, null));
227+
228+
return true;
229+
}
230+
231+
private static final class PreemptVisitor extends StackFrameVisitor {
232+
private final Pointer endSP;
233+
private boolean startFromNextFrame = false;
234+
235+
Pointer leafSP;
236+
CodePointer leafIP;
237+
int preemptStatus = Continuation.YIELD_SUCCESS;
238+
239+
PreemptVisitor(Pointer endSP) {
240+
this.endSP = endSP;
241+
}
242+
243+
@Override
244+
protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame) {
245+
if (sp.aboveOrEqual(endSP)) {
246+
return false;
247+
}
248+
249+
FrameInfoQueryResult frameInfo = CodeInfoTable.lookupCodeInfoQueryResult(codeInfo, ip).getFrameInfo();
250+
if (frameInfo.getSourceClass().equals(StoredContinuationAccess.class) && frameInfo.getSourceMethodName().equals("allocateToYield")) {
251+
// Continuation is already in the process of yielding, cancel preemption.
252+
preemptStatus = Continuation.YIELDING;
253+
return false;
254+
}
255+
256+
if (leafSP.isNull()) {
257+
// Should start from the method calling `enterSlowPathSafepointCheck`.
258+
if (startFromNextFrame) {
259+
leafSP = sp;
260+
leafIP = ip;
261+
} else {
262+
if (frameInfo.getSourceClass().equals(Safepoint.class) && frameInfo.getSourceMethodName().equals("enterSlowPathSafepointCheck")) {
263+
startFromNextFrame = true;
264+
}
265+
return true;
266+
}
267+
}
268+
269+
VMError.guarantee(codeInfo.equal(CodeInfoTable.getImageCodeInfo()));
270+
271+
return true;
272+
}
273+
}
274+
}

0 commit comments

Comments
 (0)