Skip to content

Commit e4c5e6f

Browse files
committed
Use static field for native symbol cache if possible
1 parent 7e49d46 commit e4c5e6f

File tree

2 files changed

+72
-17
lines changed

2 files changed

+72
-17
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262

6363
import org.graalvm.collections.EconomicMap;
6464
import org.graalvm.collections.Pair;
65+
import org.graalvm.nativeimage.ImageInfo;
6566

6667
import com.oracle.graal.python.PythonLanguage;
6768
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
@@ -199,7 +200,16 @@ public final class CApiContext extends CExtContext {
199200
public Object timezoneType;
200201
private PyCapsule pyDateTimeCAPICapsule;
201202

202-
/** A cache for C symbols. */
203+
/**
204+
* Same as {@link #nativeSymbolCache} if there is only one context per JVM (i.e. just one engine
205+
* in single-context mode). Will be {@code null} in case of multiple contexts.
206+
*/
207+
@CompilationFinal(dimensions = 1) private static Object[] nativeSymbolCacheSingleContext;
208+
private static final AtomicBoolean STATIC_SYMBOL_CACHE_USED = new AtomicBoolean();
209+
210+
/**
211+
* A private (i.e. per-context) cache of C API symbols (usually helper functions).
212+
*/
203213
private final Object[] nativeSymbolCache;
204214

205215
private record ClosureInfo(Object closure, Object delegate, Object executable, long pointer) {
@@ -242,6 +252,33 @@ public CApiContext(PythonContext context, Object llvmLibrary, boolean useNativeB
242252
super(context, llvmLibrary, useNativeBackend);
243253
this.nativeSymbolCache = new Object[NativeCAPISymbol.values().length];
244254

255+
/*
256+
* Publish the native symbol cache to the static field if following is given: (1) The static
257+
* field hasn't been used by another instance yet (i.e. '!used'), and (2) we are in
258+
* single-context mode. This initialization ensures that if
259+
* 'CApiContext.nativeSymbolCacheSingleContext != null', the context is safe to use it and
260+
* just needs to do a null check.
261+
*/
262+
if (!STATIC_SYMBOL_CACHE_USED.compareAndExchange(false, true) && context.getLanguage().isSingleContext()) {
263+
assert CApiContext.nativeSymbolCacheSingleContext == null;
264+
265+
// we cannot be in built-time code because this is using pre-initialized contexts
266+
assert !ImageInfo.inImageBuildtimeCode();
267+
268+
// this is the first context accessing the static symbol cache
269+
CApiContext.nativeSymbolCacheSingleContext = this.nativeSymbolCache;
270+
} else if (CApiContext.nativeSymbolCacheSingleContext != null) {
271+
/*
272+
* In this case, this context instance is at least the second one attempting to use the
273+
* static symbol cache. We now clear the static field to indicate that every context
274+
* instance should use its private cache. If a former context already used the cache and
275+
* there is already compiled code, it is not necessary to invalidate the code because
276+
* the cache is still valid.
277+
*/
278+
CApiContext.nativeSymbolCacheSingleContext = null;
279+
}
280+
assert STATIC_SYMBOL_CACHE_USED.get();
281+
245282
// initialize primitive native wrapper cache
246283
primitiveNativeWrapperCache = new PrimitiveNativeWrapper[262];
247284
for (int i = 0; i < primitiveNativeWrapperCache.length; i++) {
@@ -391,19 +428,44 @@ public Object getModuleByIndex(int i) {
391428
return null;
392429
}
393430

394-
Object getNativeSymbol(NativeCAPISymbol symbol) {
431+
/**
432+
* Retrieves the C API symbol cache instance in the fastest possible way. If there is just one
433+
* instance of {@link CApiContext}, it will load the cache stored from the static field
434+
* {@link CApiContext#nativeSymbolCacheSingleContext}. Otherwise, it will load the cache from
435+
* the instance field {@link CApiContext#nativeSymbolCache}.
436+
*
437+
* @param caller The requesting node (may be {@code null}). Used for the fast-path lookup of the
438+
* {@link CApiContext} instance (if necessary).
439+
* @return The C API symbol cache.
440+
*/
441+
private static Object[] getSymbolCache(Node caller) {
442+
Object[] nativeSymbolCacheSingleContext = CApiContext.nativeSymbolCacheSingleContext;
443+
if (nativeSymbolCacheSingleContext != null) {
444+
return nativeSymbolCacheSingleContext;
445+
}
446+
return PythonContext.get(caller).getCApiContext().nativeSymbolCache;
447+
}
448+
449+
static Object getNativeSymbol(Node caller, NativeCAPISymbol symbol) {
450+
Object[] nativeSymbolCache = getSymbolCache(caller);
395451
Object result = nativeSymbolCache[symbol.ordinal()];
396-
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.SLOWPATH_PROBABILITY, result == null)) {
397-
result = lookupNativeSymbol(symbol);
452+
if (result == null) {
453+
CompilerDirectives.transferToInterpreterAndInvalidate();
454+
result = lookupNativeSymbol(nativeSymbolCache, symbol);
398455
}
456+
assert result != null;
399457
return result;
400458
}
401459

402-
@TruffleBoundary
403-
private Object lookupNativeSymbol(NativeCAPISymbol symbol) {
460+
/**
461+
* Lookup the given C API symbol in the library, store it to the provided cache, and return the
462+
* callable symbol.
463+
*/
464+
private static Object lookupNativeSymbol(Object[] nativeSymbolCache, NativeCAPISymbol symbol) {
465+
CompilerAsserts.neverPartOfCompilation();
404466
String name = symbol.getName();
405467
try {
406-
Object nativeSymbol = InteropLibrary.getUncached().readMember(getLLVMLibrary(), name);
468+
Object nativeSymbol = InteropLibrary.getUncached().readMember(PythonContext.get(null).getCApiContext().getLLVMLibrary(), name);
407469
return nativeSymbolCache[symbol.ordinal()] = CExtContext.ensureExecutable(nativeSymbol, symbol);
408470
} catch (UnsupportedMessageException | UnknownIdentifierException e) {
409471
throw CompilerDirectives.shouldNotReachHere(e);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2032,16 +2032,9 @@ public final Object execute(Node inliningTarget, NativeCAPISymbol symbol) {
20322032

20332033
public abstract Object execute(Node inliningTarget, CApiContext nativeContext, NativeCAPISymbol symbol);
20342034

2035-
@Specialization(guards = {"isSingleContext()", "cachedSymbol == symbol"}, limit = "1")
2036-
static Object doCached(@SuppressWarnings("unused") Node inliningTarget, @SuppressWarnings("unused") CApiContext nativeContext, @SuppressWarnings("unused") NativeCAPISymbol symbol,
2037-
@Cached("symbol") @SuppressWarnings("unused") NativeCAPISymbol cachedSymbol,
2038-
@Cached(value = "nativeContext.getNativeSymbol(symbol)", weak = true) Object llvmSymbol) {
2039-
return llvmSymbol;
2040-
}
2041-
2042-
@Specialization(replaces = "doCached")
2043-
static Object doGeneric(CApiContext nativeContext, NativeCAPISymbol symbol) {
2044-
return nativeContext.getNativeSymbol(symbol);
2035+
@Specialization
2036+
static Object doGeneric(Node inliningTarget, @SuppressWarnings("unused") CApiContext nativeContext, NativeCAPISymbol symbol) {
2037+
return CApiContext.getNativeSymbol(inliningTarget, symbol);
20452038
}
20462039
}
20472040
}

0 commit comments

Comments
 (0)