Skip to content

Commit 260ade4

Browse files
committed
Avoid interop lock in single-threaded multi-context case.
1 parent 549760e commit 260ade4

File tree

7 files changed

+78
-16
lines changed

7 files changed

+78
-16
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ public final class PythonLanguage extends TruffleLanguage<PythonContext> {
133133

134134
public final Assumption singleContextAssumption = Truffle.getRuntime().createAssumption("Only a single context is active");
135135

136+
/**
137+
* This assumption will be valid if all contexts are single-threaded. Hence, it will be
138+
* invalidated as soon as at least one context has been initialized for multi-threading.
139+
*/
140+
public final Assumption singleThreadedAssumption = Truffle.getRuntime().createAssumption("Only a single thread is active");
141+
136142
private final NodeFactory nodeFactory;
137143
private final ConcurrentHashMap<String, RootCallTarget> builtinCallTargetCache = new ConcurrentHashMap<>();
138144

@@ -621,6 +627,7 @@ protected boolean isThreadAccessAllowed(Thread thread, boolean singleThreaded) {
621627

622628
@Override
623629
protected void initializeMultiThreading(PythonContext context) {
630+
singleThreadedAssumption.invalidate();
624631
context.initializeMultiThreading();
625632
}
626633

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinConstructors.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@
221221
import com.oracle.graal.python.nodes.util.CastToJavaStringNodeGen;
222222
import com.oracle.graal.python.nodes.util.SplitArgsNode;
223223
import com.oracle.graal.python.runtime.ExecutionContext.ForeignCallContext;
224+
import com.oracle.graal.python.runtime.ExecutionContextFactory;
225+
import com.oracle.graal.python.runtime.ExecutionContextFactory.ForeignCallContextNodeGen;
224226
import com.oracle.graal.python.runtime.PythonContext;
225227
import com.oracle.graal.python.runtime.PythonCore;
226228
import com.oracle.graal.python.runtime.exception.PException;
@@ -2126,6 +2128,7 @@ public abstract static class TypeNode extends PythonBuiltinNode {
21262128
@Child private IsSubtypeNode isSubtypeNode;
21272129
@Child private GetObjectArrayNode getObjectArrayNode;
21282130
@Child private IsAcceptableBaseNode isAcceptableBaseNode;
2131+
@Child private ForeignCallContext foreignCallContext;
21292132

21302133
protected abstract Object execute(VirtualFrame frame, Object cls, Object name, Object bases, Object dict, PKeyword[] kwds);
21312134

@@ -2391,7 +2394,7 @@ private PythonClass typeMetaclass(VirtualFrame frame, String name, PTuple bases,
23912394
// Make slots into a tuple
23922395
}
23932396
PythonContext context = getContextRef().get();
2394-
Object state = ForeignCallContext.enter(frame, context, this);
2397+
Object state = ensureForeignCallContext().enter(frame, context, this);
23952398
try {
23962399
pythonClass.setAttribute(__SLOTS__, slotsObject);
23972400
if (basesArray.length > 1) {
@@ -2407,7 +2410,7 @@ private PythonClass typeMetaclass(VirtualFrame frame, String name, PTuple bases,
24072410
addNativeSlots(pythonClass, newSlots);
24082411
}
24092412
} finally {
2410-
ForeignCallContext.exit(frame, context, state);
2413+
ensureForeignCallContext().exit(frame, context, state);
24112414
}
24122415
}
24132416

@@ -2788,6 +2791,14 @@ private IsAcceptableBaseNode ensureIsAcceptableBaseNode() {
27882791
}
27892792
return isAcceptableBaseNode;
27902793
}
2794+
2795+
private ForeignCallContext ensureForeignCallContext() {
2796+
if (foreignCallContext == null) {
2797+
CompilerDirectives.transferToInterpreterAndInvalidate();
2798+
foreignCallContext = insert(ForeignCallContextNodeGen.create());
2799+
}
2800+
return foreignCallContext;
2801+
}
27912802
}
27922803

27932804
@Builtin(name = MODULE, minNumOfPositionalArgs = 1, takesVarArgs = true, takesVarKeywordArgs = true, constructsClass = PythonBuiltinClassType.PythonModule, isPublic = false)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ExternalFunctionNodes.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
import com.oracle.graal.python.nodes.truffle.PythonTypes;
8686
import com.oracle.graal.python.runtime.ExecutionContext.CalleeContext;
8787
import com.oracle.graal.python.runtime.ExecutionContext.ForeignCallContext;
88+
import com.oracle.graal.python.runtime.ExecutionContextFactory.ForeignCallContextNodeGen;
8889
import com.oracle.graal.python.runtime.PythonContext;
8990
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
9091
import com.oracle.graal.python.util.PythonUtils;
@@ -199,6 +200,7 @@ static final class ExternalFunctionInvokeNode extends PNodeWithContext implement
199200
@Child private ToJavaStealingNode asPythonObjectNode = ToJavaStealingNodeGen.create();
200201
@Child private InteropLibrary lib;
201202
@Child private PRaiseNode raiseNode;
203+
@Child private ForeignCallContext foreignCallContext;
202204

203205
@CompilationFinal private Assumption nativeCodeDoesntNeedExceptionState = Truffle.getRuntime().createAssumption();
204206
@CompilationFinal private Assumption nativeCodeDoesntNeedMyFrame = Truffle.getRuntime().createAssumption();
@@ -249,7 +251,7 @@ public Object execute(VirtualFrame frame, String name, Object callable, Object[]
249251

250252
// If any code requested the caught exception (i.e. used 'sys.exc_info()'), we store
251253
// it to the context since we cannot propagate it through the native frames.
252-
Object state = ForeignCallContext.enter(frame, ctx, this);
254+
Object state = ensureForeignCallContext().enter(frame, ctx, this);
253255

254256
try {
255257
return fromNative(asPythonObjectNode.execute(checkResultNode.execute(name, lib.execute(callable, cArguments))));
@@ -263,7 +265,7 @@ public Object execute(VirtualFrame frame, String name, Object callable, Object[]
263265
// special case after calling a C function: transfer caught exception back to frame
264266
// to simulate the global state semantics
265267
PArguments.setException(frame, ctx.getCaughtException());
266-
ForeignCallContext.exit(frame, ctx, state);
268+
ensureForeignCallContext().exit(frame, ctx, state);
267269
}
268270
}
269271

@@ -287,6 +289,14 @@ private PythonContext getContext() {
287289
return contextRef.get();
288290
}
289291

292+
private ForeignCallContext ensureForeignCallContext() {
293+
if (foreignCallContext == null) {
294+
CompilerDirectives.transferToInterpreterAndInvalidate();
295+
foreignCallContext = insert(ForeignCallContextNodeGen.create());
296+
}
297+
return foreignCallContext;
298+
}
299+
290300
public static ExternalFunctionInvokeNode create() {
291301
return new ExternalFunctionInvokeNode();
292302
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,15 @@ public abstract static class CreateDynamic extends PythonBuiltinNode {
194194
@Child private LookupAndCallUnaryNode callReprNode = LookupAndCallUnaryNode.create(SpecialMethodNames.__REPR__);
195195

196196
@Specialization
197-
@SuppressWarnings("try")
198197
public Object run(VirtualFrame frame, PythonObject moduleSpec, @SuppressWarnings("unused") Object filename,
198+
@Cached ForeignCallContext foreignCallContext,
199199
@CachedLibrary(limit = "1") InteropLibrary interop) {
200200
PythonContext context = getContextRef().get();
201-
Object state = ForeignCallContext.enter(frame, context, this);
201+
Object state = foreignCallContext.enter(frame, context, this);
202202
try {
203203
return run(moduleSpec, interop);
204204
} finally {
205-
ForeignCallContext.exit(frame, context, state);
205+
foreignCallContext.exit(frame, context, state);
206206
}
207207
}
208208

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/hpy/HPyExternalFunctionNodes.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ abstract static class HPyExternalFunctionInvokeNode extends Node implements Indi
121121
Object doIt(VirtualFrame frame, String name, Object callable, Object[] frameArgs,
122122
@CachedLibrary("callable") InteropLibrary lib,
123123
@CachedContext(PythonLanguage.class) PythonContext ctx,
124+
@Cached ForeignCallContext foreignCallContext,
124125
@Cached HPyEnsureHandleNode ensureHandleNode,
125126
@Cached HPyCheckFunctionResultNode checkFunctionResultNode,
126127
@Cached HPyAsPythonObjectNode asPythonObjectNode,
@@ -134,7 +135,7 @@ Object doIt(VirtualFrame frame, String name, Object callable, Object[] frameArgs
134135

135136
// If any code requested the caught exception (i.e. used 'sys.exc_info()'), we store
136137
// it to the context since we cannot propagate it through the native frames.
137-
Object state = ForeignCallContext.enter(frame, ctx, this);
138+
Object state = foreignCallContext.enter(frame, ctx, this);
138139

139140
try {
140141
GraalHPyHandle resultHandle = ensureHandleNode.execute(hPyContext, lib.execute(callable, arguments));
@@ -147,7 +148,7 @@ Object doIt(VirtualFrame frame, String name, Object callable, Object[] frameArgs
147148
// special case after calling a C function: transfer caught exception back to frame
148149
// to simulate the global state semantics
149150
PArguments.setException(frame, ctx.getCaughtException());
150-
ForeignCallContext.exit(frame, ctx, state);
151+
foreignCallContext.exit(frame, ctx, state);
151152
}
152153
}
153154

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignObjectBuiltins.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ protected Object doInteropCall(VirtualFrame frame, Object callee, Object[] argum
590590
@SuppressWarnings("unused") @CachedLibrary("callee") PythonObjectLibrary plib,
591591
@CachedLibrary("callee") InteropLibrary lib,
592592
@CachedContext(PythonLanguage.class) PythonContext context,
593+
@Cached ForeignCallContext foreignCallContext,
593594
@Cached PTypeToForeignNode toForeignNode,
594595
@Cached PForeignToPTypeNode toPTypeNode) {
595596
try {
@@ -598,7 +599,7 @@ protected Object doInteropCall(VirtualFrame frame, Object callee, Object[] argum
598599
convertedArgs[i] = toForeignNode.executeConvert(arguments[i]);
599600
}
600601
Object res = null;
601-
Object state = ForeignCallContext.enter(frame, context, this);
602+
Object state = foreignCallContext.enter(frame, context, this);
602603
try {
603604
if (lib.isExecutable(callee)) {
604605
res = lib.execute(callee, convertedArgs);
@@ -608,7 +609,7 @@ protected Object doInteropCall(VirtualFrame frame, Object callee, Object[] argum
608609
return toPTypeNode.executeConvert(res);
609610
}
610611
} finally {
611-
ForeignCallContext.exit(frame, context, state);
612+
foreignCallContext.exit(frame, context, state);
612613
}
613614
} catch (ArityException | UnsupportedTypeException | UnsupportedMessageException e) {
614615
throw raise(PythonErrorType.TypeError, ErrorMessages.INVALID_INSTANTIATION_OF_FOREIGN_OBJ);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/ExecutionContext.java

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,29 @@
4141

4242
package com.oracle.graal.python.runtime;
4343

44+
import com.oracle.graal.python.PythonLanguage;
4445
import com.oracle.graal.python.builtins.objects.frame.PFrame;
4546
import com.oracle.graal.python.builtins.objects.frame.PFrame.Reference;
4647
import com.oracle.graal.python.builtins.objects.function.PArguments;
4748
import com.oracle.graal.python.nodes.IndirectCallNode;
49+
import com.oracle.graal.python.nodes.PNodeWithContext;
4850
import com.oracle.graal.python.nodes.PRootNode;
4951
import com.oracle.graal.python.nodes.control.TopLevelExceptionHandler;
5052
import com.oracle.graal.python.nodes.frame.MaterializeFrameNode;
5153
import com.oracle.graal.python.nodes.frame.MaterializeFrameNodeGen;
5254
import com.oracle.graal.python.nodes.frame.ReadCallerFrameNode;
5355
import com.oracle.graal.python.nodes.util.ExceptionStateNodes.GetCaughtExceptionNode;
5456
import com.oracle.graal.python.runtime.exception.PException;
57+
import com.oracle.truffle.api.Assumption;
5558
import com.oracle.truffle.api.CompilerAsserts;
5659
import com.oracle.truffle.api.CompilerDirectives;
5760
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
5861
import com.oracle.truffle.api.CompilerDirectives.ValueType;
5962
import com.oracle.truffle.api.RootCallTarget;
63+
import com.oracle.truffle.api.dsl.Cached;
64+
import com.oracle.truffle.api.dsl.CachedContext;
65+
import com.oracle.truffle.api.dsl.CachedLanguage;
66+
import com.oracle.truffle.api.dsl.Specialization;
6067
import com.oracle.truffle.api.frame.Frame;
6168
import com.oracle.truffle.api.frame.FrameInstance;
6269
import com.oracle.truffle.api.frame.VirtualFrame;
@@ -377,7 +384,28 @@ public static void exit(VirtualFrame frame, PythonContext context, Object savedS
377384
}
378385
}
379386

380-
public abstract static class ForeignCallContext {
387+
public abstract static class ForeignCallContext extends PNodeWithContext {
388+
389+
/**
390+
* Return {@code true} if we need to acquire/release the interop lock.
391+
*/
392+
protected abstract boolean execute(PythonContext context);
393+
394+
@Specialization(assumptions = "singleThreadedAssumption()")
395+
static boolean doEnterSingleThreaded(@SuppressWarnings("unused") PythonContext context) {
396+
return false;
397+
}
398+
399+
@Specialization(guards = "singleThreadedAssumption == context.getSingleThreadedAssumption()", limit = "3", replaces = "doEnterSingleThreaded")
400+
static boolean doEnterMultiContextCached(@SuppressWarnings("unused") PythonContext context,
401+
@Cached("context.getSingleThreadedAssumption()") Assumption singleThreadedAssumption) {
402+
return !singleThreadedAssumption.isValid();
403+
}
404+
405+
@Specialization(replaces = "doEnterMultiContextCached")
406+
static boolean doEnterMultiContextMultiThreaded(@SuppressWarnings("unused") PythonContext context) {
407+
return true;
408+
}
381409

382410
/**
383411
* Prepare a call from a Python frame to foreign callable. This will also call
@@ -409,11 +437,11 @@ public abstract static class ForeignCallContext {
409437
* </pre>
410438
* </p>
411439
*/
412-
public static Object enter(VirtualFrame frame, PythonContext context, IndirectCallNode callNode) {
440+
public final Object enter(VirtualFrame frame, PythonContext context, IndirectCallNode callNode) {
413441
if (context == null) {
414442
return null;
415443
}
416-
if (!context.getSingleThreadedAssumption().isValid()) {
444+
if (execute(context)) {
417445
context.acquireInteropLock();
418446
}
419447
return IndirectCallContext.enter(frame, context, callNode);
@@ -422,14 +450,18 @@ public static Object enter(VirtualFrame frame, PythonContext context, IndirectCa
422450
/**
423451
* Cleanup after an interop call. For more details, see {@link #enter}.
424452
*/
425-
public static void exit(VirtualFrame frame, PythonContext context, Object savedState) {
453+
public final void exit(VirtualFrame frame, PythonContext context, Object savedState) {
426454
if (context != null) {
427455
IndirectCallContext.exit(frame, context, savedState);
428-
if (!context.getSingleThreadedAssumption().isValid()) {
456+
if (execute(context)) {
429457
context.releaseInteropLock();
430458
}
431459
}
432460
}
461+
462+
static Assumption singleThreadedAssumption() {
463+
return PythonLanguage.getCurrent().singleThreadedAssumption;
464+
}
433465
}
434466

435467
public abstract static class IndirectCalleeContext {

0 commit comments

Comments
 (0)