All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.polyglot.PolyglotFastThreadLocals Maven / Gradle / Ivy

Go to download

Truffle is a multi-language framework for executing dynamic languages that achieves high performance when combined with Graal.

There is a newer version: 24.1.1
Show newest version
/*
 * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.oracle.truffle.polyglot;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.ContextReference;
import com.oracle.truffle.api.TruffleLanguage.LanguageReference;
import com.oracle.truffle.api.impl.AbstractFastThreadLocal;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.polyglot.EngineAccessor.AbstractClassLoaderSupplier;
import com.oracle.truffle.polyglot.EngineAccessor.EngineImpl;

// 0: PolyglotThreadInfo
// 1: PolyglotContextImpl
// 2 + (languageIndex * 2 + 0): language context impl for fast access
// 2 + (languageIndex * 2 + 1): language spi for fast access
final class PolyglotFastThreadLocals {

    private static final AbstractFastThreadLocal IMPL = EngineAccessor.RUNTIME.getContextThreadLocal();
    private static final ConcurrentHashMap, Map> CLASS_NAME_CACHE = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap, CachedReferences> CONTEXT_REFERENCE_CACHE = new ConcurrentHashMap<>();

    private static final FinalIntMap LANGUAGE_INDEXES = new FinalIntMap();
    private static final int RESERVED_NULL = -1; // never set
    private static final int THREAD_INDEX = 0;
    static final int CONTEXT_INDEX = 1;
    private static final int LANGUAGE_START = 2;

    static final int LANGUAGE_CONTEXT_OFFSET = 0;
    static final int LANGUAGE_SPI_OFFSET = 1;
    private static final int LANGUAGE_ELEMENTS = 2;

    static void resetNativeImageState() {
        CONTEXT_REFERENCE_CACHE.clear();
        CLASS_NAME_CACHE.clear();
    }

    static Object[] createFastThreadLocals(PolyglotThreadInfo thread) {
        PolyglotContextImpl context = thread.context;
        assert Thread.holdsLock(context);
        Object[] data = new Object[LANGUAGE_START + (thread.context.engine.languages.length * LANGUAGE_ELEMENTS)];
        data[THREAD_INDEX] = thread;
        data[CONTEXT_INDEX] = thread.context;
        for (PolyglotLanguageContext languageContext : thread.context.contexts) {
            if (languageContext.isCreated()) {
                updateLanguageObjects(data, languageContext);
            }
        }
        return data;
    }

    private static Object[] createFastThreadLocalsForLanguage(PolyglotLanguageInstance instance) {
        Object[] data = new Object[LANGUAGE_START + (instance.language.engine.languages.length * LANGUAGE_ELEMENTS)];
        data[THREAD_INDEX] = null; // not available if only engine is entered
        data[CONTEXT_INDEX] = null; // not available if only engine is entered

        // we take the first language we find. should we fail maybe if there is more than one?
        data[getLanguageIndex(instance) + LANGUAGE_SPI_OFFSET] = instance.spi;
        return data;
    }

    private static int getLanguageIndex(PolyglotLanguageInstance instance) {
        return LANGUAGE_START + (instance.language.cache.getStaticIndex() * LANGUAGE_ELEMENTS);
    }

    @SuppressWarnings({"unchecked"})
    public static > LanguageReference createLanguageReference(Node legacyNode, Class> language) {
        LanguageReference ref = createLanguageReference(language);
        if (legacyNode != null) {
            return new LegacyLanguageReference<>(legacyNode, ref);
        }
        return ref;
    }

    @SuppressWarnings({"unchecked"})
    private static > LanguageReference createLanguageReference(Class> language) {
        return (LanguageReference) lookupReferences(language).languageReference;
    }

    @SuppressWarnings("unchecked")
    @TruffleBoundary
    public static  ContextReference createContextReference(Node legacyNode, Class> language) {
        ContextReference ref = createContextReference(language);
        if (legacyNode != null) {
            return new LegacyContextReference<>(legacyNode, ref);
        }
        return ref;
    }

    @SuppressWarnings("unchecked")
    private static  ContextReference createContextReference(Class> language) {
        return (ContextReference) lookupReferences(language).contextReference;
    }

    public static boolean needsEnter(PolyglotContextImpl context) {
        return IMPL.fastGet(CONTEXT_INDEX, PolyglotContextImpl.class, false) != context;
    }

    public static Object[] enter(PolyglotThreadInfo threadInfo) {
        Object[] prev = IMPL.get();
        IMPL.set(threadInfo.fastThreadLocals);
        return prev;
    }

    public static void leave(Object[] prev) {
        IMPL.set(prev);
    }

    public static Object enterLanguage(PolyglotLanguageInstance language) {
        Object[] prev = IMPL.get();
        IMPL.set(createFastThreadLocalsForLanguage(language));
        return prev;
    }

    public static void leaveLanguage(PolyglotLanguageInstance instance, Object prev) {
        assert IMPL.get()[getLanguageIndex(instance) + LANGUAGE_SPI_OFFSET] != null : "language not entered";
        IMPL.set((Object[]) prev);
    }

    public static void cleanup(Object[] threadLocals) {
        Arrays.fill(threadLocals, null);
    }

    public static PolyglotThreadInfo getCurrentThread(PolyglotEngineImpl inEngine) {
        if (CompilerDirectives.inCompiledCode() && inEngine != null) {
            PolyglotContextImpl singleContext = inEngine.singleContextValue.getConstant();
            if (singleContext != null && CompilerDirectives.isPartialEvaluationConstant(singleContext)) {
                PolyglotThreadInfo constantThread = singleContext.singleThreadValue.getConstant();
                if (constantThread != null) {
                    return constantThread;
                }
            }
        }
        return IMPL.fastGet(THREAD_INDEX, PolyglotThreadInfo.class, true);
    }

    public static PolyglotContextImpl getContext(PolyglotEngineImpl inEngine) {
        if (CompilerDirectives.inCompiledCode() && inEngine != null) {
            Object value = inEngine.singleContextValue.getConstant();
            if (value != null) {
                return (PolyglotContextImpl) value;
            }
        }
        return IMPL.fastGet(CONTEXT_INDEX, PolyglotContextImpl.class, true);
    }

    @SuppressWarnings("unchecked")
    public static TruffleLanguage getLanguage(Node node, int index, Class languageClass) {
        assert validSharing(node);
        if (CompilerDirectives.inCompiledCode()) {
            PolyglotLanguageInstance instance = resolveLanguageInstance(node, index);
            if (instance != null) {
                return instance.spi;
            }
        }
        return (TruffleLanguage) IMPL.fastGet(index, languageClass, true);
    }

    public static Object getLanguageContext(Node node, int index) {
        assert validSharing(node);
        Class contextClass = null;
        if (CompilerDirectives.inCompiledCode()) {
            PolyglotLanguageInstance instance = resolveLanguageInstance(node, index);
            if (instance != null) {
                PolyglotLanguageContext languageContext = instance.singleLanguageContext.getConstant();
                if (languageContext != null) {
                    return languageContext.getContextImpl();
                }
            }
            contextClass = findContextClass(node, index);
        }
        return IMPL.fastGet(index, contextClass, true);
    }

    private static boolean validSharing(Node node) {
        PolyglotEngineImpl engine = resolveEngine(node);
        if (engine == null) {
            return true;
        }
        PolyglotContextImpl context = getContext(null);
        if (context == null) {
            // no current context, let us be optimistic that this is fine
            return true;
        }
        if (context.engine != engine) {
            throw EngineImpl.invalidSharingError(context.engine);
        }
        return true;
    }

    private static CachedReferences lookupReferences(Class> language) {
        return CONTEXT_REFERENCE_CACHE.computeIfAbsent(language, (c) -> new CachedReferences(c));
    }

    static void notifyLanguageCreated(PolyglotLanguageContext languageContext) {
        assert Thread.holdsLock(languageContext.context);
        for (PolyglotThreadInfo threadInfo : languageContext.context.getSeenThreads().values()) {
            updateLanguageObjects(threadInfo.fastThreadLocals, languageContext);
        }
    }

    private static void updateLanguageObjects(Object[] data, PolyglotLanguageContext languageContext) {
        PolyglotLanguageInstance instance = languageContext.getLanguageInstance();
        int languageIndex = getLanguageIndex(instance);
        assert languageIndex + LANGUAGE_ELEMENTS - 1 < data.length : "unexpected fast thread local state";

        data[languageIndex + LANGUAGE_CONTEXT_OFFSET] = languageContext.getContextImpl();
        data[languageIndex + LANGUAGE_SPI_OFFSET] = instance.spi;
    }

    /*
     * This method is intended to be invoked in the compiler only and must always compile to a
     * constant result.
     */
    private static PolyglotLanguageInstance resolveLanguageInstance(Node node, int index) {
        CompilerAsserts.partialEvaluationConstant(index);

        if (!CompilerDirectives.isPartialEvaluationConstant(node)) {
            // no constant folding without PE constant node
            return null;
        }

        if (node == null) {
            return null;
        }

        RootNode root = node.getRootNode();
        if (root == null) {
            return null;
        }

        TruffleLanguage inLanguageSpi = EngineAccessor.NODES.getLanguage(root);
        PolyglotLanguageInstance inLanguageInstance = null;
        PolyglotEngineImpl engine;
        if (inLanguageSpi != null) {
            inLanguageInstance = (PolyglotLanguageInstance) EngineAccessor.LANGUAGE.getPolyglotLanguageInstance(inLanguageSpi);
            engine = inLanguageInstance.getEngine();
        } else {
            engine = (PolyglotEngineImpl) EngineAccessor.NODES.getPolyglotEngine(root);
            if (engine == null) {
                return null;
            }
        }
        int languageIndex = resolveLanguageIndex(index);
        PolyglotLanguage forLanguage = engine.languages[languageIndex];

        if (inLanguageInstance != null && inLanguageInstance.language == forLanguage) {
            return inLanguageInstance;
        } else {
            return forLanguage.singleLanguageInstance.getConstant();
        }
    }

    private static int computeLanguageIndex(Class languageClass, int offset) {
        List loaders = EngineAccessor.locatorOrDefaultLoaders();
        int staticIndex;
        if (EngineAccessor.HOST.isHostLanguage(languageClass)) {
            staticIndex = PolyglotEngineImpl.HOST_LANGUAGE_INDEX;
        } else {
            Map classNames = CLASS_NAME_CACHE.get(loaders);
            if (classNames == null) {
                classNames = new HashMap<>();
                Map idToLanguage = LanguageCache.loadLanguages(loaders);
                for (LanguageCache cache : idToLanguage.values()) {
                    classNames.put(cache.getClassName(), cache);
                }
                Map finalClassNames = classNames;
                classNames = CLASS_NAME_CACHE.computeIfAbsent(loaders, (k) -> Collections.unmodifiableMap(finalClassNames));
            }
            LanguageCache cache = classNames.get(languageClass.getName());
            if (cache == null) {
                return RESERVED_NULL;
            }
            staticIndex = cache.getStaticIndex();
            assert staticIndex <= LanguageCache.getMaxStaticIndex() : "invalid sharing between class loaders";
        }
        return computeLanguageIndexFromStaticIndex(staticIndex, offset);
    }

    static int computeLanguageIndexFromStaticIndex(int staticIndex, int offset) {
        return LANGUAGE_START + (staticIndex * LANGUAGE_ELEMENTS) + offset;
    }

    private static int resolveLanguageIndex(int index) {
        if (index < LANGUAGE_START || index >= LANGUAGE_START + ((LanguageCache.getMaxStaticIndex() + 1) * LANGUAGE_ELEMENTS)) {
            throw CompilerDirectives.shouldNotReachHere("invalid fast thread local index");
        }
        return Math.floorDiv(index - LANGUAGE_START, LANGUAGE_ELEMENTS);
    }

    static int computePELanguageIndex(Class> languageClass, int offset) {
        int indexValue = LANGUAGE_INDEXES.get(languageClass);
        if (indexValue == -1) {
            CompilerDirectives.transferToInterpreterAndInvalidate();
            synchronized (LANGUAGE_INDEXES) {
                indexValue = LANGUAGE_INDEXES.get(languageClass);
                if (indexValue == -1) {
                    indexValue = computeLanguageIndex(languageClass, 0);
                    LANGUAGE_INDEXES.put(languageClass, indexValue);
                }
            }
        }
        return indexValue + offset;
    }

    private static PolyglotEngineImpl resolveEngine(Node node) {
        if (!CompilerDirectives.isPartialEvaluationConstant(node)) {
            // no constant folding without node
            return null;
        }
        if (node == null) {
            return null;
        }
        RootNode root = node.getRootNode();
        if (root == null) {
            return null;
        }
        return (PolyglotEngineImpl) EngineAccessor.NODES.getPolyglotEngine(root);
    }

    private static PolyglotLanguage findLanguage(Node node, int index) {
        PolyglotEngineImpl engine = resolveEngine(node);
        if (engine == null) {
            return null;
        }
        int languageIndex = resolveLanguageIndex(index);
        if (languageIndex > engine.languages.length) {
            // out of bounds language. might happen for invalid language accesses
            return null;
        }
        return engine.languages[languageIndex];
    }

    @SuppressWarnings("unchecked")
    private static  Class findContextClass(Node node, int index) {
        if (index == RESERVED_NULL) {
            return null;
        }
        PolyglotLanguage language = findLanguage(node, index);
        CompilerAsserts.partialEvaluationConstant(language);
        Class targetClass = null;
        if (language != null) {
            targetClass = (Class) language.contextClass;
        }
        return targetClass;
    }

    static boolean assertValidGet(int index, int expectedOffset, Class expectedType, Class languageClass) {
        if (index == RESERVED_NULL) {
            throw new IllegalArgumentException("Language " + languageClass + " not installed but used.");
        }
        Object[] data = IMPL.get();
        assert data != null : "No polyglot context is entered. A language or context reference must not be used if there is no polyglot context entered.";
        assert index >= LANGUAGE_START && index < LANGUAGE_START + ((LanguageCache.getMaxStaticIndex() + 1) * LANGUAGE_ELEMENTS) : "Invalid internal language index range";
        assert ((index - LANGUAGE_START) % LANGUAGE_ELEMENTS) == expectedOffset : "Invalid internal language index offset";
        Object value = data[index];
        assert value == null || expectedType == null || expectedType.isInstance(value) : "Invalid type in internal state.";
        return true;
    }

    static final class CachedReferences {

        final ContextReferenceImpl contextReference;
        final LanguageReferenceImpl languageReference;

        CachedReferences(Class languageClass) {
            this.contextReference = new ContextReferenceImpl(languageClass);
            this.languageReference = new LanguageReferenceImpl(languageClass);
        }
    }

    /*
     * Intended to support deprecated API in fast way, where a Node is not yet passed explicitly.
     * Remove with deprecated APIs.
     */
    static final class LegacyContextReference extends ContextReference {

        private final ContextReference delegate;
        private final Node fixedNode;

        LegacyContextReference(Node node, ContextReference delegate) {
            this.fixedNode = node;
            this.delegate = delegate;
        }

        @SuppressWarnings("deprecation")
        @Override
        public C get() {
            return delegate.get(fixedNode);
        }

        @Override
        public C get(Node node) {
            return delegate.get(node);
        }

    }

    static final class ContextReferenceImpl extends ContextReference {

        private final Class languageClass;
        private final int index;

        ContextReferenceImpl(Class languageClass) {
            this.languageClass = languageClass;
            this.index = computeLanguageIndex(languageClass, LANGUAGE_CONTEXT_OFFSET);

        }

        @SuppressWarnings("deprecation")
        @Override
        public Object get() {
            return get(null);
        }

        @Override
        public Object get(Node node) {
            assert assertValidGet(index, LANGUAGE_CONTEXT_OFFSET, findContextClass(node, index), languageClass);
            return getLanguageContext(node, index);
        }

        @Override
        public String toString() {
            return "ContextReference[language=" + languageClass + ", index = " + index + "]";
        }
    }

    /*
     * Remove with deprecated APIs.
     */
    static final class LegacyLanguageReference> extends LanguageReference {

        private final LanguageReference delegate;
        private final Node fixedNode;

        LegacyLanguageReference(Node node, LanguageReference delegate) {
            this.fixedNode = node;
            this.delegate = delegate;
        }

        @SuppressWarnings("deprecation")
        @Override
        public C get() {
            return delegate.get(fixedNode);
        }

        @Override
        public C get(Node node) {
            return delegate.get(node);
        }

    }

    static final class LanguageReferenceImpl extends LanguageReference> {

        private final Class> languageClass;
        private final int index;

        @SuppressWarnings("unchecked")
        LanguageReferenceImpl(Class languageClass) {
            this.languageClass = (Class>) languageClass;
            this.index = computeLanguageIndex(languageClass, LANGUAGE_SPI_OFFSET);
        }

        @SuppressWarnings("deprecation")
        @Override
        public TruffleLanguage get() {
            return get(null);
        }

        @Override
        public TruffleLanguage get(Node node) {
            assert assertValidGet(index, LANGUAGE_SPI_OFFSET, languageClass, languageClass);
            return getLanguage(node, index, languageClass);
        }

        @Override
        public String toString() {
            return "LanguageReference[language=" + languageClass + ", index = " + index + "]";
        }
    }

}