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

com.oracle.truffle.api.vm.PolyglotValue 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: 1.0.0-rc7
Show newest version
/*
 * Copyright (c) 2017, 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
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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 com.oracle.truffle.api.vm;

import static com.oracle.truffle.api.vm.VMAccessor.LANGUAGE;

import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;

import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.TypeLiteral;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.impl.AbstractPolyglotImpl.AbstractValueImpl;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.interop.KeyInfo;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.vm.PolyglotLanguageContext.ToGuestValueNode;
import com.oracle.truffle.api.vm.PolyglotLanguageContext.ToGuestValuesNode;
import com.oracle.truffle.api.vm.PolyglotLanguageContext.ToHostValueNode;
import org.graalvm.polyglot.SourceSection;

@SuppressWarnings("deprecation")
abstract class PolyglotValue extends AbstractValueImpl {

    private static final double DOUBLE_MAX_SAFE_INTEGER = 9007199254740991d; // 2 ** 53 - 1
    private static final long LONG_MAX_SAFE_DOUBLE = 9007199254740991L; // 2 ** 53 - 1
    private static final float FLOAT_MAX_SAFE_INTEGER = 16777215f; // 2 ** 24 - 1
    private static final int INT_MAX_SAFE_FLOAT = 16777215; // 2 ** 24 - 1

    private static final String TRUNCATION_SUFFIX = "...";

    protected final PolyglotLanguageContext languageContext;

    PolyglotValue(PolyglotLanguageContext languageContext) {
        super(languageContext.getEngine().impl);
        this.languageContext = languageContext;
    }

    protected final String formatSuppliedValues(UnsupportedTypeException e) {
        Object[] suppliedValues = e.getSuppliedValues();
        String[] args = new String[suppliedValues.length];
        for (int i = 0; i < suppliedValues.length; i++) {
            Object value = suppliedValues[i];
            String s = null;
            if (value == null) {
                s = "null";
            } else {
                PolyglotLanguageContext displayLanguageContext = languageContext;
                final PolyglotLanguage resolvedLanguage = PolyglotImpl.EngineImpl.findObjectLanguage(languageContext.context, languageContext, value);
                if (resolvedLanguage != null) {
                    displayLanguageContext = languageContext.context.getContext(resolvedLanguage);
                }
                s = LANGUAGE.toStringIfVisible(displayLanguageContext.env, value, false);
            }
            args[i] = s;
        }
        return Arrays.toString(args);
    }

    @Override
    public Value getMetaObject(Object receiver) {
        Object prev = languageContext.context.enterIfNeeded();
        try {
            Object metaObject = findMetaObject(receiver);
            if (metaObject != null) {
                return newValue(metaObject);
            } else {
                return null;
            }
        } catch (Throwable e) {
            throw PolyglotImpl.wrapGuestException(languageContext, e);
        } finally {
            languageContext.context.leaveIfNeeded(prev);
        }
    }

    private Object findMetaObject(Object target) {
        if (target instanceof PolyglotLanguageBindings) {
            return languageContext.language.getName() + " Bindings";
        } else if (target instanceof PolyglotBindings) {
            return "Polyglot Bindings";
        } else {
            final PolyglotLanguage resolvedLanguage = PolyglotImpl.EngineImpl.findObjectLanguage(languageContext.context, languageContext, target);
            if (resolvedLanguage == null) {
                return null;
            }
            final PolyglotLanguageContext resolvedLanguageContext = languageContext.context.getContext(resolvedLanguage);
            assert resolvedLanguageContext != null;
            return LANGUAGE.findMetaObject(resolvedLanguageContext.env, target);
        }
    }

    @Override
    protected RuntimeException unsupported(Object receiver, String message, String useToCheck) {
        Object prev = languageContext.context.enterIfNeeded();
        try {
            String polyglotMessage;
            if (useToCheck != null) {
                polyglotMessage = String.format("Unsupported operation %s.%s for %s. You can ensure that the operation is supported using %s.%s.",
                                Value.class.getSimpleName(), message, getValueInfo(languageContext, receiver), Value.class.getSimpleName(), useToCheck);
            } else {
                polyglotMessage = String.format("Unsupported operation %s.%s for %s.",
                                Value.class.getSimpleName(), message, getValueInfo(languageContext, receiver));
            }
            throw new PolyglotUnsupportedException(polyglotMessage);
        } catch (Throwable e) {
            throw PolyglotImpl.wrapGuestException(languageContext, e);
        } finally {
            languageContext.context.leaveIfNeeded(prev);
        }
    }

    private static final int CHARACTER_LIMIT = 140;

    static String getValueInfo(PolyglotLanguageContext languageContext, Object receiver) {
        if (languageContext == null) {
            return receiver.toString();
        } else if (receiver == null) {
            assert false : "receiver should never be null";
            return "null";
        }

        PolyglotLanguage displayLanguage = languageContext.language;
        PolyglotLanguageContext displayContext = languageContext;
        if (!(receiver instanceof Number || receiver instanceof String || receiver instanceof Character || receiver instanceof Boolean)) {
            try {
                PolyglotLanguage resolvedDisplayLanguage = PolyglotImpl.EngineImpl.findObjectLanguage(languageContext.context, languageContext, receiver);
                if (resolvedDisplayLanguage != null) {
                    displayLanguage = resolvedDisplayLanguage;
                }
                displayContext = languageContext.context.getContext(displayLanguage);
            } catch (Throwable e) {
                // don't fail without assertions for stability.
                assert rethrow(e);
            }
        }

        TruffleLanguage.Env displayEnv = displayContext.env;
        String metaObjectToString = "Unknown";
        if (displayEnv != null) {
            try {
                Object metaObject = LANGUAGE.findMetaObject(displayEnv, receiver);
                if (metaObject != null) {
                    metaObjectToString = truncateString(LANGUAGE.toStringIfVisible(displayEnv, metaObject, false), CHARACTER_LIMIT);
                }
            } catch (Throwable e) {
                assert rethrow(e);
            }
        }

        String valueToString = "Unknown";
        try {
            valueToString = truncateString(LANGUAGE.toStringIfVisible(displayEnv, receiver, false), CHARACTER_LIMIT);
        } catch (Throwable e) {
            assert rethrow(e);
        }
        String languageName;
        boolean hideType = false;
        if (displayLanguage.isHost()) {
            languageName = "Java"; // java is our host language for now

            // hide meta objects of null
            if (metaObjectToString.equals("java.lang.Void")) {
                hideType = true;
            }
        } else {
            languageName = displayLanguage.getName();
        }
        if (hideType) {
            return String.format("'%s'(language: %s)", valueToString, languageName);
        } else {
            return String.format("'%s'(language: %s, type: %s)", valueToString, languageName, metaObjectToString);
        }

    }

    private static String truncateString(String s, int i) {
        if (s.length() > i) {
            return s.substring(0, i - TRUNCATION_SUFFIX.length()) + TRUNCATION_SUFFIX;
        } else {
            return s;
        }
    }

    @SuppressWarnings("unchecked")
    private static  boolean rethrow(Throwable e) throws T {
        throw (T) e;
    }

    @Override
    protected RuntimeException nullCoercion(Object receiver, Class targetType, String message, String useToCheck) {
        Object prev = languageContext.context.enterIfNeeded();
        try {
            String valueInfo = getValueInfo(languageContext, receiver);
            throw new PolyglotNullPointerException(String.format("Cannot convert null value %s to Java type '%s' using %s.%s. " +
                            "You can ensure that the operation is supported using %s.%s.",
                            valueInfo, targetType, Value.class.getSimpleName(), message, Value.class.getSimpleName(), useToCheck));
        } catch (Throwable e) {
            throw PolyglotImpl.wrapGuestException(languageContext, e);
        } finally {
            languageContext.context.leaveIfNeeded(prev);
        }
    }

    @Override
    protected RuntimeException cannotConvert(Object receiver, Class targetType, String message, String useToCheck, String reason) {
        Object prev = languageContext.context.enterIfNeeded();
        try {
            String valueInfo = getValueInfo(languageContext, receiver);
            String targetTypeString = "";
            if (targetType != null) {
                targetTypeString = String.format("to Java type '%s'", targetType.getTypeName());
            }
            throw new PolyglotClassCastException(
                            String.format("Cannot convert %s %s using %s.%s: %s You can ensure that the value can be converted using %s.%s.",
                                            valueInfo, targetTypeString, Value.class.getSimpleName(), message, reason, Value.class.getSimpleName(), useToCheck));
        } catch (Throwable e) {
            throw PolyglotImpl.wrapGuestException(languageContext, e);
        } finally {
            languageContext.context.leaveIfNeeded(prev);
        }
    }

    protected static RuntimeException invalidArrayIndex(PolyglotLanguageContext context, Object receiver, long index) {
        String message = String.format("Invalid array index %s for array %s.", index, getValueInfo(context, receiver));
        throw new PolyglotArrayIndexOutOfBoundsException(message);
    }

    protected static RuntimeException invalidArrayValue(PolyglotLanguageContext context, Object receiver, long identifier, Object value) {
        throw new PolyglotClassCastException(
                        String.format("Invalid array value %s for array %s and index %s.",
                                        getValueInfo(context, value), getValueInfo(context, receiver), identifier));
    }

    protected static RuntimeException invalidMemberKey(PolyglotLanguageContext context, Object receiver, String identifier) {
        String message = String.format("Invalid member key '%s' for object %s.", identifier, getValueInfo(context, receiver));
        throw new PolyglotIllegalArgumentException(message);
    }

    protected static RuntimeException invalidMemberValue(PolyglotLanguageContext context, Object receiver, String identifier, Object value) {
        String message = String.format("Invalid member value %s for object %s and member key '%s'.", getValueInfo(context, value), getValueInfo(context, receiver), identifier);
        throw new PolyglotIllegalArgumentException(message);
    }

    protected static RuntimeException invalidExecuteArgumentType(PolyglotLanguageContext context, Object receiver, UnsupportedTypeException e) {
        String[] formattedArgs = formatArgs(context, e.getSuppliedValues());
        String message = String.format("Invalid argument when executing %s with arguments %s.", getValueInfo(context, receiver), Arrays.asList(formattedArgs));
        throw new PolyglotIllegalArgumentException(message);

    }

    protected static RuntimeException invalidInstantiateArgumentType(PolyglotLanguageContext context, Object receiver, Object[] arguments) {
        String[] formattedArgs = formatArgs(context, arguments);
        String message = String.format("Invalid argument when instantiating %s with arguments %s.", getValueInfo(context, receiver), Arrays.asList(formattedArgs));
        throw new PolyglotIllegalArgumentException(message);
    }

    protected static RuntimeException invalidInstantiateArity(PolyglotLanguageContext context, Object receiver, Object[] arguments, int expected, int actual) {
        String[] formattedArgs = formatArgs(context, arguments);
        String message = String.format("Invalid argument count when instantiating %s with arguments %s. Expected %s argument(s) but got %s.",
                        getValueInfo(context, receiver), Arrays.asList(formattedArgs), expected, actual);
        throw new PolyglotIllegalArgumentException(message);
    }

    protected static RuntimeException invalidExecuteArity(PolyglotLanguageContext context, Object receiver, Object[] arguments, int expected, int actual) {
        String[] formattedArgs = formatArgs(context, arguments);
        String message = String.format("Invalid argument count when executing %s with arguments %s. Expected %s argument(s) but got %s.",
                        getValueInfo(context, receiver), Arrays.asList(formattedArgs), expected, actual);
        throw new PolyglotIllegalArgumentException(message);
    }

    private static String[] formatArgs(PolyglotLanguageContext context, Object[] arguments) {
        String[] formattedArgs = new String[arguments.length];
        for (int i = 0; i < arguments.length; i++) {
            formattedArgs[i] = getValueInfo(context, arguments[i]);
        }
        return formattedArgs;
    }

    private static PolyglotException error(String message) {
        throw new PolyglotUnsupportedException(message, null);
    }

    @Override
    public String toString(Object receiver) {
        Object prev = languageContext.context.enterIfNeeded();
        try {
            if (receiver instanceof PolyglotLanguageBindings) {
                return languageContext.language.getName() + " Bindings";
            } else if (receiver instanceof PolyglotBindings) {
                return "Polyglot Bindings";
            } else {
                PolyglotLanguageContext displayLanguageContext = languageContext;
                final PolyglotLanguage resolvedLanguage = PolyglotImpl.EngineImpl.findObjectLanguage(languageContext.context, languageContext, receiver);
                if (resolvedLanguage != null) {
                    displayLanguageContext = languageContext.context.getContext(resolvedLanguage);
                }
                return LANGUAGE.toStringIfVisible(displayLanguageContext.env, receiver, false);
            }
        } catch (Throwable e) {
            throw PolyglotImpl.wrapGuestException(languageContext, e);
        } finally {
            languageContext.context.leaveIfNeeded(prev);
        }
    }

    @Override
    public SourceSection getSourceLocation(Object receiver) {
        Object prev = languageContext.context.enterIfNeeded();
        try {
            final PolyglotLanguage resolvedLanguage = PolyglotImpl.EngineImpl.findObjectLanguage(languageContext.context, languageContext, receiver);
            if (resolvedLanguage == null) {
                return null;
            }
            final PolyglotLanguageContext resolvedLanguageContext = languageContext.context.getContext(resolvedLanguage);
            com.oracle.truffle.api.source.SourceSection result = LANGUAGE.findSourceLocation(resolvedLanguageContext.env, receiver);
            return result != null ? VMAccessor.engine().createSourceSection(resolvedLanguageContext, null, result) : null;
        } catch (final Throwable t) {
            throw PolyglotImpl.wrapGuestException(languageContext, t);
        } finally {
            languageContext.context.leaveIfNeeded(prev);
        }
    }

    protected final Value newValue(Object receiver) {
        return languageContext.toHostValue(receiver);
    }

    static CallTarget createTarget(PolyglotNode root) {
        CallTarget target = Truffle.getRuntime().createCallTarget(root);
        Class[] types = root.getArgumentTypes();
        if (types != null) {
            assert verifyTypes(types);
            VMAccessor.SPI.initializeProfile(target, types);
        }
        return target;
    }

    private static boolean verifyTypes(Class[] types) {
        for (Class type : types) {
            assert type != null;
        }
        return true;
    }

    static PolyglotValue createInteropValueCache(PolyglotLanguageContext languageContext, TruffleObject receiver, Class receiverType) {
        return new Interop(languageContext, receiver, receiverType);
    }

    static void createDefaultValueCaches(PolyglotLanguageContext context, Map, PolyglotValue> valueCache) {
        valueCache.put(Boolean.class, new BooleanValueCache(context));
        valueCache.put(Byte.class, new ByteValueCache(context));
        valueCache.put(Short.class, new ShortValueCache(context));
        valueCache.put(Integer.class, new IntValueCache(context));
        valueCache.put(Long.class, new LongValueCache(context));
        valueCache.put(Float.class, new FloatValueCache(context));
        valueCache.put(Double.class, new DoubleValueCache(context));
        valueCache.put(String.class, new StringValueCache(context));
        valueCache.put(Character.class, new CharacterValueCache(context));
    }

    private static boolean inSafeIntegerRange(double d) {
        return d >= -DOUBLE_MAX_SAFE_INTEGER && d <= DOUBLE_MAX_SAFE_INTEGER;
    }

    private static boolean inSafeDoubleRange(long l) {
        return l >= -LONG_MAX_SAFE_DOUBLE && l <= LONG_MAX_SAFE_DOUBLE;
    }

    private static boolean inSafeIntegerRange(float f) {
        return f >= -FLOAT_MAX_SAFE_INTEGER && f <= FLOAT_MAX_SAFE_INTEGER;
    }

    private static boolean inSafeFloatRange(int i) {
        return i >= -INT_MAX_SAFE_FLOAT && i <= INT_MAX_SAFE_FLOAT;
    }

    private static boolean inSafeFloatRange(long l) {
        return l >= -INT_MAX_SAFE_FLOAT && l <= INT_MAX_SAFE_FLOAT;
    }

    private static boolean isNegativeZero(double d) {
        return d == 0d && Double.doubleToRawLongBits(d) == Double.doubleToRawLongBits(-0d);
    }

    private static boolean isNegativeZero(float f) {
        return f == 0f && Float.floatToRawIntBits(f) == Float.floatToRawIntBits(-0f);
    }

    abstract static class BaseCache extends PolyglotValue {

        final PolyglotImpl impl;

        final CallTarget asClassLiteral;
        final CallTarget asTypeLiteral;

        final Class receiverType;

        BaseCache(PolyglotLanguageContext context, Class receiverType) {
            super(context);
            this.impl = context.getEngine().impl;
            this.receiverType = receiverType;
            this.asClassLiteral = createTarget(new AsClassLiteralNode(this));
            this.asTypeLiteral = createTarget(new AsTypeLiteralNode(this));
        }

        @SuppressWarnings("unchecked")
        @Override
        public final  T as(Object receiver, Class targetType) {
            return (T) VMAccessor.SPI.callProfiled(asClassLiteral, receiver, targetType);
        }

        @SuppressWarnings("unchecked")
        @Override
        public final  T as(Object receiver, TypeLiteral targetType) {
            return (T) VMAccessor.SPI.callProfiled(asTypeLiteral, receiver, targetType);
        }

        private static class AsClassLiteralNode extends PolyglotNode {

            @Child Node toJava = VMAccessor.JAVAINTEROP.createToJavaNode();

            protected AsClassLiteralNode(BaseCache interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, Class.class};
            }

            @Override
            protected String getOperationName() {
                return "as";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                return VMAccessor.JAVAINTEROP.toJava(toJava, (Class) args[1], null, args[0], polyglot.languageContext);
            }

        }

        private static class AsTypeLiteralNode extends PolyglotNode {

            @Child Node toJava = VMAccessor.JAVAINTEROP.createToJavaNode();

            protected AsTypeLiteralNode(BaseCache interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, TypeLiteral.class};
            }

            @Override
            protected String getOperationName() {
                return "as";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                TypeLiteral typeLiteral = (TypeLiteral) args[1];
                return VMAccessor.JAVAINTEROP.toJava(toJava, typeLiteral.getRawType(), typeLiteral.getType(), args[0], polyglot.languageContext);
            }

        }

    }

    private abstract static class PolyglotNode extends RootNode {

        protected final BaseCache polyglot;

        protected abstract String getOperationName();

        @CompilationFinal private boolean seenEnter;
        @CompilationFinal private boolean seenNonEnter;

        protected PolyglotNode(BaseCache polyglot) {
            super(null);
            this.polyglot = polyglot;
        }

        protected abstract Class[] getArgumentTypes();

        @Override
        public final Object execute(VirtualFrame frame) {
            Object[] args = frame.getArguments();
            Object receiver = polyglot.receiverType.cast(args[0]);
            PolyglotContextImpl context = polyglot.languageContext.context;
            boolean needsEnter = context.needsEnter();
            Object prev;
            if (needsEnter) {
                if (!seenEnter) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    seenEnter = true;
                }
                prev = context.enter();
            } else {
                if (!seenNonEnter) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    seenNonEnter = true;
                }
                prev = null;
            }
            try {
                return executeImpl(receiver, args);
            } catch (Throwable e) {
                CompilerDirectives.transferToInterpreter();
                throw PolyglotImpl.wrapGuestException(polyglot.languageContext, e);
            } finally {
                if (needsEnter) {
                    context.leave(prev);
                }
            }
        }

        protected abstract Object executeImpl(Object receiver, Object[] args);

        @Override
        public final String getName() {
            return "org.graalvm.polyglot.Value<" + polyglot.receiverType.getSimpleName() + ">." + getOperationName();
        }

        @Override
        public final String toString() {
            return getName();
        }

    }

    private static final class StringValueCache extends BaseCache {

        StringValueCache(PolyglotLanguageContext context) {
            super(context, String.class);
        }

        @Override
        public boolean isString(Object receiver) {
            return true;
        }

        @Override
        public String asString(Object receiver) {
            return (String) receiver;
        }

    }

    private static final class BooleanValueCache extends BaseCache {

        BooleanValueCache(PolyglotLanguageContext context) {
            super(context, Boolean.class);
        }

        @Override
        public boolean isBoolean(Object receiver) {
            return true;
        }

        @Override
        public boolean asBoolean(Object receiver) {
            return (boolean) receiver;
        }

    }

    private static final class ByteValueCache extends BaseCache {

        ByteValueCache(PolyglotLanguageContext context) {
            super(context, Byte.class);
        }

        @Override
        public boolean isNumber(Object receiver) {
            return true;
        }

        @Override
        public boolean fitsInByte(Object receiver) {
            return true;
        }

        @Override
        public byte asByte(Object receiver) {
            return (byte) receiver;
        }

        @Override
        public boolean fitsInShort(Object receiver) {
            return true;
        }

        @Override
        public short asShort(Object receiver) {
            return (byte) receiver;
        }

        @Override
        public boolean fitsInInt(Object receiver) {
            return true;
        }

        @Override
        public int asInt(Object receiver) {
            return (byte) receiver;
        }

        @Override
        public boolean fitsInLong(Object receiver) {
            return true;
        }

        @Override
        public long asLong(Object receiver) {
            return (byte) receiver;
        }

        @Override
        public boolean fitsInFloat(Object receiver) {
            return true;
        }

        @Override
        public float asFloat(Object receiver) {
            return (byte) receiver;
        }

        @Override
        public boolean fitsInDouble(Object receiver) {
            return true;
        }

        @Override
        public double asDouble(Object receiver) {
            return (byte) receiver;
        }

    }

    private static final class ShortValueCache extends BaseCache {

        ShortValueCache(PolyglotLanguageContext context) {
            super(context, Short.class);
        }

        @Override
        public boolean isNumber(Object receiver) {
            return true;
        }

        @Override
        public boolean fitsInByte(Object receiver) {
            short originalReceiver = (short) receiver;
            byte castValue = (byte) originalReceiver;
            return originalReceiver == castValue;
        }

        @Override
        public byte asByte(Object receiver) {
            short originalReceiver = (short) receiver;
            byte castValue = (byte) originalReceiver;
            if (originalReceiver == castValue) {
                return castValue;
            } else {
                return super.asByte(receiver);
            }
        }

        @Override
        public boolean fitsInShort(Object receiver) {
            return true;
        }

        @Override
        public short asShort(Object receiver) {
            return (short) receiver;
        }

        @Override
        public boolean fitsInInt(Object receiver) {
            return true;
        }

        @Override
        public int asInt(Object receiver) {
            return (short) receiver;
        }

        @Override
        public boolean fitsInLong(Object receiver) {
            return true;
        }

        @Override
        public long asLong(Object receiver) {
            return (short) receiver;
        }

        @Override
        public boolean fitsInFloat(Object receiver) {
            return true;
        }

        @Override
        public float asFloat(Object receiver) {
            return (short) receiver;
        }

        @Override
        public boolean fitsInDouble(Object receiver) {
            return true;
        }

        @Override
        public double asDouble(Object receiver) {
            return (short) receiver;
        }

    }

    private static final class CharacterValueCache extends BaseCache {

        CharacterValueCache(PolyglotLanguageContext context) {
            super(context, Character.class);
        }

        @Override
        public boolean isString(Object receiver) {
            return true;
        }

        @Override
        public String asString(Object receiver) {
            return String.valueOf((char) receiver);
        }
    }

    private static final class LongValueCache extends BaseCache {

        LongValueCache(PolyglotLanguageContext context) {
            super(context, Long.class);
        }

        @Override
        public boolean isNumber(Object receiver) {
            return true;
        }

        @Override
        public boolean fitsInByte(Object receiver) {
            long originalReceiver = (long) receiver;
            byte castValue = (byte) originalReceiver;
            return originalReceiver == castValue;
        }

        @Override
        public byte asByte(Object receiver) {
            long originalReceiver = (long) receiver;
            byte castValue = (byte) originalReceiver;
            if (originalReceiver == castValue) {
                return castValue;
            } else {
                return super.asByte(receiver);
            }
        }

        @Override
        public boolean fitsInInt(Object receiver) {
            long originalReceiver = (long) receiver;
            int castValue = (int) originalReceiver;
            return originalReceiver == castValue;
        }

        @Override
        public int asInt(Object receiver) {
            long originalReceiver = (long) receiver;
            int castValue = (int) originalReceiver;
            if (originalReceiver == castValue) {
                return castValue;
            } else {
                return super.asInt(receiver);
            }
        }

        @Override
        public boolean fitsInLong(Object receiver) {
            return true;
        }

        @Override
        public long asLong(Object receiver) {
            return (long) receiver;
        }

        @Override
        public boolean fitsInFloat(Object receiver) {
            long originalReceiver = (long) receiver;
            return inSafeFloatRange(originalReceiver);
        }

        @Override
        public float asFloat(Object receiver) {
            long originalReceiver = (long) receiver;
            float castValue = originalReceiver;
            if (inSafeFloatRange(originalReceiver)) {
                return castValue;
            } else {
                return super.asFloat(receiver);
            }
        }

        @Override
        public boolean fitsInDouble(Object receiver) {
            long originalReceiver = (long) receiver;
            return inSafeDoubleRange(originalReceiver);
        }

        @Override
        public double asDouble(Object receiver) {
            long originalReceiver = (long) receiver;
            double castValue = originalReceiver;
            if (inSafeDoubleRange(originalReceiver)) {
                return castValue;
            } else {
                return super.asDouble(receiver);
            }
        }

        @Override
        public boolean fitsInShort(Object receiver) {
            long originalReceiver = (long) receiver;
            short castValue = (short) originalReceiver;
            return originalReceiver == castValue;
        }

        @Override
        public short asShort(Object receiver) {
            long originalReceiver = (long) receiver;
            short castValue = (short) originalReceiver;
            if (originalReceiver == castValue) {
                return castValue;
            } else {
                return super.asShort(receiver);
            }
        }
    }

    private static final class FloatValueCache extends BaseCache {

        FloatValueCache(PolyglotLanguageContext context) {
            super(context, Float.class);
        }

        @Override
        public boolean isNumber(Object receiver) {
            return true;
        }

        @Override
        public boolean fitsInByte(Object receiver) {
            float originalReceiver = (float) receiver;
            byte castValue = (byte) originalReceiver;
            return originalReceiver == castValue && !isNegativeZero(originalReceiver);
        }

        @Override
        public byte asByte(Object receiver) {
            float originalReceiver = (float) receiver;
            byte castValue = (byte) originalReceiver;
            if (originalReceiver == castValue && !isNegativeZero(originalReceiver)) {
                return castValue;
            } else {
                return super.asByte(receiver);
            }
        }

        @Override
        public boolean fitsInInt(Object receiver) {
            float originalReceiver = (float) receiver;
            int castValue = (int) originalReceiver;
            return inSafeIntegerRange(originalReceiver) && !isNegativeZero(originalReceiver) && originalReceiver == castValue;
        }

        @Override
        public int asInt(Object receiver) {
            float originalReceiver = (float) receiver;
            int castValue = (int) originalReceiver;
            if (inSafeIntegerRange(originalReceiver) && !isNegativeZero(originalReceiver) && originalReceiver == castValue) {
                return castValue;
            } else {
                return super.asInt(receiver);
            }
        }

        @Override
        public boolean fitsInLong(Object receiver) {
            float originalReceiver = (float) receiver;
            long castValue = (long) originalReceiver;
            return inSafeIntegerRange(originalReceiver) && !isNegativeZero(originalReceiver) && originalReceiver == castValue;
        }

        @Override
        public long asLong(Object receiver) {
            float originalReceiver = (float) receiver;
            long castValue = (long) originalReceiver;
            if (inSafeIntegerRange(originalReceiver) && !isNegativeZero(originalReceiver) && originalReceiver == castValue) {
                return castValue;
            } else {
                return super.asLong(receiver);
            }
        }

        @Override
        public boolean fitsInFloat(Object receiver) {
            return true;
        }

        @Override
        public float asFloat(Object receiver) {
            return (float) receiver;
        }

        @Override
        public boolean fitsInDouble(Object receiver) {
            float originalReceiver = (float) receiver;
            double castValue = originalReceiver;
            return !Float.isFinite(originalReceiver) || castValue == originalReceiver;
        }

        @Override
        public double asDouble(Object receiver) {
            float originalReceiver = (float) receiver;
            double castValue = originalReceiver;
            if (!Float.isFinite(originalReceiver) || castValue == originalReceiver) {
                return castValue;
            } else {
                return super.asLong(receiver);
            }
        }

        @Override
        public boolean fitsInShort(Object receiver) {
            float originalReceiver = (float) receiver;
            short castValue = (short) originalReceiver;
            return originalReceiver == castValue && !isNegativeZero(originalReceiver);
        }

        @Override
        public short asShort(Object receiver) {
            float originalReceiver = (float) receiver;
            short castValue = (short) originalReceiver;
            if (originalReceiver == castValue && !isNegativeZero(originalReceiver)) {
                return castValue;
            } else {
                return super.asShort(receiver);
            }
        }
    }

    private static final class DoubleValueCache extends BaseCache {

        DoubleValueCache(PolyglotLanguageContext context) {
            super(context, Double.class);
        }

        @Override
        public boolean isNumber(Object receiver) {
            return true;
        }

        @Override
        public boolean fitsInByte(Object receiver) {
            double originalReceiver = (double) receiver;
            byte castValue = (byte) originalReceiver;
            return originalReceiver == castValue && !isNegativeZero(originalReceiver);
        }

        @Override
        public byte asByte(Object receiver) {
            double originalReceiver = (double) receiver;
            byte castValue = (byte) originalReceiver;
            if (originalReceiver == castValue && !isNegativeZero(originalReceiver)) {
                return castValue;
            } else {
                return super.asByte(receiver);
            }
        }

        @Override
        public boolean fitsInInt(Object receiver) {
            double originalReceiver = (double) receiver;
            int castValue = (int) originalReceiver;
            return originalReceiver == castValue && !isNegativeZero(originalReceiver);
        }

        @Override
        public int asInt(Object receiver) {
            double originalReceiver = (double) receiver;
            int castValue = (int) originalReceiver;
            if (originalReceiver == castValue && !isNegativeZero(originalReceiver)) {
                return castValue;
            } else {
                return super.asInt(receiver);
            }
        }

        @Override
        public boolean fitsInLong(Object receiver) {
            double originalReceiver = (double) receiver;
            long castValue = (long) originalReceiver;
            return inSafeIntegerRange(originalReceiver) && !isNegativeZero(originalReceiver) && originalReceiver == castValue;
        }

        @Override
        public long asLong(Object receiver) {
            double originalReceiver = (double) receiver;
            long castValue = (long) originalReceiver;
            if (inSafeIntegerRange(originalReceiver) && !isNegativeZero(originalReceiver) && originalReceiver == castValue) {
                return castValue;
            } else {
                return super.asLong(receiver);
            }
        }

        @Override
        public boolean fitsInFloat(Object receiver) {
            double originalReceiver = (double) receiver;
            float castValue = (float) originalReceiver;
            return !Double.isFinite(originalReceiver) || castValue == originalReceiver;
        }

        @Override
        public float asFloat(Object receiver) {
            double originalReceiver = (double) receiver;
            float castValue = (float) originalReceiver;
            if (!Double.isFinite(originalReceiver) || castValue == originalReceiver) {
                return castValue;
            } else {
                return super.asFloat(receiver);
            }
        }

        @Override
        public boolean fitsInDouble(Object receiver) {
            return true;
        }

        @Override
        public double asDouble(Object receiver) {
            return (double) receiver;
        }

        @Override
        public boolean fitsInShort(Object receiver) {
            double originalReceiver = (double) receiver;
            short castValue = (short) originalReceiver;
            return originalReceiver == castValue && !isNegativeZero(originalReceiver);
        }

        @Override
        public short asShort(Object receiver) {
            double originalReceiver = (double) receiver;
            short castValue = (short) originalReceiver;
            if (originalReceiver == castValue && !isNegativeZero(originalReceiver)) {
                return castValue;
            } else {
                return super.asShort(receiver);
            }
        }
    }

    private static final class IntValueCache extends BaseCache {

        IntValueCache(PolyglotLanguageContext context) {
            super(context, Integer.class);
        }

        @Override
        public boolean isNumber(Object receiver) {
            return true;
        }

        @Override
        public boolean fitsInInt(Object receiver) {
            return true;
        }

        @Override
        public int asInt(Object receiver) {
            return (int) receiver;
        }

        @Override
        public boolean fitsInLong(Object receiver) {
            return true;
        }

        @Override
        public long asLong(Object receiver) {
            return (int) receiver;
        }

        @Override
        public boolean fitsInDouble(Object receiver) {
            return true;
        }

        @Override
        public double asDouble(Object receiver) {
            return (int) receiver;
        }

        @Override
        public boolean fitsInByte(Object receiver) {
            int intReceiver = (int) receiver;
            byte castValue = (byte) intReceiver;
            return intReceiver == castValue;
        }

        @Override
        public byte asByte(Object receiver) {
            int intReceiver = (int) receiver;
            byte castValue = (byte) intReceiver;
            if (intReceiver == castValue) {
                return castValue;
            } else {
                return super.asByte(receiver);
            }
        }

        @Override
        public boolean fitsInFloat(Object receiver) {
            int intReceiver = (int) receiver;
            return inSafeFloatRange(intReceiver);
        }

        @Override
        public float asFloat(Object receiver) {
            int intReceiver = (int) receiver;
            float castValue = intReceiver;
            if (inSafeFloatRange(intReceiver)) {
                return castValue;
            } else {
                return super.asFloat(receiver);
            }
        }

        @Override
        public boolean fitsInShort(Object receiver) {
            int intReceiver = (int) receiver;
            short castValue = (short) intReceiver;
            return intReceiver == castValue;
        }

        @Override
        public short asShort(Object receiver) {
            int intReceiver = (int) receiver;
            short castValue = (short) intReceiver;
            if (intReceiver == castValue) {
                return castValue;
            } else {
                return super.asShort(receiver);
            }
        }
    }

    static final class Default extends BaseCache {

        Default(PolyglotLanguageContext context) {
            super(context, Object.class);
        }

    }

    private static final class Interop extends BaseCache {

        final Node keysNode = Message.KEYS.createNode();
        final Node keyInfoNode = Message.KEY_INFO.createNode();
        final Node keysSizeNode = Message.GET_SIZE.createNode();
        final Node keysReadNode = Message.READ.createNode();

        final CallTarget isNativePointer;
        final CallTarget asNativePointer;
        final CallTarget hasArrayElements;
        final CallTarget getArrayElement;
        final CallTarget setArrayElement;
        final CallTarget removeArrayElement;
        final CallTarget getArraySize;
        final CallTarget hasMembers;
        final CallTarget hasMember;
        final CallTarget getMember;
        final CallTarget putMember;
        final CallTarget removeMember;
        final CallTarget isNull;
        final CallTarget canExecute;
        final CallTarget execute;
        final CallTarget canInstantiate;
        final CallTarget newInstance;
        final CallTarget executeNoArgs;
        final CallTarget executeVoid;
        final CallTarget executeVoidNoArgs;
        final CallTarget asPrimitive;

        final boolean isProxy;
        final boolean isJava;

        Interop(PolyglotLanguageContext context, TruffleObject receiver, Class receiverType) {
            super(context, receiverType);
            Objects.requireNonNull(receiverType);
            this.isNativePointer = createTarget(new IsNativePointerNode(this));
            this.asNativePointer = createTarget(new AsNativePointerNode(this));
            this.hasArrayElements = createTarget(new HasArrayElementsNode(this));
            this.getArrayElement = createTarget(new GetArrayElementNode(this));
            this.setArrayElement = createTarget(new SetArrayElementNode(this));
            this.removeArrayElement = createTarget(new RemoveArrayElementNode(this));
            this.getArraySize = createTarget(new GetArraySizeNode(this));
            this.hasMember = createTarget(new HasMemberNode(this));
            this.getMember = createTarget(new GetMemberNode(this));
            this.putMember = createTarget(new PutMemberNode(this));
            this.removeMember = createTarget(new RemoveMemberNode(this));
            this.isNull = createTarget(new IsNullNode(this));
            this.execute = createTarget(new ExecuteNode(this));
            this.executeNoArgs = createTarget(new ExecuteNoArgsNode(this));
            this.executeVoid = createTarget(new ExecuteVoidNode(this));
            this.executeVoidNoArgs = createTarget(new ExecuteVoidNoArgsNode(this));
            this.newInstance = createTarget(new NewInstanceNode(this));
            this.canInstantiate = createTarget(new CanInstantiateNode(this));
            this.canExecute = createTarget(new CanExecuteNode(this));
            this.hasMembers = createTarget(new HasMembersNode(this));
            this.asPrimitive = createTarget(new AsPrimitiveNode(this));
            this.isProxy = PolyglotProxy.isProxyGuestObject(receiver);
            this.isJava = VMAccessor.JAVAINTEROP.isHostObject(receiver);
        }

        @Override
        public boolean isNativePointer(Object receiver) {
            return (boolean) VMAccessor.SPI.callProfiled(isNativePointer, receiver);
        }

        @Override
        public boolean hasArrayElements(Object receiver) {
            return (boolean) VMAccessor.SPI.callProfiled(hasArrayElements, receiver);
        }

        @Override
        public Value getArrayElement(Object receiver, long index) {
            return (Value) VMAccessor.SPI.callProfiled(getArrayElement, receiver, index);
        }

        @Override
        public void setArrayElement(Object receiver, long index, Object value) {
            VMAccessor.SPI.callProfiled(setArrayElement, receiver, index, value);
        }

        @Override
        public boolean removeArrayElement(Object receiver, long index) {
            return (boolean) VMAccessor.SPI.callProfiled(removeArrayElement, receiver, index);
        }

        @Override
        public long getArraySize(Object receiver) {
            return (long) VMAccessor.SPI.callProfiled(getArraySize, receiver);
        }

        @Override
        public boolean hasMembers(Object receiver) {
            return (boolean) hasMembers.call(receiver);
        }

        @Override
        public Value getMember(Object receiver, String key) {
            return (Value) VMAccessor.SPI.callProfiled(getMember, receiver, key);
        }

        @Override
        public boolean hasMember(Object receiver, String key) {
            return (boolean) VMAccessor.SPI.callProfiled(hasMember, receiver, key);
        }

        @Override
        public void putMember(Object receiver, String key, Object member) {
            VMAccessor.SPI.callProfiled(putMember, receiver, key, member);
        }

        @Override
        public boolean removeMember(Object receiver, String key) {
            return (boolean) VMAccessor.SPI.callProfiled(removeMember, receiver, key);
        }

        @Override
        public Set getMemberKeys(Object receiver) {
            Object prev = languageContext.context.enterIfNeeded();
            try {
                try {
                    final Object keys = ForeignAccess.sendKeys(keysNode, (TruffleObject) receiver, false);
                    if (!(keys instanceof TruffleObject)) {
                        return Collections.emptySet();
                    }
                    return new MemberSet((TruffleObject) receiver, (TruffleObject) keys);
                } catch (UnsupportedMessageException e) {
                    return Collections.emptySet();
                }
            } catch (Throwable e) {
                throw PolyglotImpl.wrapGuestException(languageContext, e);
            } finally {
                languageContext.context.leaveIfNeeded(prev);
            }
        }

        @Override
        public long asNativePointer(Object receiver) {
            return (long) VMAccessor.SPI.callProfiled(asNativePointer, receiver);
        }

        @Override
        public boolean isHostObject(Object receiver) {
            return isJava;
        }

        @Override
        public boolean isProxyObject(Object receiver) {
            return isProxy;
        }

        @Override
        public Object asProxyObject(Object receiver) {
            if (isProxy) {
                return PolyglotProxy.toProxyHostObject((TruffleObject) receiver);
            } else {
                return super.asProxyObject(receiver);
            }
        }

        @Override
        public Object asHostObject(Object receiver) {
            if (isJava) {
                return VMAccessor.JAVAINTEROP.asHostObject(receiver);
            } else {
                return super.asHostObject(receiver);
            }
        }

        @Override
        public boolean isNull(Object receiver) {
            return (boolean) VMAccessor.SPI.callProfiled(isNull, receiver);
        }

        @Override
        public boolean canExecute(Object receiver) {
            return (boolean) VMAccessor.SPI.callProfiled(canExecute, receiver);
        }

        @Override
        public void executeVoid(Object receiver, Object[] arguments) {
            VMAccessor.SPI.callProfiled(executeVoid, receiver, arguments);
        }

        @Override
        public void executeVoid(Object receiver) {
            VMAccessor.SPI.callProfiled(executeVoidNoArgs, receiver);
        }

        @Override
        public Value execute(Object receiver, Object[] arguments) {
            return (Value) VMAccessor.SPI.callProfiled(execute, receiver, arguments);
        }

        @Override
        public Value execute(Object receiver) {
            return (Value) VMAccessor.SPI.callProfiled(executeNoArgs, receiver);
        }

        @Override
        public boolean canInstantiate(Object receiver) {
            return (boolean) canInstantiate.call(receiver);
        }

        @Override
        public Value newInstance(Object receiver, Object[] arguments) {
            return (Value) newInstance.call(receiver, arguments);
        }

        private Object asPrimitive(Object receiver) {
            return VMAccessor.SPI.callProfiled(asPrimitive, receiver);
        }

        private PolyglotValue getPrimitiveCache(Object primitive) {
            assert primitive != null;
            PolyglotValue cache = languageContext.getValueCache().get(primitive.getClass());
            if (cache == null) {
                // TODO maybe this should be an assertion here because it likely means
                // that unbox returned an invalid value.
                cache = languageContext.getDefaultValueCache();
            }
            return cache;
        }

        @Override
        public boolean isNumber(Object receiver) {
            return asPrimitive(receiver) instanceof Number;
        }

        @Override
        public boolean fitsInByte(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.fitsInByte(receiver);
            }
            return getPrimitiveCache(primitive).fitsInByte(primitive);
        }

        @Override
        public byte asByte(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.asByte(receiver);
            }
            return getPrimitiveCache(primitive).asByte(primitive);
        }

        @Override
        public boolean isString(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.isString(receiver);
            }
            return getPrimitiveCache(primitive).isString(primitive);
        }

        @Override
        public String asString(Object receiver) {
            if (isNull(receiver)) {
                return null;
            }
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.asString(receiver);
            }
            return getPrimitiveCache(primitive).asString(primitive);
        }

        @Override
        public boolean fitsInInt(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.fitsInInt(receiver);
            }
            return getPrimitiveCache(primitive).fitsInInt(primitive);
        }

        @Override
        public int asInt(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.asInt(receiver);
            }
            return getPrimitiveCache(primitive).asInt(primitive);
        }

        @Override
        public boolean isBoolean(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.isBoolean(receiver);
            }
            return getPrimitiveCache(primitive).isBoolean(primitive);
        }

        @Override
        public boolean asBoolean(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.asBoolean(receiver);
            }
            return getPrimitiveCache(primitive).asBoolean(primitive);
        }

        @Override
        public boolean fitsInFloat(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.fitsInFloat(receiver);
            }
            return getPrimitiveCache(primitive).fitsInFloat(primitive);
        }

        @Override
        public float asFloat(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.asFloat(receiver);
            }
            return getPrimitiveCache(primitive).asFloat(primitive);
        }

        @Override
        public boolean fitsInDouble(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.fitsInDouble(receiver);
            }
            return getPrimitiveCache(primitive).fitsInDouble(primitive);
        }

        @Override
        public double asDouble(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.asDouble(receiver);
            }
            return getPrimitiveCache(primitive).asDouble(primitive);
        }

        @Override
        public boolean fitsInLong(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.fitsInLong(receiver);
            }
            return getPrimitiveCache(primitive).fitsInLong(primitive);
        }

        @Override
        public long asLong(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.asLong(receiver);
            }
            return getPrimitiveCache(primitive).asLong(primitive);
        }

        @Override
        public boolean fitsInShort(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.fitsInShort(receiver);
            }
            return getPrimitiveCache(primitive).fitsInShort(primitive);
        }

        @Override
        public short asShort(Object receiver) {
            Object primitive = asPrimitive(receiver);
            if (primitive == null) {
                return super.asShort(receiver);
            }
            return getPrimitiveCache(primitive).asShort(primitive);
        }

        private static class IsNativePointerNode extends PolyglotNode {

            @Child private Node isPointerNode = Message.IS_POINTER.createNode();

            protected IsNativePointerNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected String getOperationName() {
                return "isNativePointer";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                return ForeignAccess.sendIsPointer(isPointerNode, (TruffleObject) receiver);
            }

        }

        private static class AsNativePointerNode extends PolyglotNode {

            @Child private Node asPointerNode = Message.AS_POINTER.createNode();

            protected AsNativePointerNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected String getOperationName() {
                return "asNativePointer";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                try {
                    return ForeignAccess.sendAsPointer(asPointerNode, (TruffleObject) receiver);
                } catch (UnsupportedMessageException e) {
                    CompilerDirectives.transferToInterpreter();
                    return polyglot.asNativePointerUnsupported(receiver);
                }
            }

        }

        private static class HasArrayElementsNode extends PolyglotNode {

            @Child private Node hasSizeNode = Message.HAS_SIZE.createNode();

            protected HasArrayElementsNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected String getOperationName() {
                return "hasArrayElements";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                return ForeignAccess.sendHasSize(hasSizeNode, (TruffleObject) receiver);
            }

        }

        private static class GetArrayElementNode extends PolyglotNode {

            @Child private Node readArrayNode = Message.READ.createNode();
            private final ToHostValueNode toHostValue = polyglot.languageContext.createToHostValue();

            protected GetArrayElementNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, Long.class};
            }

            @Override
            protected String getOperationName() {
                return "getArrayElement";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                long index = (long) args[1];
                try {
                    return toHostValue.execute(ForeignAccess.sendRead(readArrayNode, (TruffleObject) receiver, index));
                } catch (UnsupportedMessageException e) {
                    CompilerDirectives.transferToInterpreter();
                    return polyglot.getArrayElementUnsupported(receiver);
                } catch (UnknownIdentifierException e) {
                    CompilerDirectives.transferToInterpreter();
                    throw invalidArrayIndex(polyglot.languageContext, receiver, index);
                }
            }

        }

        private static class SetArrayElementNode extends PolyglotNode {

            @Child private Node writeArrayNode = Message.WRITE.createNode();

            private final ToGuestValueNode toGuestValue = ToGuestValueNode.create();

            protected SetArrayElementNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, Long.class, Object.class};
            }

            @Override
            protected String getOperationName() {
                return "setArrayElement";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                long index = (long) args[1];
                Object value = toGuestValue.apply(polyglot.languageContext, args[2]);
                try {
                    ForeignAccess.sendWrite(writeArrayNode, (TruffleObject) receiver, index, value);
                } catch (UnsupportedMessageException e) {
                    CompilerDirectives.transferToInterpreter();
                    polyglot.setArrayElementUnsupported(receiver);
                } catch (UnknownIdentifierException e) {
                    CompilerDirectives.transferToInterpreter();
                    throw invalidArrayIndex(polyglot.languageContext, receiver, index);
                } catch (UnsupportedTypeException e) {
                    CompilerDirectives.transferToInterpreter();
                    throw invalidArrayValue(polyglot.languageContext, receiver, index, value);
                }
                return null;
            }
        }

        private static class RemoveArrayElementNode extends PolyglotNode {

            @Child private Node removeArrayNode = Message.REMOVE.createNode();
            @Child private Node keyInfoNode = Message.KEY_INFO.createNode();
            @Child private Node hasSizeNode = Message.HAS_SIZE.createNode();

            @CompilationFinal private boolean optimistic = true;

            protected RemoveArrayElementNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, Long.class};
            }

            @Override
            protected String getOperationName() {
                return "removeArrayElement";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                long index = (long) args[1];
                TruffleObject truffleReceiver = (TruffleObject) receiver;
                try {
                    if (optimistic) {
                        return ForeignAccess.sendRemove(removeArrayNode, (TruffleObject) receiver, index);
                    } else {
                        int keyInfo = ForeignAccess.sendKeyInfo(keyInfoNode, truffleReceiver, index);
                        if (KeyInfo.isRemovable(keyInfo)) {
                            return ForeignAccess.sendRemove(removeArrayNode, (TruffleObject) receiver, index);
                        } else {
                            if (KeyInfo.isExisting(keyInfo) || !ForeignAccess.sendHasSize(hasSizeNode, truffleReceiver)) {
                                CompilerDirectives.transferToInterpreter();
                                polyglot.removeArrayElementUnsupported(receiver);
                            }
                        }
                    }
                } catch (UnsupportedMessageException | UnknownIdentifierException e) {
                    if (optimistic) {
                        CompilerDirectives.transferToInterpreterAndInvalidate();
                        optimistic = false;
                    } else {
                        CompilerDirectives.transferToInterpreter();
                    }
                    int keyInfo = ForeignAccess.sendKeyInfo(keyInfoNode, truffleReceiver, index);
                    if (KeyInfo.isExisting(keyInfo) || !ForeignAccess.sendHasSize(hasSizeNode, truffleReceiver)) {
                        polyglot.removeArrayElementUnsupported(receiver);
                    }
                }
                CompilerDirectives.transferToInterpreter();
                throw invalidArrayIndex(polyglot.languageContext, receiver, index);
            }
        }

        private static class GetArraySizeNode extends PolyglotNode {

            @Child private Node getSizeNode = Message.GET_SIZE.createNode();

            protected GetArraySizeNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected String getOperationName() {
                return "getArraySize";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                try {
                    return ((Number) ForeignAccess.sendGetSize(getSizeNode, (TruffleObject) receiver)).longValue();
                } catch (UnsupportedMessageException e) {
                    CompilerDirectives.transferToInterpreter();
                    return polyglot.getArraySizeUnsupported(receiver);
                }
            }

        }

        private static class GetMemberNode extends PolyglotNode {

            @Child private Node readMemberNode = Message.READ.createNode();
            @Child private Node keyInfoNode = Message.KEY_INFO.createNode();
            @Child private Node hasKeysNode = Message.HAS_KEYS.createNode();
            @CompilationFinal private boolean optimistic = true;
            private final ToHostValueNode toHostValue = polyglot.languageContext.createToHostValue();

            protected GetMemberNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, String.class};
            }

            @Override
            protected String getOperationName() {
                return "getMember";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                String key = (String) args[1];
                Object value;
                TruffleObject truffleReceiver = (TruffleObject) receiver;
                try {
                    if (optimistic) {
                        value = toHostValue.execute(ForeignAccess.sendRead(readMemberNode, truffleReceiver, key));
                    } else {
                        int keyInfo = ForeignAccess.sendKeyInfo(keyInfoNode, truffleReceiver, key);
                        if (KeyInfo.isReadable(keyInfo)) {
                            value = toHostValue.execute(ForeignAccess.sendRead(readMemberNode, truffleReceiver, key));
                        } else {
                            if (KeyInfo.isExisting(keyInfo) || !ForeignAccess.sendHasKeys(hasKeysNode, truffleReceiver)) {
                                CompilerDirectives.transferToInterpreter();
                                return polyglot.getMemberUnsupported(receiver, key);
                            }
                            value = null;
                        }
                    }
                } catch (UnsupportedMessageException | UnknownIdentifierException e) {
                    if (optimistic) {
                        CompilerDirectives.transferToInterpreterAndInvalidate();
                        optimistic = false;
                    } else {
                        CompilerDirectives.transferToInterpreter();
                    }
                    int keyInfo = ForeignAccess.sendKeyInfo(keyInfoNode, truffleReceiver, key);
                    if (KeyInfo.isExisting(keyInfo) || !ForeignAccess.sendHasKeys(hasKeysNode, truffleReceiver)) {
                        return polyglot.getMemberUnsupported(receiver, key);
                    }
                    value = null;
                }
                return value;
            }

        }

        private static class PutMemberNode extends PolyglotNode {

            @Child private Node writeMemberNode = Message.WRITE.createNode();
            private final ToGuestValueNode toGuestValue = ToGuestValueNode.create();

            protected PutMemberNode(Interop interop) {
                super(interop);
            }

            @Override
            protected String getOperationName() {
                return "putMember";
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, String.class, Object.class};
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                String key = (String) args[1];
                Object originalValue = args[2];
                Object value = toGuestValue.apply(polyglot.languageContext, originalValue);
                try {
                    ForeignAccess.sendWrite(writeMemberNode, (TruffleObject) receiver, key, value);
                } catch (UnsupportedMessageException e) {
                    CompilerDirectives.transferToInterpreter();
                    polyglot.putMemberUnsupported(receiver);
                } catch (UnknownIdentifierException e) {
                    CompilerDirectives.transferToInterpreter();
                    throw invalidMemberKey(polyglot.languageContext, receiver, key);
                } catch (UnsupportedTypeException e) {
                    CompilerDirectives.transferToInterpreter();
                    throw invalidMemberValue(polyglot.languageContext, receiver, key, value);
                }
                return null;
            }

        }

        private static class RemoveMemberNode extends PolyglotNode {

            @Child private Node removeMemberNode = Message.REMOVE.createNode();
            @Child private Node keyInfoNode = Message.KEY_INFO.createNode();
            @Child private Node hasKeysNode = Message.HAS_KEYS.createNode();

            @CompilationFinal private boolean optimistic = true;

            protected RemoveMemberNode(Interop interop) {
                super(interop);
            }

            @Override
            protected String getOperationName() {
                return "removeMember";
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, String.class};
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                String key = (String) args[1];
                TruffleObject truffleReceiver = (TruffleObject) receiver;
                try {
                    if (optimistic) {
                        return ForeignAccess.sendRemove(removeMemberNode, truffleReceiver, key);
                    } else {
                        int keyInfo = ForeignAccess.sendKeyInfo(keyInfoNode, truffleReceiver, key);
                        if (KeyInfo.isRemovable(keyInfo)) {
                            return ForeignAccess.sendRemove(removeMemberNode, truffleReceiver, key);
                        } else {
                            if (KeyInfo.isExisting(keyInfo) || !ForeignAccess.sendHasKeys(hasKeysNode, truffleReceiver)) {
                                CompilerDirectives.transferToInterpreter();
                                return polyglot.getMemberUnsupported(receiver, key);
                            }
                        }
                    }
                } catch (UnsupportedMessageException | UnknownIdentifierException e) {
                    if (optimistic) {
                        CompilerDirectives.transferToInterpreterAndInvalidate();
                        optimistic = false;
                    } else {
                        CompilerDirectives.transferToInterpreter();
                    }
                    int keyInfo = ForeignAccess.sendKeyInfo(keyInfoNode, truffleReceiver, key);
                    if (KeyInfo.isExisting(keyInfo) || !ForeignAccess.sendHasKeys(hasKeysNode, truffleReceiver)) {
                        polyglot.removeMemberUnsupported(receiver);
                    }
                }
                return false;
            }

        }

        private static class IsNullNode extends PolyglotNode {

            @Child private Node isNullNode = Message.IS_NULL.createNode();

            protected IsNullNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected String getOperationName() {
                return "isNull";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                return ForeignAccess.sendIsNull(isNullNode, (TruffleObject) receiver);
            }

        }

        private static class HasMembersNode extends PolyglotNode {

            @Child private Node hasKeysNode = Message.HAS_KEYS.createNode();

            protected HasMembersNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected String getOperationName() {
                return "hasMembers";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                return ForeignAccess.sendHasKeys(hasKeysNode, (TruffleObject) receiver);
            }

        }

        private static class HasMemberNode extends PolyglotNode {

            final Node keyInfoNode = Message.KEY_INFO.createNode();

            protected HasMemberNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, String.class};
            }

            @Override
            protected String getOperationName() {
                return "hasMember";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                String key = (String) args[1];
                int keyInfo = ForeignAccess.sendKeyInfo(keyInfoNode, (TruffleObject) receiver, key);
                return KeyInfo.isExisting(keyInfo);
            }

        }

        private static class CanExecuteNode extends PolyglotNode {

            @Child private Node isExecutableNode = Message.IS_EXECUTABLE.createNode();

            protected CanExecuteNode(Interop interop) {
                super(interop);
            }

            @Override
            protected String getOperationName() {
                return "canExecute";
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                return ForeignAccess.sendIsExecutable(isExecutableNode, (TruffleObject) receiver);
            }

        }

        private static class CanInstantiateNode extends PolyglotNode {

            @Child private Node isInstantiableNode = Message.IS_INSTANTIABLE.createNode();

            protected CanInstantiateNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected String getOperationName() {
                return "canInstantiate";
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                return ForeignAccess.sendIsInstantiable(isInstantiableNode, (TruffleObject) receiver);
            }

        }

        private static class AsPrimitiveNode extends PolyglotNode {

            @Child private Node isBoxedNode = Message.IS_BOXED.createNode();
            @Child private Node unboxNode = Message.UNBOX.createNode();

            protected AsPrimitiveNode(Interop interop) {
                super(interop);
            }

            @Override
            protected String getOperationName() {
                return "asPrimitive";
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                if (ForeignAccess.sendIsBoxed(isBoxedNode, (TruffleObject) receiver)) {
                    try {
                        return ForeignAccess.sendUnbox(unboxNode, (TruffleObject) receiver);
                    } catch (UnsupportedMessageException e) {
                        CompilerDirectives.transferToInterpreter();
                        throw new AssertionError("isBoxed returned true but unbox threw unsupported error.");
                    }
                } else {
                    return null;
                }
            }
        }

        private abstract static class AbstractExecuteNode extends PolyglotNode {

            @Child private Node executeNode = Message.createExecute(0).createNode();
            private final ToGuestValuesNode toGuestValues = ToGuestValuesNode.create();

            protected AbstractExecuteNode(Interop interop) {
                super(interop);
            }

            protected final Object executeShared(Object receiver, Object[] args) {
                Object[] guestArguments = toGuestValues.apply(polyglot.languageContext, args);
                try {
                    return ForeignAccess.sendExecute(executeNode, (TruffleObject) receiver, guestArguments);
                } catch (UnsupportedTypeException e) {
                    CompilerDirectives.transferToInterpreter();
                    throw invalidExecuteArgumentType(polyglot.languageContext, receiver, e);
                } catch (ArityException e) {
                    CompilerDirectives.transferToInterpreter();
                    throw invalidExecuteArity(polyglot.languageContext, receiver, guestArguments, e.getExpectedArity(), e.getActualArity());
                } catch (UnsupportedMessageException e) {
                    CompilerDirectives.transferToInterpreter();
                    return polyglot.executeUnsupported(receiver);
                }
            }

        }

        private static class ExecuteVoidNode extends AbstractExecuteNode {

            protected ExecuteVoidNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, Object[].class};
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                executeShared(receiver, (Object[]) args[1]);
                return null;
            }

            @Override
            protected String getOperationName() {
                return "executeVoid";
            }

        }

        private static class ExecuteVoidNoArgsNode extends AbstractExecuteNode {

            private static final Object[] NO_ARGS = new Object[0];

            protected ExecuteVoidNoArgsNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                executeShared(receiver, NO_ARGS);
                return null;
            }

            @Override
            protected String getOperationName() {
                return "executeVoid";
            }

        }

        private static class ExecuteNode extends AbstractExecuteNode {

            private final ToHostValueNode toHostValue = polyglot.languageContext.createToHostValue();

            protected ExecuteNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, Object[].class};
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                return toHostValue.execute(executeShared(receiver, (Object[]) args[1]));
            }

            @Override
            protected String getOperationName() {
                return "execute";
            }

        }

        private static class ExecuteNoArgsNode extends AbstractExecuteNode {

            private final ToHostValueNode toHostValue = polyglot.languageContext.createToHostValue();

            protected ExecuteNoArgsNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType};
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                return toHostValue.execute(executeShared(receiver, ExecuteVoidNoArgsNode.NO_ARGS));
            }

            @Override
            protected String getOperationName() {
                return "execute";
            }

        }

        private static class NewInstanceNode extends PolyglotNode {

            @Child private Node newInstanceNode = Message.createNew(0).createNode();
            private final ToGuestValuesNode toGuestValues = ToGuestValuesNode.create();
            private final ToHostValueNode toHostValue = polyglot.languageContext.createToHostValue();

            protected NewInstanceNode(Interop interop) {
                super(interop);
            }

            @Override
            protected Class[] getArgumentTypes() {
                return new Class[]{polyglot.receiverType, Object[].class};
            }

            @Override
            protected Object executeImpl(Object receiver, Object[] args) {
                Object[] instantiateArguments = toGuestValues.apply(polyglot.languageContext, (Object[]) args[1]);
                try {
                    return toHostValue.execute(ForeignAccess.sendNew(newInstanceNode, (TruffleObject) receiver, instantiateArguments));
                } catch (UnsupportedTypeException e) {
                    CompilerDirectives.transferToInterpreter();
                    throw invalidInstantiateArgumentType(polyglot.languageContext, receiver, args);
                } catch (ArityException e) {
                    CompilerDirectives.transferToInterpreter();
                    throw handleInvalidArity(e);
                } catch (UnsupportedMessageException e) {
                    CompilerDirectives.transferToInterpreter();
                    return polyglot.newInstanceUnsupported(receiver);
                }
            }

            private PolyglotException handleInvalidArity(ArityException e) {
                int actual = e.getActualArity();
                int expected = e.getExpectedArity();
                return error(String.format("Expected %s number of arguments but got %s when creating a new instance of %s.", expected, actual, toString()));
            }

            @Override
            protected String getOperationName() {
                return "newInstance";
            }

        }

        private final class MemberSet extends AbstractSet {

            private final TruffleObject receiver;
            private final TruffleObject keys;
            private int cachedSize = -1;

            MemberSet(TruffleObject receiver, TruffleObject keys) {
                this.receiver = receiver;
                this.keys = keys;
            }

            @Override
            public boolean contains(Object o) {
                if (!(o instanceof String)) {
                    return false;
                }
                Object prev = languageContext.context.enterIfNeeded();
                try {
                    int keyInfo = ForeignAccess.sendKeyInfo(keyInfoNode, receiver, o);
                    return KeyInfo.isExisting(keyInfo);
                } catch (Throwable e) {
                    throw PolyglotImpl.wrapGuestException(languageContext, e);
                } finally {
                    languageContext.context.leaveIfNeeded(prev);
                }
            }

            @Override
            public Iterator iterator() {
                return new Iterator() {

                    int index = 0;

                    public boolean hasNext() {
                        return index < size();
                    }

                    public String next() {
                        if (index >= size()) {
                            throw new NoSuchElementException();
                        }
                        Object prev = languageContext.context.enterIfNeeded();
                        try {
                            try {
                                Object result = ForeignAccess.sendRead(keysReadNode, keys, index);
                                if (!(result instanceof String || result instanceof Character)) {
                                    throw PolyglotImpl.wrapHostException(languageContext, new ClassCastException("Cannot cast " + result + " to String."));
                                }
                                index++;
                                return result.toString();
                            } catch (UnsupportedMessageException | UnknownIdentifierException e) {
                                throw new AssertionError("Implementation error: Language must support read messages for keys objects.");
                            }
                        } catch (Throwable e) {
                            throw PolyglotImpl.wrapGuestException(languageContext, e);
                        } finally {
                            languageContext.context.leaveIfNeeded(prev);
                        }
                    }
                };
            }

            @Override
            public int size() {
                if (cachedSize != -1) {
                    return cachedSize;
                }
                Object prev = languageContext.context.enterIfNeeded();
                try {
                    try {
                        cachedSize = ((Number) ForeignAccess.sendGetSize(keysSizeNode, keys)).intValue();
                    } catch (UnsupportedMessageException e) {
                        return 0;
                    }
                    return cachedSize;
                } catch (Throwable e) {
                    throw PolyglotImpl.wrapGuestException(languageContext, e);
                } finally {
                    languageContext.context.leaveIfNeeded(prev);
                }
            }

        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy